Files
BetterDesk/web-nodejs/services/alertRulesEngine.js
UNITRONIX e855f5786d Add Go server and security audit; update web console
Add a new betterdesk-server Go codebase (server, api, auth, db, relay, signal, metrics, audit, ratelimit, proto, tools, tests) and related deployment/migration scripts. Add a comprehensive SECURITY_AUDIT_2026-03-01 report and .gitattributes; update copilot-instructions (ALL-IN-ONE v2.4.0), README, VERSION, Dockerfiles, scripts, docker-compose and entrypoint. Large updates to web-nodejs (translations, routes, services, frontend assets and middleware) and numerous new utilities; remove legacy Flask web files and archive hbbs-patch-v2 artifacts. Prepares repository for PostgreSQL support, DB migration tooling and the new Go server as the production backend.
2026-03-02 00:43:04 +01:00

138 lines
4.5 KiB
JavaScript

/**
* BetterDesk Console — Alert Rules Engine
*
* Evaluates alert rules against incoming telemetry / activity data
* and triggers notifications when conditions are met.
*
* Supported condition types:
* - cpu_usage (threshold on cpu_usage_percent)
* - memory_usage (threshold on memory_used_bytes / memory_total_bytes ratio)
* - disk_usage (threshold on disk usage percent)
* - offline_duration (device offline > N seconds)
* - idle_duration (user idle > N seconds)
* - custom (always triggers — used for scheduled checks)
*
* Condition operators: gt, gte, lt, lte, eq, neq
*
* @author UNITRONIX
* @version 1.0.0
*/
'use strict';
const { getAdapter } = require('./dbAdapter');
const emailService = require('./emailService');
// Cooldown: minimum seconds between repeated alerts for the same rule + device
const DEFAULT_COOLDOWN_SECS = 300; // 5 minutes
// ---------------------------------------------------------------------------
// Operator helpers
// ---------------------------------------------------------------------------
const OPS = {
gt: (a, b) => a > b,
gte: (a, b) => a >= b,
lt: (a, b) => a < b,
lte: (a, b) => a <= b,
eq: (a, b) => a === b,
neq: (a, b) => a !== b,
};
function evalCondition(value, op, threshold) {
const fn = OPS[op];
if (!fn) return false;
return fn(Number(value), Number(threshold));
}
// ---------------------------------------------------------------------------
// Evaluation
// ---------------------------------------------------------------------------
/**
* Evaluate all enabled alert rules against a specific metric push.
*
* @param {string} deviceId
* @param {string} metricType - e.g. 'cpu_usage', 'memory_usage', 'offline_duration'
* @param {number} metricValue
*/
async function evaluateRules(deviceId, metricType, metricValue) {
const adapter = getAdapter();
let rules;
try {
rules = await adapter.getAlertRules({ enabled: true, condition_type: metricType });
} catch (err) {
console.error('[AlertEngine] Failed to load rules:', err.message);
return;
}
for (const rule of rules) {
// Scope check: rule.scope_device_id can be null (all devices) or specific
if (rule.scope_device_id && rule.scope_device_id !== deviceId) continue;
const triggered = evalCondition(metricValue, rule.condition_op, rule.condition_value);
if (!triggered) continue;
// Cooldown check
const cooldown = rule.cooldown_secs || DEFAULT_COOLDOWN_SECS;
try {
const recent = await adapter.getRecentAlert(rule.id, deviceId, cooldown);
if (recent) continue; // Still in cooldown
} catch (_) { /* ignore — fire anyway */ }
// Fire alert
const alert = {
rule_id: rule.id,
device_id: deviceId,
severity: rule.severity || 'warning',
message: `${rule.name}: ${metricType} = ${metricValue} (${rule.condition_op} ${rule.condition_value})`,
triggered_at: new Date().toISOString(),
acknowledged: false,
};
try {
await adapter.createAlert(alert);
console.log(`[AlertEngine] ALERT fired: ${alert.message} for ${deviceId}`);
// Email notification
if (rule.notify_emails) {
emailService.sendAlertEmail(alert, rule).catch(err => {
console.error('[AlertEngine] Email failed:', err.message);
});
}
} catch (err) {
console.error('[AlertEngine] Failed to create alert:', err.message);
}
}
}
/**
* Evaluate telemetry data against all applicable rules.
* Called when a device sends a telemetry update.
*/
async function evaluateTelemetry(deviceId, telemetry) {
if (telemetry.cpu_usage_percent !== undefined) {
await evaluateRules(deviceId, 'cpu_usage', telemetry.cpu_usage_percent);
}
if (telemetry.memory_used_bytes && telemetry.memory_total_bytes) {
const ratio = (telemetry.memory_used_bytes / telemetry.memory_total_bytes) * 100;
await evaluateRules(deviceId, 'memory_usage', Math.round(ratio));
}
}
/**
* Periodic sweep: check offline devices, scheduled tasks, etc.
* Should be called on a timer (e.g. every 60s).
*/
async function periodicCheck() {
// Placeholder: offline duration checks, scheduled rule evaluations
// Can be extended to query all peers and check heartbeat timestamps.
}
module.exports = {
evaluateRules,
evaluateTelemetry,
periodicCheck,
evalCondition,
};