- DB: new agent_commands table (id, agent_id, command, status, output, exit_code, timestamps)
- Server: 4 new routes
POST /api/agents/:id/commands queue a command (dashboard → agent)
GET /api/agents/:id/commands history (last 50) for dashboard
GET /api/agents/commands/pending agent dequeues + marks running (Bearer auth)
POST /api/agents/commands/:id/result agent posts output/exit_code (Bearer auth)
Output capped at 128 KB; Bearer token enforced on agent-facing routes
- Agents (Linux/macOS/Windows): each polls /commands/pending each cycle,
executes via bash/PowerShell with 60s timeout, posts result back
- UI: ">_" button on each AgentCard opens CommandTerminal panel
Input line, Run button, history list (status icon + command + time)
Click any entry with output to expand/collapse its output
Auto-refreshes every 4 s while open
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- agent/skywatch-agent-macos.sh: Full macOS bash agent (launchd)
- CPU via top -l 2, memory via vm_stat/sysctl, disk via df -P
- Network via netstat -ib, uptime via kern.boottime, OS via sw_vers
- agent/install-agent-macos.sh: Installs macOS agent as launchd daemon
- agent/skywatch-agent-windows.ps1: PowerShell agent (Task Scheduler)
- CPU via Win32_PerfRawData two-sample delta, memory via Win32_OperatingSystem
- Disk via Get-PSDrive, network via Get-NetAdapterStatistics
- agent/install-agent-windows.ps1: Installs Windows agent via Task Scheduler
- server/index.js: New routes for macOS and Windows agent scripts/installers
- client/src/Agents.js: Multi-platform install panel with Linux/macOS/Windows tabs
- client/src/Agents.css: Platform tab button styles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- client/src/Agents.js: page showing registered Linux agents with live
status, CPU/memory/disk gauge bars, load/uptime/network stats, and
an install-instructions panel with copyable curl commands
- client/src/Agents.css: dark-theme card grid styles matching the app
- client/src/App.js: adds /agents route and Agents nav link
- server/index.js: adds GET /api/agents/installer to serve install-agent.sh
- client/build: production build updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server fixes:
- database.js: auto-create data/ directory on startup — was crashing on
fresh clone because the gitignored directory didn't exist
- better-sqlite3 upgraded to latest (13.x) for Node 26 compatibility
Network discovery:
- discoverNetwork() accepts optional subnet param; scans that range instead
of auto-detecting when provided
- New GET /api/network-subnets endpoint returns detected local interfaces
so UI can offer quick-select buttons
- Discovery modal: subnet text input + Scan button + auto-detect quick-select;
local subnet buttons populate once the modal is open
- server/index.js: pass subnet from request body to discoverNetwork()
Infra:
- React client runs on PORT=3456 in dev to avoid port conflicts
- .claude/launch.json added for one-command preview via Express (serves
the pre-built client, no port conflict with the preview tool)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug fixes:
- tags field was missing from INSERT/UPDATE SQL — tags never persisted
- is_public: `0 !== false` is true in JS, so private resources (is_public=0)
were silently flipped to public on every edit; fixed with explicit 0/false check
Global setting inheritance:
- One-time DB migration nulls out check_interval and timeout on all existing
resources so they inherit from global monitoring settings going forward
- Scheduler reads global check_interval and timeout from DB each cycle and
uses them as the fallback when per-resource values are null
- Create and edit modals: empty field = use global; placeholder shows the
current global value so it's clear what will be used
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes the consecutive-slow-checks requirement as a configurable setting
instead of hardcoding 3. Saved under key 'slow_alert_consecutive' in the
DB, readable via SLOW_ALERT_CONSECUTIVE env var. UI appears in Settings →
Monitoring alongside the existing Consecutive Failures Threshold field.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract getServerTimezone() helper in timeUtils.js; apply it in
formatChartTime and formatLocalTimeOnly (previously ignored TZ)
- Status.js: remove local formatLocalTime (was bare toLocaleString),
import the shared util that reads server timezone
- SettingsWizard: immediately update localStorage when timezone is saved
so new value takes effect without waiting for cache to expire
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bugs fixed:
- Status page: autoRefresh checkbox now actually pauses the interval
- Dashboard edit modal: hide URL field for heartbeat resources (was always shown as required)
- Dashboard: apply items_per_page, default_sort, and refresh_interval from settings on mount
- Dashboard: call loadResources() immediately on mount (was waiting 15s for fallback)
- Observability: listen for WS 'metrics' messages so tab updates live, not just on poll
UX improvements:
- Dashboard: replaced window.confirm() with inline Confirm/Cancel buttons for single and bulk delete
- Dashboard: skeleton rows shown while initial data loads (no more flash of empty state)
- Notifications: WebSocket moved from window global to useRef; exponential backoff on reconnect
- Notifications: replaced window.confirm() with inline Confirm/Cancel for Clear All
- ResourceDetail: initial API calls run in parallel via Promise.all
New features:
- ntfy.sh push notification channel: enable in Settings, enter topic, get mobile push alerts
- Network Discovery: Actions → Discover Network scans LAN for reachable hosts, probes HTTP ports, and lets you add any found host to monitoring in one click
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>
- 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
- 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: 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