The previous fix changed code defaults to dark but the DB still had
theme='light' stored explicitly — a truthy value that bypassed all
fallbacks. On every restart the server was returning theme:'light',
which the client applied via data-theme="light", making the app white.
- Delete the 'theme' key from DB on startup (one-time migration)
- Always return theme:'dark' from buildSettingsFromDb (not from DB)
- Stop writing theme to DB/env/process.env on settings save
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app is a dark-mode design but all theme defaults were 'light'.
Once the theme setting was wired to data-theme, it applied light mode
overrides that turned the app background white while panels stayed dark.
- Changed all server-side theme defaults from 'light' to 'dark'
- Added data-theme="dark" to index.html so the correct theme applies
before React loads (eliminates any white flash on first render)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- maintenance_mode and hasActiveIncident are SQLite integers (0/1), not JS booleans.
{0 && <span>} evaluates to {0} which React renders as the text "0" in the DOM.
Fixed with !! coercion on both conditions in renderResourceRow.
- avgResponseTime can be NaN for resources with no checks (e.g. icmp with no data).
Added || 0 guard in the aggregate average and —/ms conditional in the row display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hard-coded white/light colors with dark theme palette matching the rest
of the app. Key changes: dark navy background (#1e293b/#0f172a), high-contrast
text (#f1f5f9 titles, #94a3b8 body), teal accent (#14b8a6) for active filter and
unread left-border, red (#f87171) clear button, dark scrollbar, subtle hover
states on items and action buttons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HTTP method + request body: GET/POST/PUT/PATCH/DELETE/HEAD selector and body textarea for http/health checks; stored in http_method/http_body columns; monitorService sends correct axios method+data
- Alert escalation: incidents open longer than escalation_hours get forwarded to fallback_webhook and marked escalated=1; hourly cron in scheduler
- Baseline-relative slow alerts: effectiveThreshold = response_time_threshold || (baseline * 2) — adaptive threshold without manual config
- Notification grouping: pendingAlerts collected per check cycle; ≥2 alerts flush as one grouped email+webhook; individual in-app entries still saved per resource
- Rate limiting: in-memory per-IP limiter (20/min heartbeat, 60/min status-page); no external package
- App.js split: Dashboard.js (monitors list + modals) and ResourceDetail.js extracted; App.js reduced from ~1987 to ~80 lines
- formatDuration moved to utils/timeUtils.js; imported by both new components
- DB migrations: http_method, http_body, incidents.escalated columns
- 18 passing API/unit tests (node:test, zero dependencies)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Heartbeat type: cron jobs POST to /api/heartbeat/:token to stay alive; scheduler marks
them down if no ping is received within the configured timeout window
- Alert rules: per-resource consecutive_failures_threshold now respected in handleIncident,
overriding the global setting; GET /api/resources/:id/alert-rules endpoint added
- SSL cert expiry badge: dashboard rows surface days_remaining for TLS monitors, turning
orange at ≤30 days and red at ≤7 days
- Webhook payload templates: {{name}}/{{status}}/{{url}}/{{type}}/{{message}}/{{timestamp}}
variables interpolated before POST; Slack-compatible text wrapping as fallback
- Status page visibility: is_public column per resource; GET /api/status-page returns only
public resources; Status.js updated to use the new endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Refactored SettingsWizard to allow saving each section separately (Email, Webhook, Monitoring)
- Each section now has its own Save button
- Removed global Save button - sections save independently
- No longer need to re-enter email passwords when changing other settings
- Added success/error status messages that appear for 3-5 seconds
- Status messages appear as toast notifications in top-right corner
- Updated CSS with save-status styling and slideIn animation
- handleSaveSection() only POSTs the relevant fields for that section
- Add description and failed_check_count fields to incidents table
- Update handleIncident() to use configurable threshold (default 10 checks)
- Auto-generate incident description with:
- Number of consecutive failures
- Last error message/reason
- First failure timestamp
- Add API endpoints: GET/POST /api/settings/incident-threshold
- Add UI settings field to configure incident failure threshold
- Server: Get threshold from database settings (persistent across restarts)
- Default threshold: 10 consecutive failed checks per resource
- Allows users to enter multiple email addresses separated by commas
- Updated both Add Resource and Edit Resource forms
- Improved placeholder text to show example of comma-separated format
- notificationService.js: Parse comma-separated emails from resource.email_to or global config; validate and send to each recipient
- index.js: Add PATCH /api/resources/:id/group endpoint to move resource between groups with validation and cache invalidation
- App.js: Add group selector to edit modal; wire group change to PATCH endpoint; add help text for multi-email format (comma-separated)
- notificationService: Parse comma-separated emails from resource.email_to or global config; validate and send to each recipient
- API: Add PATCH /api/resources/:id/group endpoint to move resource between groups with validation and cache invalidation
- UI: Add group selector to edit modal; wire group change to PATCH endpoint; add help text for multi-email format