polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
515 lines • 18.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlertRuleManager = void 0;
const events_1 = require("events");
class AlertRuleManager extends events_1.EventEmitter {
constructor() {
super();
this.rules = new Map();
this.lastTriggerTimes = new Map();
this.evaluationIntervalMs = 5000;
this.isRunning = false;
this.setupDefaultRules();
}
setupDefaultRules() {
const cpuRule = {
id: 'cpu-high-usage',
name: 'High CPU Usage',
description: 'Alert when CPU usage exceeds threshold',
enabled: true,
level: 'warning',
type: 'system',
conditions: [
{
type: 'threshold',
field: 'cpu.usage',
operator: '>',
value: 80,
timeWindow: 30000,
}
],
actions: [
{
type: 'log',
config: { level: 'warn' },
enabled: true,
}
],
cooldown: 60000,
priority: 2,
tags: ['system', 'performance'],
createdAt: Date.now(),
updatedAt: Date.now(),
triggerCount: 0,
};
const memoryRule = {
id: 'memory-high-usage',
name: 'High Memory Usage',
description: 'Alert when memory usage exceeds threshold',
enabled: true,
level: 'warning',
type: 'system',
conditions: [
{
type: 'threshold',
field: 'memory.percentage',
operator: '>',
value: 85,
timeWindow: 30000,
}
],
actions: [
{
type: 'log',
config: { level: 'warn' },
enabled: true,
}
],
cooldown: 60000,
priority: 2,
tags: ['system', 'memory'],
createdAt: Date.now(),
updatedAt: Date.now(),
triggerCount: 0,
};
const criticalCpuRule = {
id: 'cpu-critical-usage',
name: 'Critical CPU Usage',
description: 'Alert when CPU usage is critically high',
enabled: true,
level: 'critical',
type: 'system',
conditions: [
{
type: 'threshold',
field: 'cpu.usage',
operator: '>',
value: 95,
timeWindow: 10000,
}
],
actions: [
{
type: 'log',
config: { level: 'error' },
enabled: true,
},
{
type: 'notification',
config: { title: 'Critical CPU Alert', urgent: true },
enabled: true,
}
],
cooldown: 30000,
priority: 1,
tags: ['system', 'critical'],
createdAt: Date.now(),
updatedAt: Date.now(),
triggerCount: 0,
};
const streamDisconnectRule = {
id: 'stream-disconnected',
name: 'Stream Disconnected',
description: 'Alert when a stream goes offline unexpectedly',
enabled: true,
level: 'error',
type: 'stream',
conditions: [
{
type: 'state_change',
field: 'stream.status',
operator: '=',
value: 'offline',
}
],
actions: [
{
type: 'log',
config: { level: 'error' },
enabled: true,
}
],
cooldown: 10000,
priority: 2,
tags: ['stream', 'connection'],
createdAt: Date.now(),
updatedAt: Date.now(),
triggerCount: 0,
};
this.addRule(cpuRule);
this.addRule(memoryRule);
this.addRule(criticalCpuRule);
this.addRule(streamDisconnectRule);
}
addRule(rule) {
this.validateRule(rule);
rule.updatedAt = Date.now();
this.rules.set(rule.id, rule);
this.emit('rule:added', { rule });
}
updateRule(ruleId, updates) {
const existingRule = this.rules.get(ruleId);
if (!existingRule)
return false;
const updatedRule = {
...existingRule,
...updates,
id: ruleId,
updatedAt: Date.now(),
};
this.validateRule(updatedRule);
this.rules.set(ruleId, updatedRule);
this.emit('rule:updated', { rule: updatedRule });
return true;
}
deleteRule(ruleId) {
const rule = this.rules.get(ruleId);
if (!rule)
return false;
this.rules.delete(ruleId);
this.lastTriggerTimes.delete(ruleId);
this.emit('rule:deleted', { ruleId });
return true;
}
getRule(ruleId) {
return this.rules.get(ruleId);
}
getAllRules() {
return Array.from(this.rules.values());
}
getEnabledRules() {
return Array.from(this.rules.values()).filter(rule => rule.enabled);
}
getRulesByType(type) {
return Array.from(this.rules.values()).filter(rule => rule.type === type);
}
getRulesByTag(tag) {
return Array.from(this.rules.values()).filter(rule => rule.tags && rule.tags.includes(tag));
}
setRuleEnabled(ruleId, enabled) {
const rule = this.rules.get(ruleId);
if (!rule)
return false;
rule.enabled = enabled;
rule.updatedAt = Date.now();
this.emit('rule:toggled', { ruleId, enabled });
return true;
}
validateRule(rule) {
if (!rule.id || typeof rule.id !== 'string') {
throw new Error('Rule must have a valid ID');
}
if (!rule.name || typeof rule.name !== 'string') {
throw new Error('Rule must have a valid name');
}
if (!rule.conditions || !Array.isArray(rule.conditions) || rule.conditions.length === 0) {
throw new Error('Rule must have at least one condition');
}
if (!rule.actions || !Array.isArray(rule.actions) || rule.actions.length === 0) {
throw new Error('Rule must have at least one action');
}
rule.conditions.forEach((condition, index) => {
this.validateCondition(condition, `Condition ${index + 1}`);
});
rule.actions.forEach((action, index) => {
this.validateAction(action, `Action ${index + 1}`);
});
if (typeof rule.cooldown !== 'number' || rule.cooldown < 0) {
throw new Error('Rule cooldown must be a non-negative number');
}
if (typeof rule.priority !== 'number' || rule.priority < 1) {
throw new Error('Rule priority must be a positive number');
}
}
validateCondition(condition, context) {
if (!condition.type || !['threshold', 'state_change', 'time_window', 'pattern'].includes(condition.type)) {
throw new Error(`${context}: Invalid condition type`);
}
if (!condition.field || typeof condition.field !== 'string') {
throw new Error(`${context}: Invalid field`);
}
if (!condition.operator || !['>', '<', '=', '>=', '<=', '!=', 'contains', 'regex'].includes(condition.operator)) {
throw new Error(`${context}: Invalid operator`);
}
if (condition.value === undefined || condition.value === null) {
throw new Error(`${context}: Value is required`);
}
if (condition.timeWindow && (typeof condition.timeWindow !== 'number' || condition.timeWindow <= 0)) {
throw new Error(`${context}: Time window must be a positive number`);
}
}
validateAction(action, context) {
if (!action.type || !['log', 'notification', 'email', 'webhook', 'sound'].includes(action.type)) {
throw new Error(`${context}: Invalid action type`);
}
if (!action.config || typeof action.config !== 'object') {
throw new Error(`${context}: Action config is required`);
}
if (typeof action.enabled !== 'boolean') {
throw new Error(`${context}: Action enabled flag is required`);
}
}
start() {
if (this.isRunning)
return;
this.isRunning = true;
this.evaluationInterval = setInterval(() => {
this.evaluateRules();
}, this.evaluationIntervalMs);
this.emit('manager:started');
}
stop() {
if (!this.isRunning)
return;
this.isRunning = false;
if (this.evaluationInterval) {
clearInterval(this.evaluationInterval);
this.evaluationInterval = undefined;
}
this.emit('manager:stopped');
}
evaluateRules(context) {
if (!context) {
context = {
timestamp: Date.now(),
};
}
const results = [];
const enabledRules = this.getEnabledRules();
for (const rule of enabledRules) {
const result = this.evaluateRule(rule, context);
results.push(result);
if (result.triggered && result.alert) {
this.handleTriggeredRule(rule, result);
}
}
return results;
}
evaluateRule(rule, context) {
const result = {
ruleId: rule.id,
triggered: false,
context,
evaluatedAt: Date.now(),
details: {},
};
try {
const lastTrigger = this.lastTriggerTimes.get(rule.id);
if (lastTrigger && (Date.now() - lastTrigger) < rule.cooldown) {
result.details = result.details || {};
result.details['skipped'] = 'cooldown';
return result;
}
const conditionResults = rule.conditions.map(condition => this.evaluateCondition(condition, context));
const allConditionsMet = conditionResults.every(condResult => condResult.met);
if (allConditionsMet) {
result.triggered = true;
result.alert = this.createAlertFromRule(rule, context, conditionResults);
result.details = result.details || {};
result.details['conditionResults'] = conditionResults;
}
}
catch (error) {
result.details = result.details || {};
result.details['error'] = error instanceof Error ? error.message : 'Evaluation error';
}
return result;
}
evaluateCondition(condition, context) {
const fieldValue = this.getFieldValue(condition.field, context);
if (fieldValue === undefined || fieldValue === null) {
return { met: false, details: 'Field not found or null' };
}
let met = false;
const details = { fieldValue, operator: condition.operator, threshold: condition.value };
switch (condition.operator) {
case '>':
met = Number(fieldValue) > Number(condition.value);
break;
case '<':
met = Number(fieldValue) < Number(condition.value);
break;
case '>=':
met = Number(fieldValue) >= Number(condition.value);
break;
case '<=':
met = Number(fieldValue) <= Number(condition.value);
break;
case '=':
met = fieldValue === condition.value;
break;
case '!=':
met = fieldValue !== condition.value;
break;
case 'contains':
met = String(fieldValue).includes(String(condition.value));
break;
case 'regex': {
const regex = new RegExp(String(condition.value));
met = regex.test(String(fieldValue));
break;
}
default:
details.error = 'Unknown operator';
}
return { met, value: fieldValue, details };
}
getFieldValue(fieldPath, context) {
const parts = fieldPath.split('.');
let current = context;
for (const part of parts) {
if (current && typeof current === 'object' && part in current) {
current = current[part];
}
else {
return undefined;
}
}
return current;
}
createAlertFromRule(rule, context, conditionResults) {
const conditionsSummary = conditionResults
.filter(cr => cr.met)
.map(cr => `${cr.details?.fieldValue} ${cr.details?.operator} ${cr.details?.threshold}`)
.join(', ');
return {
level: rule.level,
type: rule.type,
title: rule.name,
message: `${rule.description || rule.name}: ${conditionsSummary}`,
source: 'alert-rule-manager',
acknowledged: false,
status: 'active',
metadata: {
ruleId: rule.id,
ruleName: rule.name,
conditionResults,
context: {
timestamp: context.timestamp,
},
},
};
}
handleTriggeredRule(rule, result) {
this.lastTriggerTimes.set(rule.id, Date.now());
rule.triggerCount++;
rule.lastTriggered = Date.now();
for (const action of rule.actions) {
if (action.enabled) {
this.executeAction(action, rule, result);
}
}
this.emit('rule:triggered', { rule, result });
}
executeAction(action, rule, result) {
try {
switch (action.type) {
case 'log':
this.executeLogAction(action, rule, result);
break;
case 'notification':
this.executeNotificationAction(action, rule, result);
break;
case 'email':
this.executeEmailAction(action, rule, result);
break;
case 'webhook':
this.executeWebhookAction(action, rule, result);
break;
case 'sound':
this.executeSoundAction(action, rule);
break;
default:
console.warn(`Unknown action type: ${action.type}`);
}
}
catch (error) {
console.error(`Failed to execute action ${action.type} for rule ${rule.id}:`, error);
this.emit('action:error', { action, rule, error });
}
}
executeLogAction(action, rule, result) {
const level = action.config['level'] || 'info';
const message = `[Alert] ${rule.name}: ${result.alert?.message}`;
if (level === 'info')
console.info(message);
else if (level === 'warn')
console.warn(message);
else if (level === 'error')
console.error(message);
else
console.log(message);
this.emit('action:executed', { action, rule, type: 'log' });
}
executeNotificationAction(action, rule, result) {
const notification = {
title: action.config['title'] || rule.name,
message: result.alert?.message || '',
level: rule.level,
urgent: action.config['urgent'] || false,
};
this.emit('notification:show', notification);
this.emit('action:executed', { action, rule, type: 'notification' });
}
executeEmailAction(action, rule, result) {
console.log(`[Email Alert] ${rule.name}: ${result.alert?.message}`);
this.emit('action:executed', { action, rule, type: 'email' });
}
executeWebhookAction(action, rule, result) {
console.log(`[Webhook Alert] ${rule.name}: ${result.alert?.message}`);
this.emit('action:executed', { action, rule, type: 'webhook' });
}
executeSoundAction(action, rule) {
process.stdout.write('\x07');
this.emit('action:executed', { action, rule, type: 'sound' });
}
getRuleStatistics() {
const rules = this.getAllRules();
const stats = {
total: rules.length,
enabled: rules.filter(r => r.enabled).length,
triggered: rules.filter(r => r.triggerCount > 0).length,
byType: { system: 0, stream: 0, channel: 0, network: 0 },
};
rules.forEach(rule => {
stats.byType[rule.type]++;
});
return stats;
}
exportRules() {
return this.getAllRules();
}
importRules(rules, replace = false) {
const errors = [];
let imported = 0;
if (replace) {
this.rules.clear();
this.lastTriggerTimes.clear();
}
for (const rule of rules) {
try {
this.validateRule(rule);
this.rules.set(rule.id, rule);
imported++;
}
catch (error) {
errors.push(`Rule ${rule.id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
this.emit('rules:imported', { imported, errors });
return { imported, errors };
}
clearRules() {
this.rules.clear();
this.lastTriggerTimes.clear();
this.emit('rules:cleared');
}
destroy() {
this.stop();
this.removeAllListeners();
this.rules.clear();
this.lastTriggerTimes.clear();
}
}
exports.AlertRuleManager = AlertRuleManager;
//# sourceMappingURL=alert-rule-manager.js.map