UNPKG

okta-mcp-server

Version:

Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching

385 lines (379 loc) 12.8 kB
/** * Circuit Breaker Monitoring Dashboard * * Simple dashboard implementation for monitoring circuit breakers */ import { CircuitState } from './monitoring-types.js'; /** * Console dashboard renderer */ export class ConsoleDashboard { monitor; refreshInterval; constructor(monitor) { this.monitor = monitor; } /** * Start rendering the dashboard */ start(refreshRate = 1000) { // Clear console and render initial state this.render(); // Set up refresh interval this.refreshInterval = setInterval(() => { this.render(); }, refreshRate); // Listen for alerts this.monitor.on('alert', (alert) => { this.renderAlert(alert); }); } /** * Stop rendering the dashboard */ stop() { if (this.refreshInterval) { clearInterval(this.refreshInterval); } } /** * Render the dashboard */ render() { const data = this.monitor.getDashboard(); // Clear console (works in most terminals) console.clear(); // Header console.log('╔════════════════════════════════════════════════════════════════╗'); console.log('║ Circuit Breaker Monitoring Dashboard ║'); console.log('╚════════════════════════════════════════════════════════════════╝'); console.log(''); // System health this.renderSystemHealth(data.systemHealth); // Circuit breakers data.circuits.forEach((circuit, name) => { this.renderCircuit(name, circuit); }); // Footer console.log(''); console.log(`Last updated: ${new Date(data.systemHealth.lastUpdated).toLocaleTimeString()}`); } renderSystemHealth(health) { console.log('System Health Overview'); console.log('─────────────────────'); console.log(`Total Requests: ${health.totalRequests.toLocaleString()}`); console.log(`Success Rate: ${health.avgSuccessRate.toFixed(2)}%`); console.log(`Active Alerts: ${health.activeAlerts} ${this.getAlertIcon(health.activeAlerts)}`); console.log(''); } renderCircuit(name, circuit) { const { current: stats, health, alerts } = circuit; console.log(`┌─ ${name} ${this.getHealthIcon(health)}`); console.log(`├─ State: ${this.getStateDisplay(stats.state)}`); console.log(`├─ Requests: ${stats.totalRequests.toLocaleString()} (Success: ${stats.successes}, Failures: ${stats.failures})`); console.log(`├─ Success Rate: ${(100 - stats.failureRate).toFixed(2)}%`); console.log(`├─ Rolling Window: Success: ${stats.rollingCountSuccess}, Failure: ${stats.rollingCountFailure}`); if (stats.nextAttempt) { const timeLeft = Math.max(0, stats.nextAttempt - Date.now()); console.log(`├─ Next Attempt: ${Math.ceil(timeLeft / 1000)}s`); } if (alerts.length > 0) { console.log(`├─ Alerts: ${alerts.length}`); alerts.slice(0, 3).forEach((alert) => { console.log(`│ └─ ${this.getSeverityIcon(alert.severity)} ${alert.message}`); }); } console.log('└────────────────────'); console.log(''); } renderAlert(alert) { const timestamp = new Date(alert.timestamp).toLocaleTimeString(); console.log(`\n🚨 ALERT [${timestamp}]: ${alert.message}\n`); } getStateDisplay(state) { switch (state) { case CircuitState.CLOSED: return '🟢 CLOSED'; case CircuitState.OPEN: return '🔴 OPEN'; case CircuitState.HALF_OPEN: return '🟡 HALF-OPEN'; default: return state; } } getHealthIcon(health) { switch (health) { case 'healthy': return '✅'; case 'degraded': return '⚠️'; case 'unhealthy': return '❌'; default: return ''; } } getAlertIcon(count) { if (count === 0) return '✅'; if (count <= 2) return '⚠️'; return '🚨'; } getSeverityIcon(severity) { switch (severity) { case 'info': return 'ℹ️'; case 'warning': return '⚠️'; case 'error': return '❌'; case 'critical': return '🚨'; default: return ''; } } } /** * HTML dashboard generator */ export class HtmlDashboard { monitor; constructor(monitor) { this.monitor = monitor; } /** * Generate HTML dashboard */ generateHtml() { const data = this.monitor.getDashboard(); const metrics = this.monitor.exportPrometheus(); return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Circuit Breaker Dashboard</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; } .header { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; } .system-health { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; } .metric-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .metric-value { font-size: 2em; font-weight: bold; color: #333; } .metric-label { color: #666; margin-top: 5px; } .circuit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; } .circuit-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .circuit-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .circuit-name { font-size: 1.2em; font-weight: bold; } .state-badge { padding: 4px 12px; border-radius: 12px; font-size: 0.85em; font-weight: 500; } .state-closed { background: #d4edda; color: #155724; } .state-open { background: #f8d7da; color: #721c24; } .state-half-open { background: #fff3cd; color: #856404; } .health-healthy { color: #28a745; } .health-degraded { color: #ffc107; } .health-unhealthy { color: #dc3545; } .metrics-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px; } .metric-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .alert-list { margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 4px; } .alert-item { padding: 5px 0; font-size: 0.9em; } .alert-critical { color: #dc3545; } .alert-error { color: #fd7e14; } .alert-warning { color: #ffc107; } .alert-info { color: #17a2b8; } .prometheus-metrics { margin-top: 30px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .metrics-content { font-family: monospace; font-size: 0.85em; white-space: pre-wrap; background: #f8f9fa; padding: 15px; border-radius: 4px; max-height: 400px; overflow-y: auto; } </style> </head> <body> <div class="container"> <div class="header"> <h1>Circuit Breaker Monitoring Dashboard</h1> <p>Real-time monitoring of circuit breaker states and metrics</p> </div> <div class="system-health"> <div class="metric-card"> <div class="metric-value">${data.systemHealth.totalRequests.toLocaleString()}</div> <div class="metric-label">Total Requests</div> </div> <div class="metric-card"> <div class="metric-value">${data.systemHealth.avgSuccessRate.toFixed(1)}%</div> <div class="metric-label">Success Rate</div> </div> <div class="metric-card"> <div class="metric-value">${data.systemHealth.activeAlerts}</div> <div class="metric-label">Active Alerts</div> </div> </div> <div class="circuit-grid"> ${Array.from(data.circuits.entries()) .map(([name, circuit]) => this.renderCircuitCard(name, circuit)) .join('')} </div> <div class="prometheus-metrics"> <h3>Prometheus Metrics</h3> <div class="metrics-content">${metrics}</div> </div> <script> // Auto-refresh every 5 seconds setTimeout(() => location.reload(), 5000); </script> </div> </body> </html> `; } renderCircuitCard(name, circuit) { const { current: stats, health, alerts } = circuit; return ` <div class="circuit-card"> <div class="circuit-header"> <div class="circuit-name health-${health}">${name}</div> <div class="state-badge state-${stats.state.toLowerCase()}">${stats.state}</div> </div> <div class="metrics-grid"> <div class="metric-item"> <span>Total Requests</span> <strong>${stats.totalRequests}</strong> </div> <div class="metric-item"> <span>Success Rate</span> <strong>${(100 - stats.failureRate).toFixed(1)}%</strong> </div> <div class="metric-item"> <span>Failures</span> <strong>${stats.failures}</strong> </div> <div class="metric-item"> <span>Rejections</span> <strong>${stats.rejections}</strong> </div> </div> ${alerts.length > 0 ? ` <div class="alert-list"> <strong>Active Alerts:</strong> ${alerts .map((alert) => ` <div class="alert-item alert-${alert.severity}"> ${alert.message} </div> `) .join('')} </div> ` : ''} </div> `; } } /** * Create monitoring dashboard handlers */ export function createDashboardHandlers(monitor) { const consoleDashboard = new ConsoleDashboard(monitor); const htmlDashboard = new HtmlDashboard(monitor); return { console: consoleDashboard, html: htmlDashboard, // Express route handler for HTML dashboard htmlRoute: (req, res) => { res.set('Content-Type', 'text/html'); res.send(htmlDashboard.generateHtml()); }, // JSON API endpoint jsonRoute: (req, res) => { res.json(monitor.getDashboard()); }, }; } //# sourceMappingURL=monitoring-dashboard.js.map