PSA/shared/rmm/alerts/ruleEvaluator.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

73 lines
2.5 KiB
TypeScript

import type { NormalizedRmmAlertEvent, RmmAlertRuleRow } from './contracts';
export interface RuleEvaluationResult {
rule: RmmAlertRuleRow | null;
/** Non-fatal problems encountered while evaluating (bad stored regex etc.). */
warnings: string[];
}
/**
* First-match rule selection. Callers pass active rules for the integration
* ordered by priority_order ascending. Every condition field present on a rule
* must match; a rule with no conditions is a catch-all. A rule that cannot be
* evaluated (e.g. an invalid stored regex) is skipped with a warning and never
* aborts evaluation.
*/
export function evaluateAlertRules(
rules: RmmAlertRuleRow[],
event: NormalizedRmmAlertEvent
): RuleEvaluationResult {
const warnings: string[] = [];
for (const rule of rules) {
try {
if (ruleMatches(rule, event)) {
return { rule, warnings };
}
} catch (error) {
warnings.push(
`Rule ${rule.rule_id} (${rule.name}) skipped: ${error instanceof Error ? error.message : String(error)}`
);
}
}
return { rule: null, warnings };
}
function ruleMatches(rule: RmmAlertRuleRow, event: NormalizedRmmAlertEvent): boolean {
const conditions = typeof rule.conditions === 'string' ? JSON.parse(rule.conditions) : rule.conditions ?? {};
if (conditions.severities?.length && !conditions.severities.includes(event.severity)) {
return false;
}
if (conditions.activityTypes?.length && !includesValue(conditions.activityTypes, event.activityType)) {
return false;
}
if (conditions.alertClasses?.length && !includesValue(conditions.alertClasses, event.alertClass)) {
return false;
}
if (conditions.sourceTypes?.length && !includesValue(conditions.sourceTypes, event.sourceType)) {
return false;
}
if (
conditions.organizationIds?.length &&
!includesValue(conditions.organizationIds, event.externalOrganizationId)
) {
return false;
}
if (conditions.keywords?.length) {
const message = (event.message ?? '').toLowerCase();
const hasKeyword = conditions.keywords.some((keyword: string) => message.includes(keyword.toLowerCase()));
if (!hasKeyword) return false;
}
if (conditions.messagePattern) {
// May throw on an invalid stored pattern; caught by the caller as a skip.
const pattern = new RegExp(conditions.messagePattern);
if (!pattern.test(event.message ?? '')) return false;
}
return true;
}
function includesValue(haystack: string[], value: string | null | undefined): boolean {
return value != null && haystack.includes(value);
}