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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
73 lines
2.5 KiB
TypeScript
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);
|
|
}
|