The previous fix (agent .on('error')) doesn't reach individual socket
objects — socket-level parse errors still escaped as uncaught exceptions.
Root fix: disable keepAlive on the HTTP/HTTPS monitoring agents. Without
connection reuse there is no stale connection to trigger "Parse Error:
Data after Connection: close". Monitoring intervals are 60s+ so the lack
of connection pooling has no meaningful performance impact.
Belt-and-suspenders: add process.on('unhandledRejection') alongside the
existing uncaughtException guard to catch any async paths that leak.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AQYLFrscmMnzMiXAAANoa
Monitored services that send data after Connection: close (malformed HTTP)
were causing unhandled 'Parse Error' exceptions, crashing the process and
triggering a PM2 restart loop (191 restarts observed) which caused 502s.
Two-layer fix:
- monitorService.js: attach .on('error', noop) to the shared HTTP/HTTPS
keep-alive agents so socket-level parse errors don't escape as uncaught
exceptions
- index.js: process-level uncaughtException guard that swallows HTTP parse
errors (HPE_INVALID_CONSTANT / 'Parse Error') while still crashing on
real fatal errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AQYLFrscmMnzMiXAAANoa
Previously a single check above 2× average response time fired an alert.
This caused false positives from normal network variance.
- scheduler.js: track consecutive slow checks per resource; only alert after
3 in a row (resets to 0 when response time normalises)
- monitorService.js: calculateResponseTimeBaseline now returns P95 instead
of mean, making 2× baseline a much more realistic slow threshold
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>
- Add in-app notification system with database storage
- Implement bell icon component with unread count badge in dashboard header
- Add full Notifications page tab with pagination and filtering (All/Unread/Read)
- Implement real-time WebSocket push notifications to all connected clients
- Add notification API endpoints: GET, PUT /read, DELETE, POST /clear
- Fix critical bug in notificationService where 'status' variable was undefined
- Add Notifications route to App.js routing
- Enhanced UI with notification popup in header and dedicated full-page view
- Auto-reconnect WebSocket with 5s timeout on disconnect
- Support rich notification content with resource details and timestamps
- 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
- 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