UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

492 lines 19.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AlertHistoryManager = void 0; const events_1 = require("events"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); class AlertHistoryManager extends events_1.EventEmitter { constructor(config) { super(); this.historyEntries = []; this.isDirty = false; this.maxHistoryEntries = config?.maxHistoryEntries || 10000; this.persistenceEnabled = config?.persistenceEnabled !== false; this.persistenceFilePath = config?.persistenceFile || path.join(process.cwd(), '.polyv-alerts', 'alert-history.json'); this.initializePersistence(); this.setupAutoSave(); } async initializePersistence() { if (!this.persistenceEnabled) return; try { const dir = path.dirname(this.persistenceFilePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } await this.loadHistory(); } catch (error) { console.warn('Failed to initialize alert history persistence:', error); this.emit('persistence:error', { action: 'initialize', error }); } } setupAutoSave() { if (!this.persistenceEnabled) return; this.autoSaveInterval = setInterval(async () => { if (this.isDirty) { await this.saveHistory(); } }, 30000); } async loadHistory() { if (!this.persistenceEnabled || !fs.existsSync(this.persistenceFilePath)) { return; } try { const data = fs.readFileSync(this.persistenceFilePath, 'utf8'); const parsed = JSON.parse(data); if (Array.isArray(parsed.entries)) { this.historyEntries = parsed.entries.map((entry) => ({ ...entry, alert: { ...entry.alert }, timestamp: typeof entry.timestamp === 'string' ? new Date(entry.timestamp).getTime() : entry.timestamp })); if (this.historyEntries.length > this.maxHistoryEntries) { this.historyEntries = this.historyEntries .sort((a, b) => b.timestamp - a.timestamp) .slice(0, this.maxHistoryEntries); this.isDirty = true; } } this.emit('history:loaded', { count: this.historyEntries.length, file: this.persistenceFilePath }); } catch (error) { console.warn('Failed to load alert history:', error); this.emit('persistence:error', { action: 'load', error }); } } async saveHistory() { if (!this.persistenceEnabled || !this.isDirty) return; try { const data = { version: '1.0', timestamp: new Date().toISOString(), count: this.historyEntries.length, entries: this.historyEntries }; const dir = path.dirname(this.persistenceFilePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(this.persistenceFilePath, JSON.stringify(data, null, 2), 'utf8'); this.isDirty = false; this.emit('history:saved', { count: this.historyEntries.length, file: this.persistenceFilePath }); } catch (error) { console.warn('Failed to save alert history:', error); this.emit('persistence:error', { action: 'save', error }); } } addAlert(alert, action = 'created', user, context) { const entry = { alert: { ...alert }, timestamp: Date.now(), action, ...(user && { user }), ...(context && { context }) }; this.historyEntries.unshift(entry); this.enforceMaxSize(); this.isDirty = true; this.emit('entry:added', { entry, alert }); } recordAction(alertId, action, user, context) { const alertEntry = this.historyEntries.find(entry => entry.alert.id === alertId); if (!alertEntry) return null; const actionEntry = { alert: { ...alertEntry.alert }, timestamp: Date.now(), action, ...(user && { user }), ...(context && { context }) }; this.historyEntries.unshift(actionEntry); this.enforceMaxSize(); this.isDirty = true; this.emit('action:recorded', { entry: actionEntry, alertId, action }); return actionEntry; } updateAlert(updatedAlert, user) { let updateCount = 0; this.historyEntries.forEach(entry => { if (entry.alert.id === updatedAlert.id) { entry.alert = { ...updatedAlert }; updateCount++; } }); if (updateCount > 0) { this.recordAction(updatedAlert.id, 'updated', user, { updateCount, timestamp: Date.now() }); } } getHistory(filter, limit, offset = 0) { let filtered = this.historyEntries; if (filter) { filtered = this.applyFilter(filtered, filter); } const start = offset; const end = limit ? start + limit : undefined; return filtered.slice(start, end); } getAlertHistory(alertId) { return this.historyEntries.filter(entry => entry.alert.id === alertId); } getHistoryByAction(action) { return this.historyEntries.filter(entry => entry.action === action); } getHistoryByUser(user) { return this.historyEntries.filter(entry => entry.user === user); } getHistoryByTimeRange(startTime, endTime) { return this.historyEntries.filter(entry => entry.timestamp >= startTime && entry.timestamp <= endTime); } applyFilter(entries, filter) { return entries.filter(entry => { const alert = entry.alert; if (filter.levels?.length && !filter.levels.includes(alert.level)) { return false; } if (filter.types?.length && !filter.types.includes(alert.type)) { return false; } if (filter.statuses?.length && !filter.statuses.includes(alert.status)) { return false; } if (filter.sources?.length && !filter.sources.includes(alert.source)) { return false; } if (filter.channelIds?.length && !filter.channelIds.includes(alert.channelId || '')) { return false; } if (filter.timeRange) { if (entry.timestamp < filter.timeRange.start || entry.timestamp > filter.timeRange.end) { return false; } } if (filter.searchText) { const searchText = filter.searchText.toLowerCase(); return alert.title.toLowerCase().includes(searchText) || alert.message.toLowerCase().includes(searchText) || alert.source.toLowerCase().includes(searchText) || entry.action.toLowerCase().includes(searchText); } if (filter.acknowledgedOnly && !alert.acknowledged) { return false; } if (filter.unacknowledgedOnly && alert.acknowledged) { return false; } return true; }); } searchHistory(searchText, searchFields = ['title', 'message'], limit = 100) { if (!searchText || searchText.trim() === '') { return []; } const searchLower = searchText.toLowerCase(); const matches = this.historyEntries.filter(entry => { return searchFields.some(field => { switch (field) { case 'title': return entry.alert.title.toLowerCase().includes(searchLower); case 'message': return entry.alert.message.toLowerCase().includes(searchLower); case 'source': return entry.alert.source.toLowerCase().includes(searchLower); case 'action': return entry.action.toLowerCase().includes(searchLower); case 'user': return entry.user?.toLowerCase().includes(searchLower) || false; default: return false; } }); }); return matches.slice(0, limit); } getStatistics() { const stats = { totalEntries: this.historyEntries.length, uniqueAlerts: new Set(this.historyEntries.map(e => e.alert.id)).size, actionCounts: {}, userCounts: {}, timeRange: null, levelCounts: { info: 0, warning: 0, error: 0, critical: 0 }, typeCounts: { system: 0, stream: 0, channel: 0, network: 0 }, statusCounts: { active: 0, acknowledged: 0, resolved: 0, ignored: 0 } }; if (this.historyEntries.length === 0) { return stats; } const timestamps = this.historyEntries.map(e => e.timestamp); stats.timeRange = { earliest: Math.min(...timestamps), latest: Math.max(...timestamps) }; this.historyEntries.forEach(entry => { stats.actionCounts[entry.action] = (stats.actionCounts[entry.action] || 0) + 1; if (entry.user) { stats.userCounts[entry.user] = (stats.userCounts[entry.user] || 0) + 1; } stats.levelCounts[entry.alert.level]++; stats.typeCounts[entry.alert.type]++; stats.statusCounts[entry.alert.status]++; }); return stats; } getRecentActivity(hours = 24) { const cutoffTime = Date.now() - (hours * 60 * 60 * 1000); const recentEntries = this.historyEntries.filter(entry => entry.timestamp >= cutoffTime); const summary = { totalAlerts: recentEntries.length, newAlerts: 0, acknowledgedAlerts: 0, resolvedAlerts: 0, activeUsers: [], topAlertTypes: [] }; const userSet = new Set(); const typeCounts = new Map(); recentEntries.forEach(entry => { switch (entry.action) { case 'created': summary.newAlerts++; break; case 'acknowledged': summary.acknowledgedAlerts++; break; case 'resolved': summary.resolvedAlerts++; break; } if (entry.user) { userSet.add(entry.user); } const currentCount = typeCounts.get(entry.alert.type) || 0; typeCounts.set(entry.alert.type, currentCount + 1); }); summary.activeUsers = Array.from(userSet); summary.topAlertTypes = Array.from(typeCounts.entries()) .map(([type, count]) => ({ type, count })) .sort((a, b) => b.count - a.count); return summary; } exportHistory(format = 'json') { if (format === 'csv') { return this.exportToCSV(); } else { return JSON.stringify({ version: '1.0', exportedAt: new Date().toISOString(), count: this.historyEntries.length, entries: this.historyEntries }, null, 2); } } exportToCSV() { const headers = [ 'Timestamp', 'Action', 'User', 'Alert ID', 'Alert Level', 'Alert Type', 'Alert Status', 'Alert Title', 'Alert Message', 'Alert Source', 'Channel ID' ]; const rows = this.historyEntries.map(entry => [ new Date(entry.timestamp).toISOString(), entry.action, entry.user || '', entry.alert.id, entry.alert.level, entry.alert.type, entry.alert.status, `"${entry.alert.title.replace(/"/g, '""').replace(/\n/g, '\\n')}"`, `"${entry.alert.message.replace(/"/g, '""').replace(/\n/g, '\\n')}"`, entry.alert.source, entry.alert.channelId || '' ]); return [headers.join(','), ...rows.map(row => row.join(','))].join('\n'); } importHistory(data, format = 'json', merge = true) { const result = { imported: 0, errors: [] }; try { if (format === 'json') { const parsed = JSON.parse(data); const entries = Array.isArray(parsed.entries) ? parsed.entries : Array.isArray(parsed) ? parsed : []; if (!merge) { this.historyEntries = []; } entries.forEach((entry, index) => { try { if (!entry.alert || !entry.action || !entry.timestamp) { throw new Error('Missing required fields (alert, action, timestamp)'); } if (!entry.alert.id || !entry.alert.level || !entry.alert.type) { throw new Error('Invalid alert data'); } const historyEntry = { alert: entry.alert, timestamp: typeof entry.timestamp === 'string' ? new Date(entry.timestamp).getTime() : entry.timestamp, action: entry.action, user: entry.user, context: entry.context }; this.historyEntries.push(historyEntry); result.imported++; } catch (error) { result.errors.push(`Entry ${index}: ${error instanceof Error ? error.message : 'Invalid format'}`); } }); this.historyEntries.sort((a, b) => b.timestamp - a.timestamp); this.enforceMaxSize(); this.isDirty = true; } else { result.errors.push('CSV import not yet implemented'); } } catch (error) { result.errors.push(`Parse error: ${error instanceof Error ? error.message : 'Unknown error'}`); } this.emit('history:imported', result); return result; } clearHistory() { const count = this.historyEntries.length; this.historyEntries = []; this.isDirty = true; this.emit('history:cleared', { count }); } clearOldHistory(olderThanMs) { const cutoffTime = Date.now() - olderThanMs; const initialCount = this.historyEntries.length; this.historyEntries = this.historyEntries.filter(entry => entry.timestamp >= cutoffTime); const clearedCount = initialCount - this.historyEntries.length; if (clearedCount > 0) { this.isDirty = true; this.emit('history:pruned', { clearedCount, remaining: this.historyEntries.length }); } return clearedCount; } enforceMaxSize() { if (this.historyEntries.length > this.maxHistoryEntries) { const removedCount = this.historyEntries.length - this.maxHistoryEntries; this.historyEntries = this.historyEntries.slice(0, this.maxHistoryEntries); this.emit('history:trimmed', { removedCount, remaining: this.historyEntries.length }); } } getConfig() { return { maxHistoryEntries: this.maxHistoryEntries, persistenceEnabled: this.persistenceEnabled, persistenceFilePath: this.persistenceFilePath, currentSize: this.historyEntries.length }; } updateConfig(updates) { if (updates.maxHistoryEntries !== undefined) { this.maxHistoryEntries = updates.maxHistoryEntries; this.enforceMaxSize(); } if (updates.persistenceEnabled !== undefined) { this.persistenceEnabled = updates.persistenceEnabled; if (this.persistenceEnabled) { this.setupAutoSave(); } else if (this.autoSaveInterval) { clearInterval(this.autoSaveInterval); this.autoSaveInterval = undefined; } } if (updates.persistenceFilePath !== undefined) { this.persistenceFilePath = updates.persistenceFilePath; } this.emit('config:updated', updates); } async forceSave() { this.isDirty = true; await this.saveHistory(); } async destroy() { if (this.isDirty) { await this.saveHistory(); } if (this.autoSaveInterval) { clearInterval(this.autoSaveInterval); this.autoSaveInterval = undefined; } this.historyEntries = []; this.removeAllListeners(); this.emit('manager:destroyed'); } } exports.AlertHistoryManager = AlertHistoryManager; //# sourceMappingURL=alert-history-manager.js.map