UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

908 lines (903 loc) 33.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MonitoringDashboard = void 0; const blessed_1 = __importDefault(require("blessed")); const events_1 = require("events"); const grid_manager_1 = require("./grid-manager"); const component_registry_1 = require("./component-registry"); const status_component_1 = require("./status.component"); const channel_status_panel_1 = require("./channel-status.panel"); const system_resource_panel_1 = require("./system-resource.panel"); const interaction_manager_1 = require("./interaction-manager"); const help_panel_1 = require("./help-panel"); const tooltip_1 = require("./tooltip"); const context_menu_1 = require("./context-menu"); const context_menu_factory_1 = require("./context-menu-factory"); const search_panel_1 = require("./search-panel"); const layout_manager_1 = require("./layout-manager"); class MonitoringDashboard { constructor(config) { this.isRunning = false; this.cleanupHandlers = []; this.startTime = Date.now(); this.config = config; this.eventBus = new events_1.EventEmitter(); this.eventBus.setMaxListeners(100); this.initializeScreen(); this.setupSignalHandlers(); this.componentRegistry = new component_registry_1.ComponentRegistry(this.eventBus); this.registerComponentFactories(); this.gridManager = new grid_manager_1.GridManager(this.screen, this.eventBus, 12, 12); this.setupEventListeners(); this.setupInteractionManager(); } registerComponentFactories() { this.componentRegistry.registerFactory(status_component_1.StatusComponentFactory); const ChannelStatusPanelFactory = { type: 'channel-status', create: (config, eventBus) => { const channelConfig = { ...config, refreshInterval: config.config?.refreshInterval || 5000, maxChannels: config.config?.maxChannels || 100, showColors: config.config?.showColors !== false, columnWidths: config.config?.columnWidths || [20, 12, 10, 10, 15, 15], sortField: config.config?.sortField || 'name', sortOrder: config.config?.sortOrder || 'asc', filters: config.config?.filters || {} }; return new channel_status_panel_1.ChannelStatusPanel(channelConfig, eventBus); }, }; this.componentRegistry.registerFactory(ChannelStatusPanelFactory); const createPlaceholderFactory = (type) => ({ type, create: (config, eventBus) => { return new status_component_1.StatusComponent({ ...config, type, }, eventBus); }, }); const SystemResourcePanelFactory = { type: 'system-resources', create: (config, eventBus) => { return new system_resource_panel_1.SystemResourcePanel(config, eventBus); }, }; this.componentRegistry.registerFactory(SystemResourcePanelFactory); this.componentRegistry.registerFactory(createPlaceholderFactory('stream-metrics')); this.componentRegistry.registerFactory(createPlaceholderFactory('activity-log')); } initializeScreen() { const width = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 120; const height = process.stdout.rows && process.stdout.rows > 0 ? process.stdout.rows : 30; const envWidth = process.env['POLYV_TERMINAL_WIDTH']; const envHeight = process.env['POLYV_TERMINAL_HEIGHT']; const finalWidth = envWidth && parseInt(envWidth) > 0 ? parseInt(envWidth) : width; const finalHeight = envHeight && parseInt(envHeight) > 0 ? parseInt(envHeight) : height; this.screen = blessed_1.default.screen({ smartCSR: true, title: 'PolyV Live Monitoring Dashboard', width: finalWidth, height: finalHeight, cursor: { artificial: true, shape: 'line', blink: true, color: 'white', }, debug: false, dockBorders: true, fullUnicode: this.config.terminal?.unicodeSupport ?? true, sendFocus: true, warnings: false, }); this.applyTheme(this.config.theme); } setupSignalHandlers() { const handleShutdown = () => { this.stop().then(() => { process.exit(0); }).catch((error) => { console.error('Error during shutdown:', error); process.exit(1); }); }; process.on('SIGINT', handleShutdown); process.on('SIGTERM', handleShutdown); process.on('SIGQUIT', handleShutdown); this.cleanupHandlers.push(() => { process.off('SIGINT', handleShutdown); process.off('SIGTERM', handleShutdown); process.off('SIGQUIT', handleShutdown); }); } setupEventListeners() { this.eventBus.on('component:error', (data) => { this.handleComponentError(data); }); this.eventBus.on('component:requestUpdate', (data) => { this.handleComponentUpdateRequest(data); }); this.eventBus.on('layout:changed', (data) => { this.handleLayoutChange(data); }); this.eventBus.on('grid:componentAdded', (_data) => { this.screen.render(); }); this.eventBus.on('grid:componentRemoved', (_data) => { this.screen.render(); }); this.screen.on('keypress', (ch, key) => { this.handleKeyPress(ch, key); }); this.screen.on('resize', () => { this.handleScreenResize(); }); } setupInteractionManager() { const interactionConfig = { mouseEnabled: this.config.terminal?.mouseSupport ?? true, keyboardEnabled: true, focusRing: { enabled: true, style: { border: { fg: 'cyan' } } }, shortcuts: [ { key: 'f5', description: 'Refresh all data', action: 'refresh', global: true, }, { key: 'f1', description: 'Show help', action: 'help', global: true, }, { key: 'ctrl+r', description: 'Refresh screen', action: 'refresh-screen', global: true, }, { key: 'ctrl+l', description: 'Clear and redraw screen', action: 'clear-screen', global: true, }, { key: 'ctrl+f', description: 'Open search panel', action: 'search', global: true, }, { key: '/', description: 'Open search panel', action: 'search', global: true, }, { key: 'f11', description: 'Toggle fullscreen panel', action: 'fullscreen', global: true, }, { key: '1', description: 'Switch to compact layout', action: 'layout:compact', global: true, }, { key: '2', description: 'Switch to standard layout', action: 'layout:standard', global: true, }, { key: '3', description: 'Switch to detailed layout', action: 'layout:detailed', global: true, } ], search: { placeholder: 'Search channels...', caseSensitive: false, mode: 'fuzzy', searchFields: ['name', 'channelId', 'status'], maxHistory: 10, showSuggestions: true, }, statusBar: { enabled: true, position: 'bottom', height: 1, }, }; this.interactionManager = new interaction_manager_1.InteractionManager(this.screen, interactionConfig); this.helpPanel = new help_panel_1.HelpPanel(this.screen, this.eventBus, { title: 'PolyV Live Monitoring Dashboard - Help', showShortcuts: true, showNavigation: true, showUsageTips: true, }); this.helpPanel.setShortcuts(this.interactionManager.getAllShortcuts()); this.tooltip = new tooltip_1.Tooltip(this.screen, this.eventBus); this.contextMenu = new context_menu_1.ContextMenu(this.screen, this.eventBus); this.searchPanel = new search_panel_1.SearchPanel(this.screen, this.eventBus, { placeholder: interactionConfig.search.placeholder, caseSensitive: interactionConfig.search.caseSensitive, mode: interactionConfig.search.mode, searchFields: interactionConfig.search.searchFields, maxHistory: interactionConfig.search.maxHistory, showSuggestions: interactionConfig.search.showSuggestions, }); this.layoutManager = new layout_manager_1.LayoutManager(this.screen, this.eventBus); this.setupInteractionEventHandlers(); } setupInteractionEventHandlers() { this.interactionManager.on('shortcut:activated', (data) => { this.handleShortcutAction(data.action); }); this.interactionManager.on('focus:changed', (data) => { this.eventBus.emit('focus:changed', data); }); this.interactionManager.on('action:refresh', () => { this.refresh(); }); this.interactionManager.on('action:help', () => { this.showHelp(); }); this.interactionManager.on('action:exit', () => { this.stop(); }); this.interactionManager.on('action:fullscreen', () => { this.toggleFullscreen(); }); this.interactionManager.on('action:panel', (data) => { this.switchToPanel(parseInt(data.panel) - 1); }); this.interactionManager.on('contextmenu:requested', (data) => { this.showContextMenu(data.x, data.y, data.context, data.componentId); }); this.interactionManager.on('component:hover', (data) => { this.showTooltip(data); }); this.interactionManager.on('component:hover:clear', () => { this.tooltip.hide(); }); this.interactionManager.on('scroll:event', (_data) => { this.tooltip.hide(); this.contextMenu.hide(); }); this.eventBus.on('contextmenu:action', (data) => { this.handleContextMenuAction(data.action, data.componentId, data.context); }); this.eventBus.on('search:result:selected', (data) => { this.handleSearchResultSelected(data.result, data.query); }); this.eventBus.on('search:shown', () => { this.tooltip.hide(); this.contextMenu.hide(); }); this.eventBus.on('search:hidden', () => { }); this.eventBus.on('layout:switched', (data) => { console.log(`Layout switched from ${data.fromLayout} to ${data.toLayout}`); }); this.eventBus.on('layout:size:insufficient', (data) => { console.log(`Terminal size insufficient for ${data.layout}: required ${data.required.width}x${data.required.height}, current ${data.current.width}x${data.current.height}`); }); this.eventBus.on('panel:fullscreen:entered', (data) => { console.log(`Panel ${data.panelId} entered fullscreen mode`); }); this.eventBus.on('panel:fullscreen:exited', (data) => { console.log(`Panel ${data.panelId} exited fullscreen mode`); }); } handleShortcutAction(action) { switch (action) { case 'refresh': this.refresh(); break; case 'refresh-screen': this.screen.realloc(); this.screen.render(); break; case 'clear-screen': this.screen.clear(); this.screen.render(); break; case 'help': this.showHelp(); break; case 'search': this.showSearch(); break; case 'fullscreen': this.toggleFullscreen(); break; case 'layout:compact': this.switchLayout('compact'); break; case 'layout:standard': this.switchLayout('standard'); break; case 'layout:detailed': this.switchLayout('detailed'); break; default: break; } } toggleFullscreen() { const focused = this.interactionManager.getCurrentFocus(); if (focused) { this.layoutManager.toggleFullscreen(focused); } } switchLayout(layoutName) { try { this.layoutManager.switchLayout(layoutName); } catch (error) { console.error(`Failed to switch to layout ${layoutName}:`, error); } } switchToPanel(index) { const layouts = this.gridManager.getAvailableLayouts(); if (index >= 0 && index < layouts.length) { const layout = layouts[index]; if (layout) { this.setLayout(layout); } } } showContextMenu(x, y, context, componentId) { this.tooltip.hide(); const menuItems = context_menu_factory_1.ContextMenuFactory.generateMenuItems(context || 'global', componentId || '', this.getComponentContext(componentId, context)); const filteredItems = context_menu_factory_1.ContextMenuFactory.filterMenuItems(menuItems); const enhancedItems = context_menu_factory_1.ContextMenuFactory.addShortcutsToMenuItems(filteredItems, new Map()); if (enhancedItems.length === 0) { return; } this.contextMenu.show({ items: enhancedItems, x, y, context, componentId, autoHide: true, style: { fg: 'white', bg: 'black', selectedFg: 'black', selectedBg: 'cyan', border: { fg: 'gray', }, }, }); } getComponentContext(componentId, context) { if (!componentId || !context) { return {}; } return { componentId, context, status: context === 'channel-status' ? 'live' : 'unknown', channelData: context === 'channel-status' ? { id: componentId } : undefined, }; } handleContextMenuAction(action, componentId, context) { switch (action) { case 'channel:view-details': this.handleChannelViewDetails(componentId); break; case 'channel:refresh': this.handleChannelRefresh(componentId); break; case 'channel:start-stream': this.handleChannelStartStream(componentId); break; case 'channel:stop-stream': this.handleChannelStopStream(componentId); break; case 'channel:stream-info': this.handleChannelStreamInfo(componentId); break; case 'channel:copy-id': this.handleChannelCopyId(componentId); break; case 'channel:export': this.handleChannelExport(componentId); break; case 'channel:delete': this.handleChannelDelete(componentId); break; case 'system:refresh': this.handleSystemRefresh(componentId); break; case 'system:view-history': this.handleSystemViewHistory(componentId); break; case 'system:configure-alerts': this.handleSystemConfigureAlerts(componentId); break; case 'system:export': this.handleSystemExport(componentId); break; case 'system:reset': this.handleSystemReset(componentId); break; case 'system:settings': this.handleSystemSettings(componentId); break; case 'dashboard:refresh-all': this.refresh(); break; case 'dashboard:clear': this.screen.clear(); this.screen.render(); break; case 'dashboard:settings': this.handleDashboardSettings(); break; case 'dashboard:help': this.showHelp(); break; case 'dashboard:exit': this.stop(); break; case 'component:focus': this.handleComponentFocus(componentId); break; case 'component:refresh': this.handleComponentRefresh(componentId); break; case 'component:fullscreen': this.toggleFullscreen(); break; case 'component:help': this.showHelp(); break; default: console.log(`Unhandled context menu action: ${action}`); break; } this.eventBus.emit('contextmenu:action:executed', { action, componentId, context, timestamp: new Date(), }); } showTooltip(data) { const content = this.generateTooltipContent(data.componentType, data.componentId); if (content) { this.tooltip.show({ content, x: data.x, y: data.y, maxWidth: 50, autoHideDelay: 3000, style: { fg: 'white', bg: 'black', border: { fg: 'yellow', }, }, }); } } generateTooltipContent(componentType, componentId) { switch (componentType) { case 'channel-status': return `Channel Status Panel\nID: ${componentId}\n\nFeatures:\n• Real-time channel monitoring\n• Status indicators\n• Quick actions\n\nTip: Right-click for menu`; case 'system-resource': return `System Resource Monitor\nID: ${componentId}\n\nShows:\n• CPU usage\n• Memory usage\n• Network activity\n\nTip: Scroll to view history`; case 'stream-metrics': return `Stream Metrics Panel\nID: ${componentId}\n\nMetrics:\n• Bitrate\n• Frame rate\n• Quality indicators\n\nTip: Click to focus panel`; case 'help-panel': return `Help Panel\n\nKeyboard shortcuts:\n• ESC/Q - Close\n• Arrow keys - Scroll\n• Enter - Close`; default: return `${componentType}\nID: ${componentId}\n\nTip: Use Tab to navigate\nPress F1 for help`; } } handleComponentError(data) { console.error(`Component error (${data.componentId}):`, data.error); this.eventBus.emit('dashboard:error', { type: 'component', message: data.error, componentId: data.componentId, timestamp: new Date(), }); } handleComponentUpdateRequest(data) { this.eventBus.emit('data:requestUpdate', { componentId: data.componentId, type: data.type, timestamp: data.timestamp, }); } handleLayoutChange(_data) { this.reinitializeComponents(); } handleKeyPress(_ch, _key) { } handleScreenResize() { const width = this.screen.width || 80; const height = this.screen.height || 24; const currentLayout = this.gridManager.getCurrentLayout(); const layoutConfig = this.gridManager.getLayoutConfig(currentLayout); if (layoutConfig && (width < layoutConfig.minTerminalSize.width || height < layoutConfig.minTerminalSize.height)) { this.switchToCompatibleLayout(width, height); } this.screen.render(); } switchToCompatibleLayout(width, height) { const layouts = this.gridManager.getAvailableLayouts(); for (const layoutName of layouts) { const layoutConfig = this.gridManager.getLayoutConfig(layoutName); if (layoutConfig && width >= layoutConfig.minTerminalSize.width && height >= layoutConfig.minTerminalSize.height) { this.setLayout(layoutName); break; } } } applyTheme(themeName) { const themes = { 'default': { colors: { primary: 'blue', secondary: 'cyan', background: 'black', foreground: 'white', accent: 'yellow', error: 'red', warning: 'yellow', success: 'green', info: 'blue', muted: 'gray', highlight: 'yellow', border: 'white', selection: 'blue', }, }, dark: { colors: { primary: 'white', secondary: 'gray', background: 'black', foreground: 'white', accent: 'cyan', error: 'red', warning: 'yellow', success: 'green', info: 'blue', muted: 'gray', highlight: 'cyan', border: 'gray', selection: 'white', }, }, }; const theme = themes[themeName] || themes['default']; this.eventBus.emit('theme:change', { theme: themeName, config: theme, timestamp: new Date(), }); } reinitializeComponents() { this.componentRegistry.removeAllComponents(); const layout = this.gridManager.getCurrentLayout(); const layoutConfig = this.gridManager.getLayoutConfig(layout); if (layoutConfig) { for (const componentLayout of layoutConfig.components) { try { const config = { type: componentLayout.type, position: componentLayout.position, size: componentLayout.size, config: componentLayout.config, visible: true, priority: 1, }; const component = this.componentRegistry.createComponent(config); this.gridManager.addComponent(component, componentLayout.position); if (component && typeof component.canFocus === 'function') { this.interactionManager.registerComponent(component); } if (component) { this.layoutManager.registerPanel(componentLayout.type, component); } } catch (error) { console.error(`Failed to create component ${componentLayout.type}:`, error); } } } } handleChannelViewDetails(componentId) { console.log(`Viewing details for channel: ${componentId}`); } handleChannelRefresh(componentId) { console.log(`Refreshing channel: ${componentId}`); } handleChannelStartStream(componentId) { console.log(`Starting stream for channel: ${componentId}`); } handleChannelStopStream(componentId) { console.log(`Stopping stream for channel: ${componentId}`); } handleChannelStreamInfo(componentId) { console.log(`Viewing stream info for channel: ${componentId}`); } handleChannelCopyId(componentId) { console.log(`Copying channel ID: ${componentId}`); } handleChannelExport(componentId) { console.log(`Exporting channel data: ${componentId}`); } handleChannelDelete(componentId) { console.log(`Deleting channel: ${componentId}`); } handleSystemRefresh(componentId) { console.log(`Refreshing system metrics: ${componentId}`); } handleSystemViewHistory(componentId) { console.log(`Viewing system history: ${componentId}`); } handleSystemConfigureAlerts(componentId) { console.log(`Configuring alerts: ${componentId}`); } handleSystemExport(componentId) { console.log(`Exporting system metrics: ${componentId}`); } handleSystemReset(componentId) { console.log(`Resetting system counters: ${componentId}`); } handleSystemSettings(componentId) { console.log(`Opening system settings: ${componentId}`); } handleDashboardSettings() { console.log('Opening dashboard settings'); } handleComponentFocus(componentId) { if (componentId) { this.interactionManager.setFocus(componentId); } } handleComponentRefresh(componentId) { console.log(`Refreshing component: ${componentId}`); } showHelp() { this.helpPanel.setShortcuts(this.interactionManager.getAllShortcuts()); this.helpPanel.show(); } showSearch() { const searchData = this.gatherSearchableData(); this.searchPanel.setDataSource(searchData); this.searchPanel.show(); } gatherSearchableData() { const searchData = []; const components = this.componentRegistry.getAllComponents(); components.forEach((component, componentId) => { try { if (typeof component.getSearchableData === 'function') { const componentData = component.getSearchableData(); if (Array.isArray(componentData)) { componentData.forEach(item => { searchData.push({ ...item, componentId, componentType: component.constructor.name, }); }); } } } catch (error) { } }); if (searchData.length === 0) { searchData.push({ id: 'ch001', name: 'Live Stream Channel 1', status: 'live', type: 'channel', componentType: 'ChannelStatusPanel', }, { id: 'ch002', name: 'Test Channel', status: 'offline', type: 'channel', componentType: 'ChannelStatusPanel', }, { id: 'sys001', name: 'System Resources', status: 'active', type: 'system', componentType: 'SystemResourcePanel', }); } return searchData; } handleSearchResultSelected(result, query) { console.log(`Search result selected: ${result.item.name} (query: ${query})`); if (result.item.componentId) { this.interactionManager.setFocus(result.item.componentId); } this.eventBus.emit('search:action:executed', { result, query, action: 'focus-component', timestamp: new Date(), }); this.searchPanel.hide(); } async start() { if (this.isRunning) { throw new Error('Dashboard is already running'); } try { this.isRunning = true; this.createWelcomeScreen(); this.eventBus.emit('dashboard:started', { timestamp: new Date(), config: this.config, }); this.screen.render(); this.startRefreshCycle(); } catch (error) { this.isRunning = false; throw error; } } createWelcomeScreen() { const welcomeBox = blessed_1.default.box({ top: 'center', left: 'center', width: '80%', height: '60%', content: this.getWelcomeMessage(), tags: true, border: { type: 'line', }, style: { fg: 'white', bg: 'black', border: { fg: 'cyan', }, }, label: ' PolyV Live Monitoring Dashboard ', }); this.screen.append(welcomeBox); } getWelcomeMessage() { return ` {center}Welcome to PolyV Live Monitoring Dashboard{/center} {cyan-fg}Dashboard Status:{/cyan-fg} • Status: Running • Layout: ${this.config.layout} • Theme: ${this.config.theme} • Refresh Interval: ${this.config.refreshInterval}ms {cyan-fg}Available Commands:{/cyan-fg} • Press 'q', 'Escape', or 'Ctrl+C' to exit • Press 'Ctrl+R' to refresh • Press 'Ctrl+L' to clear screen • Press '?' or 'h' for help {cyan-fg}Features (Coming Soon):{/cyan-fg} • Stream metrics monitoring • Channel status tracking • System resource monitoring • Activity logging {yellow-fg}Note: This is a basic interface. Full grid-based dashboard is under development.{/yellow-fg} Press any key to continue monitoring... `; } async stop() { if (!this.isRunning) { return; } try { this.isRunning = false; this.stopRefreshCycle(); this.componentRegistry.removeAllComponents(); this.gridManager.destroy(); if (this.interactionManager) { this.interactionManager.destroy(); } if (this.searchPanel) { this.searchPanel.destroy(); } if (this.layoutManager) { this.layoutManager.destroy(); } this.cleanupHandlers.forEach(handler => handler()); this.cleanupHandlers = []; this.screen.destroy(); this.eventBus.emit('dashboard:stopped', { timestamp: new Date(), }); } catch (error) { console.error('Error stopping dashboard:', error); } } refresh() { this.eventBus.emit('dashboard:refresh', { timestamp: new Date(), }); this.screen.render(); } setLayout(layoutName) { try { this.gridManager.setLayout(layoutName); this.reinitializeComponents(); this.screen.render(); } catch (error) { console.error(`Failed to set layout ${layoutName}:`, error); } } getStatus() { return { isRunning: this.isRunning, layout: this.gridManager.getCurrentLayout(), components: this.componentRegistry.getComponentCount(), uptime: this.isRunning ? Date.now() - this.startTime : 0, }; } startRefreshCycle() { this.refreshInterval = setInterval(() => { if (this.isRunning) { this.eventBus.emit('dashboard:tick', { timestamp: new Date(), }); } }, this.config.refreshInterval); } stopRefreshCycle() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = undefined; } } getComponentRegistry() { return this.componentRegistry; } getGridManager() { return this.gridManager; } getEventBus() { return this.eventBus; } getScreen() { return this.screen; } } exports.MonitoringDashboard = MonitoringDashboard; //# sourceMappingURL=monitoring-dashboard.js.map