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
JavaScript
/**
* 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 `
<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