Add HTTP method/body, alert escalation, baseline alerts, notification grouping, rate limiting, App.js split, and API tests

- 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>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-31 09:37:31 -04:00
1 parent a6cf6a9762
commit 4fea0a95f5
19 files changed
+1795 -1966

No files matched your search

+14 -3
View File
@@ -145,15 +145,26 @@ class MonitorService {
}
}
const isHttps = resource.url.startsWith('https');
const response = await axios.get(resource.url, {
const method = (resource.http_method || 'GET').toUpperCase();
const requestConfig = {
method,
url: resource.url,
timeout: resource.timeout,
validateStatus: () => true,
maxRedirects: 5,
headers,
httpAgent: this.httpAgent,
httpsAgent: this.httpsAgent,
});
};
if (resource.http_body && ['POST', 'PUT', 'PATCH'].includes(method)) {
try {
requestConfig.data = JSON.parse(resource.http_body);
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
} catch {
requestConfig.data = resource.http_body;
}
}
const response = await axios(requestConfig);
const responseTime = Date.now() - startTime;
const isUp = response.status >= 200 && response.status < 400;