UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

515 lines 18.3 kB
"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