mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
e855f5786d
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.
138 lines
4.5 KiB
JavaScript
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,
|
|
};
|