UNPKG

woolball-cli

Version:

Monitor CLI para o servidor Woolball

373 lines (322 loc) 11.9 kB
import woolballClient from 'woolball-client'; const Woolball = woolballClient.default; import blessed from 'blessed'; import contrib from 'blessed-contrib'; // Woolball server configuration const WEBSOCKET_URL = process.env.WOOLBALL_WS_URL || 'ws://localhost:9003/ws'; // Terminal interface configuration const screen = blessed.screen({ smartCSR: true, title: 'Woolball Monitor' }); class NodeMonitor { constructor(clientId, wsUrl) { this.woolballClient = new Woolball(clientId, wsUrl, { environment:'node' }); this.taskCount = 0; this.activeTaskCount = 0; this.completedTaskCount = 0; this.errorTaskCount = 0; this.nodeCount = 0; this.tasks = {}; this.taskList = []; this.setupUI(); this.setupEventListeners(); this.setupKeys(); } setupUI() { // Criar layout da interface this.grid = new contrib.grid({ rows: 12, cols: 12, screen: screen }); // Header this.header = this.grid.set(0, 0, 1, 12, blessed.box, { content: ` WOOLBALL MONITOR - Connected to: ${this.woolballClient.wsUrl} - ID: ${this.woolballClient.clientId}`, style: { fg: 'blue', bold: true }, border: { type: 'line' } }); // Statistics panel this.statsBox = this.grid.set(1, 0, 3, 6, blessed.box, { label: ' Statistics ', tags: true, border: { type: 'line' }, style: { border: { fg: 'blue' } } }); // Task chart this.taskChart = this.grid.set(1, 6, 3, 6, contrib.donut, { label: ' Task Status ', radius: 8, arcWidth: 3, remainColor: 'black', yPadding: 2, data: [ { percent: 0, label: 'Active', color: 'yellow' }, { percent: 0, label: 'Completed', color: 'green' }, { percent: 0, label: 'Errors', color: 'red' } ] }); // Task list (reduced height from 7 to 4 rows) this.taskTable = this.grid.set(4, 0, 4, 12, contrib.table, { keys: true, fg: 'white', selectedFg: 'white', selectedBg: 'blue', interactive: true, label: ' Tasks ', width: '100%', height: '100%', border: { type: 'line', fg: 'blue' }, columnSpacing: 3, columnWidth: [6, 36, 12, 16] }); // Footer with help (moved closer to tasks) this.footer = this.grid.set(8, 0, 1, 12, blessed.box, { content: ' q: Exit | h: Help | c: Clear Completed', style: { fg: 'white', bg: 'blue' } }); // Atualizar estatísticas iniciais this.updateStats(); this.updateTaskTable(); // Renderizar a tela screen.render(); } setupKeys() { // Configurar teclas de atalho screen.key(['q', 'C-c'], () => { this.disconnect(); process.exit(0); }); screen.key('h', () => { this.showHelp(); }); screen.key('c', () => { // Clear completed tasks from list this.taskList = this.taskList.filter(task => task.status !== 'completed' && task.status !== 'error'); this.updateTaskTable(); screen.render(); }); // Permitir sair com escape screen.key('escape', () => { screen.render(); }); } showHelp() { // Display help modal const helpBox = blessed.box({ top: 'center', left: 'center', width: '50%', height: '50%', content: '\n Woolball Monitor - Help\n\n' + ' q, Ctrl+C: Exit monitor\n' + ' h: Show this help\n' + ' c: Clear completed tasks from list\n' + ' ESC: Close this window\n\n' + ' Use arrow keys to navigate the task list', border: { type: 'line' }, style: { border: { fg: 'blue' }, fg: 'white', bg: 'black' } }); screen.append(helpBox); helpBox.focus(); helpBox.key(['escape', 'q', 'h'], () => { screen.remove(helpBox); screen.render(); }); screen.render(); } updateStats() { // Update statistics panel this.statsBox.setContent( `\n {cyan-fg}Connected nodes:{/cyan-fg} {yellow-fg}${this.nodeCount}{/yellow-fg}\n` + ` {cyan-fg}Total tasks:{/cyan-fg} {yellow-fg}${this.taskCount}{/yellow-fg}\n` + ` {cyan-fg}Active tasks:{/cyan-fg} {yellow-fg}${this.activeTaskCount}{/yellow-fg}\n` + ` {cyan-fg}Completed tasks:{/cyan-fg} {green-fg}${this.completedTaskCount}{/green-fg}\n` + ` {cyan-fg}Failed tasks:{/cyan-fg} {red-fg}${this.errorTaskCount}{/red-fg}` ); // Update task chart const total = Math.max(this.taskCount, 1); // Avoid division by zero this.taskChart.update([ { percent: Math.round((this.activeTaskCount / total) * 100), label: 'Active', color: 'yellow' }, { percent: Math.round((this.completedTaskCount / total) * 100), label: 'Completed', color: 'green' }, { percent: Math.round((this.errorTaskCount / total) * 100), label: 'Errors', color: 'red' } ]); } updateTaskTable() { // Configure table columns this.taskTable.setData({ headers: ['ID', 'Type', 'Status', 'Time'], data: this.taskList.map(task => [ task.id.substring(0, 6), task.type, task.status, task.duration ]) }); } setupEventListeners() { this.woolballClient.on('started', (data) => { this.taskCount++; this.activeTaskCount++; // Store task information const taskInfo = { type: data.type, startTime: Date.now(), status: 'active', duration: '0s' }; this.tasks[data.id] = taskInfo; // Add to task list for display this.taskList.unshift({ id: data.id, type: data.type, status: 'active', duration: '0s', startTime: Date.now() }); // Update interface this.updateStats(); this.updateTaskTable(); screen.render(); // Start timer to update duration this.startTaskTimer(data.id); }); this.woolballClient.on('success', (data) => { this.activeTaskCount--; this.completedTaskCount++; // Update task information if (this.tasks[data.id]) { const task = this.tasks[data.id]; const duration = ((Date.now() - task.startTime) / 1000).toFixed(2); // Update in display list const taskIndex = this.taskList.findIndex(t => t.id === data.id); if (taskIndex !== -1) { this.taskList[taskIndex].status = 'completed'; this.taskList[taskIndex].duration = `${duration}s`; } delete this.tasks[data.id]; } // Update interface this.updateStats(); this.updateTaskTable(); screen.render(); }); this.woolballClient.on('error', (data) => { this.activeTaskCount--; this.errorTaskCount++; // Update task information if (this.tasks[data.id]) { const task = this.tasks[data.id]; const duration = ((Date.now() - task.startTime) / 1000).toFixed(2); // Update in display list const taskIndex = this.taskList.findIndex(t => t.id === data.id); if (taskIndex !== -1) { this.taskList[taskIndex].status = 'error'; this.taskList[taskIndex].duration = `${duration}s`; } delete this.tasks[data.id]; } // Update interface this.updateStats(); this.updateTaskTable(); screen.render(); }); this.woolballClient.on('node_count', (data) => { const oldCount = this.nodeCount; this.nodeCount = data.nodeCount; if (oldCount !== this.nodeCount) { // Update interface this.updateStats(); screen.render(); } }); } startTaskTimer(taskId) { // Update duration of active tasks every second const updateInterval = setInterval(() => { if (this.tasks[taskId]) { const task = this.tasks[taskId]; const duration = ((Date.now() - task.startTime) / 1000).toFixed(2); // Update in display list const taskIndex = this.taskList.findIndex(t => t.id === taskId); if (taskIndex !== -1) { this.taskList[taskIndex].duration = `${duration}s`; this.updateTaskTable(); screen.render(); } } else { // Task no longer exists, stop timer clearInterval(updateInterval); } }, 1000); } connect() { // Display connection message this.header.setContent(' WOOLBALL MONITOR - Connecting to server...'); screen.render(); try { this.woolballClient.start(); this.header.setContent(` WOOLBALL MONITOR - Connected to: ${this.woolballClient.wsUrl} - ID: ${this.woolballClient.clientId}`); screen.render(); } catch (error) { this.header.setContent(` WOOLBALL MONITOR - Connection error: ${error.message}`); screen.render(); setTimeout(() => process.exit(1), 3000); } } disconnect() { // Display disconnection message this.header.setContent(' WOOLBALL MONITOR - Disconnecting...'); screen.render(); try { this.woolballClient.destroy(); this.header.setContent(' WOOLBALL MONITOR - Disconnected successfully!'); screen.render(); } catch (error) { this.header.setContent(` WOOLBALL MONITOR - Disconnection error: ${error.message}`); screen.render(); } } } // Generate a simple client ID for this monitor instance const generateClientId = () => 'node-monitor-' + Math.random().toString(36).substring(2, 15); // Start the monitor const clientId = generateClientId(); const monitor = new NodeMonitor(clientId, WEBSOCKET_URL); monitor.connect(); // Handle graceful shutdown process.on('SIGINT', () => { console.log('\nReceived SIGINT. Shutting down...'); monitor.disconnect(); process.exit(0); }); process.on('SIGTERM', () => { console.log('\nReceived SIGTERM. Shutting down...'); monitor.disconnect(); process.exit(0); });