Skip to content

feat: uptime alarms with deduplication and non-blocking evaluation#332

Open
maoshuorz wants to merge 4 commits intodatabuddy-analytics:mainfrom
maoshuorz:feat/uptime-alarms
Open

feat: uptime alarms with deduplication and non-blocking evaluation#332
maoshuorz wants to merge 4 commits intodatabuddy-analytics:mainfrom
maoshuorz:feat/uptime-alarms

Conversation

@maoshuorz
Copy link

Summary

Adds uptime alarm evaluation to the monitoring pipeline:

  • Alarm trigger service with deduplication (prevents notification spam on repeated failures)
  • Non-blocking integration — alarm evaluation runs in background via .catch(), never slows uptime checks
  • New alarmTriggers table tracks trigger history (down/recovery events with HTTP codes)
  • Dashboard UI component for managing uptime alarms per website with trigger history view
  • New RPC endpoints: listByWebsite and listTriggers for website-scoped alarm access

Changes

  • apps/uptime/src/lib/alarm-trigger.ts — Core alarm evaluation service with dedup logic
  • apps/uptime/src/index.ts — Non-blocking alarm evaluation after each uptime check
  • apps/dashboard/app/(main)/websites/[id]/pulse/_components/uptime-alarms.tsx — Full alarm management UI
  • apps/dashboard/app/(main)/websites/[id]/pulse/page.tsx — Integrated UptimeAlarms component
  • packages/db/src/drizzle/schema.ts — New alarmTriggers table
  • packages/db/src/drizzle/relations.ts — Trigger relations
  • packages/rpc/src/routers/alarms.ts — Added listByWebsite + listTriggers endpoints

Resolves #268

/claim

- Add alarm_triggers table to track alarm trigger history (down/recovery events)
- Add alarm evaluation service in uptime worker that fires notifications
  on site down and sends recovery notifications when site comes back up
- Prevent duplicate notifications by checking last trigger state
- Add alarms.listByWebsite and alarms.listTriggers API endpoints
- Add UptimeAlarms component to pulse page with:
  - List of assigned alarms with enable/disable toggles
  - Quick-create sheet for new uptime alarms
  - Trigger history view showing down/recovery events
  - Alarm count badge in the pulse page header
- Add @databuddy/notifications dependency to uptime app
@CLAassistant
Copy link

CLAassistant commented Mar 6, 2026

CLA assistant check
All committers have signed the CLA.

@vercel
Copy link

vercel bot commented Mar 6, 2026

@maoshuorz is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cb41d891-8888-433c-88dd-c60ce997eac5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@maoshuorz
Copy link
Author

recheck

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR adds a complete uptime alarm system: a new alarmTriggers DB table, a backend alarm-trigger.ts service with down/recovery deduplication, two new RPC endpoints (listByWebsite, listTriggers), and dashboard UI components for managing alarms per website and globally in Settings → Notifications. The integration into the uptime check pipeline is correctly non-blocking via .catch().

Key issues found:

  • Email channel silently fails (alarm-trigger.ts): The email case always records success: false with a "not available" error, but the UI exposes email as a fully selectable notification channel. Users who configure email alerts will never receive them with no UI feedback.
  • Permission mapping inconsistency (alarms.ts router): authorizeScope collapses both "update" and "delete" operations to a "create" permission check for org-scoped alarms, allowing users with only create access to delete alarms.
  • Duplicated Alarm interface: The interface is independently redefined in uptime-alarms.tsx and settings/notifications/_components/types.ts, creating a drift risk.

Confidence Score: 2/5

  • Not safe to merge — contains a functional issue where email notifications silently fail and a security issue where users with create-only permission can delete alarms.
  • Two critical issues block merge: (1) The email notification channel is selectable in the UI but silently fails in the backend, providing no feedback to users that their email alerts won't arrive. This directly affects user-facing functionality. (2) The permission mapping in authorizeScope collapses both "update" and "delete" operations to a "create" permission check, allowing users with only create permission to delete alarms — a privilege escalation vulnerability. Additionally, there is a code maintenance concern with the duplicated Alarm interface that should be consolidated to prevent drift. The non-blocking integration pattern and deduplication logic are correctly implemented, but these issues must be resolved before shipping.
  • apps/uptime/src/lib/alarm-trigger.ts (email silently fails), packages/rpc/src/routers/alarms.ts (permission mapping vulnerability), apps/dashboard/app/(main)/websites/[id]/pulse/_components/uptime-alarms.tsx (duplicated Alarm interface).

Last reviewed commit: 138bb1a

Comment on lines +206 to +212
case "email": {
// Email requires server-side provider; skip in uptime worker
results.push({
channel: "email",
success: false,
error: "Email not available in uptime worker",
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Email channel silently fails for all users

The email case always records success: false with the error "Email not available in uptime worker", yet the UI (uptime-alarms.tsx line 88 and alarm-sheet.tsx line 70) shows Email as a fully selectable notification channel. Any user who configures an email notification will silently receive no alerts with no indication in the UI that the channel is broken.

Either the email channel should be removed from the uptime alarm UI, or a prominent warning should be displayed when it's selected, or email dispatch should be implemented here (e.g., via a queue).

context.headers.forEach((value, key) => {
headersObj[key] = value;
});
const perm = permission === "read" ? "read" : "create";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"delete" permission mapped to "create" in authorizeScope

Inside authorizeScope, any permission that isn't "read" (including "delete") is mapped to "create" before calling websitesApi.hasPermission:

const perm = permission === "read" ? "read" : "create";

This means a user who only has "create" permission on the organization is allowed to delete alarms — a more destructive operation. The delete handler calls authorizeScope(context, undefined, alarm.organizationId, "delete") expecting stricter enforcement, but the check collapses to a "create" permission test. Consider using a dedicated "delete" or "admin" scope here, or documenting clearly that "create" covers both create and delete.

Comment on lines +51 to +71
interface Alarm {
id: string;
name: string;
description: string | null;
enabled: boolean;
notificationChannels: unknown;
slackWebhookUrl: string | null;
discordWebhookUrl: string | null;
webhookUrl: string | null;
webhookHeaders: unknown;
emailAddresses: unknown;
triggerType: string;
triggerConditions: unknown;
websiteId: string | null;
organizationId: string | null;
userId: string | null;
createdBy: string;
createdAt: Date | string;
updatedAt: Date | string;
deletedAt: Date | string | null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alarm interface duplicated across two files

A nearly identical Alarm interface is defined here and again in apps/dashboard/app/(main)/settings/notifications/_components/types.ts. The two definitions can drift independently. Consider importing the shared type from types.ts:

import type { Alarm } from "@/app/(main)/settings/notifications/_components/types";

or, better, co-locating it in a shared module (e.g., @databuddy/shared).

- Fix permission mapping in authorizeScope: properly map "delete"
  permission instead of collapsing it to "create", preventing
  privilege escalation where create-only users could delete alarms
- Remove email from selectable notification channels in UI since
  email sending is not yet implemented in the uptime worker
- Add clear warning log when email channel is attempted
- Consolidate duplicated Alarm interface — import from shared types
@maoshuorz
Copy link
Author

Thanks for the review @greptile-apps! All issues addressed:

  1. Permission escalation fixedauthorizeScope now properly maps "delete" permission instead of collapsing it to "create". Users with only create-level access can no longer delete org-scoped alarms.

  2. Email channel removed from UI — Since email sending requires a transactional email provider not yet configured in the uptime worker, email has been removed from the selectable notification channels in both the uptime alarms UI and the settings alarm sheet. This prevents users from configuring a channel that silently fails. Added a clear warning log for operators.

  3. Consolidated duplicated Alarm interface — The Alarm interface in uptime-alarms.tsx now imports from the shared types.ts file, eliminating drift risk.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 7, 2026

All three fixes look great!

Permission mapping (packages/rpc/src/routers/alarms.ts:43-48) now correctly preserves "delete" permission instead of downgrading to "create".

Email channel is removed from the CHANNELS constant and the backend returns a clear error message: "Email notifications require server-side email configuration".

Alarm interface properly imports from @/app/(main)/settings/notifications/_components/types — no more duplication.

Everything checks out. Nice work addressing the security issue and preventing the email misconfiguration footgun! 🎯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🎯 Bounty: Uptime Monitoring Alarm Integration

2 participants