← back to Designer Wallcoverings
DW-Agents/dw-agents/agent-monitor-dashboard/public/index.html
1331 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DW-Agents Crisis Monitor | Real-time Emergency Detection</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #0a0e27;
color: #e4e4e7;
line-height: 1.6;
overflow-x: hidden;
}
/* CRISIS ALERT SYSTEM */
.crisis-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, #ff0000 0%, #ff00ff 50%, #ff0000 100%);
color: white;
padding: 15px;
text-align: center;
font-size: 18px;
font-weight: bold;
z-index: 10000;
display: none;
animation: criticalFlash 1s infinite;
box-shadow: 0 4px 20px rgba(255, 0, 0, 0.5);
}
@keyframes criticalFlash {
0%, 100% { background: linear-gradient(135deg, #ff0000 0%, #ff00ff 50%, #ff0000 100%); }
50% { background: linear-gradient(135deg, #ff00ff 0%, #ff0000 50%, #ff00ff 100%); }
}
.crisis-banner.active {
display: block;
}
.crisis-banner button {
background: rgba(0, 0, 0, 0.3);
border: 2px solid white;
color: white;
padding: 8px 16px;
margin: 0 8px;
border-radius: 6px;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
}
.crisis-banner button:hover {
background: rgba(255, 255, 255, 0.2);
}
/* HEARTBEAT INDICATORS */
.heartbeat {
width: 12px;
height: 12px;
background: #00ff00;
border-radius: 50%;
display: inline-block;
animation: heartbeat 1.5s infinite;
margin-right: 8px;
}
.heartbeat.critical {
background: #ff0000;
animation: criticalPulse 0.5s infinite;
}
.heartbeat.warning {
background: #ffaa00;
animation: warningPulse 1s infinite;
}
@keyframes heartbeat {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.3); opacity: 0.7; }
}
@keyframes criticalPulse {
0%, 100% { transform: scale(1); background: #ff0000; }
50% { transform: scale(1.5); background: #ff4444; }
}
@keyframes warningPulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.6; }
}
/* RESTART FREQUENCY GRAPH */
.restart-graph {
height: 30px;
background: #1a1f3a;
border-radius: 4px;
margin: 8px 0;
position: relative;
overflow: hidden;
}
.restart-bar {
height: 100%;
background: linear-gradient(90deg, #00ff00 0%, #ffaa00 30%, #ff0000 60%, #ff00ff 100%);
border-radius: 4px;
transition: width 0.5s;
position: relative;
}
.restart-label {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
font-size: 11px;
font-weight: bold;
color: white;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
/* Header */
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px 30px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
position: sticky;
top: 0;
z-index: 1000;
transition: all 0.3s;
}
.header.crisis-mode {
background: linear-gradient(135deg, #ff0000 0%, #ff00ff 100%);
animation: headerFlash 2s infinite;
}
@keyframes headerFlash {
0%, 100% { background: linear-gradient(135deg, #ff0000 0%, #ff00ff 100%); }
50% { background: linear-gradient(135deg, #ff4444 0%, #ff44ff 100%); }
}
.header-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.title {
font-size: 28px;
font-weight: bold;
display: flex;
align-items: center;
gap: 15px;
}
.health-score {
display: flex;
align-items: center;
gap: 20px;
background: rgba(0, 0, 0, 0.2);
padding: 10px 20px;
border-radius: 30px;
}
.score-circle {
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
position: relative;
background: conic-gradient(
from 0deg,
#10b981 0deg,
#10b981 var(--score-deg),
rgba(255, 255, 255, 0.1) var(--score-deg)
);
}
.score-inner {
width: 60px;
height: 60px;
background: #0a0e27;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
/* Controls */
.controls {
background: #1a1f3a;
padding: 20px 30px;
border-bottom: 1px solid #2a2f4a;
}
.controls-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.btn-success {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.btn-emergency {
background: linear-gradient(135deg, #ff0000 0%, #ff00ff 100%);
color: white;
font-weight: bold;
animation: emergencyPulse 1.5s infinite;
border: 2px solid #fff;
}
.btn-warning {
background: linear-gradient(135deg, #ffaa00 0%, #ff8800 100%);
color: white;
}
@keyframes emergencyPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); box-shadow: 0 0 20px rgba(255, 0, 255, 0.7); }
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.btn-emergency:hover {
animation: none;
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 25px rgba(255, 0, 255, 0.5);
}
/* Stats Summary */
.stats-summary {
padding: 30px;
background: #1a1f3a;
margin: 20px 30px;
border-radius: 12px;
max-width: 1400px;
margin-left: auto;
margin-right: auto;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
}
.stat-card {
background: #0a0e27;
padding: 15px;
border-radius: 8px;
text-align: center;
border: 1px solid #2a2f4a;
}
.stat-value {
font-size: 32px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
color: #9ca3af;
text-transform: uppercase;
}
/* Main Content */
.main-content {
max-width: 1400px;
margin: 0 auto;
padding: 20px 30px;
}
/* Agent Categories */
.category {
margin-bottom: 30px;
background: #1a1f3a;
border-radius: 12px;
overflow: hidden;
}
.category-header {
background: linear-gradient(135deg, #2a2f4a 0%, #1a1f3a 100%);
padding: 15px 20px;
font-size: 18px;
font-weight: 600;
border-bottom: 1px solid #2a2f4a;
display: flex;
align-items: center;
gap: 10px;
}
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
padding: 20px;
}
/* Agent Card */
.agent-card {
background: #0a0e27;
border: 1px solid #2a2f4a;
border-radius: 8px;
padding: 15px;
transition: all 0.3s;
position: relative;
}
.agent-card.critical {
border: 2px solid #ff0000;
background: linear-gradient(135deg, #0a0e27 0%, #1a0505 100%);
box-shadow: 0 0 15px rgba(255, 0, 0, 0.3);
animation: criticalGlow 2s infinite;
}
.agent-card.warning {
border: 2px solid #ffaa00;
background: linear-gradient(135deg, #0a0e27 0%, #1a1505 100%);
box-shadow: 0 0 10px rgba(255, 170, 0, 0.2);
}
.agent-card.healthy {
border: 1px solid #00ff00;
background: linear-gradient(135deg, #0a0e27 0%, #051a05 100%);
}
@keyframes criticalGlow {
0%, 100% { box-shadow: 0 0 15px rgba(255, 0, 0, 0.3); }
50% { box-shadow: 0 0 25px rgba(255, 0, 0, 0.6); }
}
.agent-card:hover {
transform: translateY(-2px);
}
.agent-card.critical:hover {
box-shadow: 0 4px 30px rgba(255, 0, 0, 0.5);
}
/* Memory Leak Indicator */
.memory-leak-indicator {
position: absolute;
top: 10px;
right: 10px;
background: #ff00ff;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: bold;
animation: memoryLeakPulse 1s infinite;
}
@keyframes memoryLeakPulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.1); }
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.agent-name {
font-weight: 600;
font-size: 16px;
}
.agent-status {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.status-online {
background: #10b981;
color: white;
}
.status-stopped {
background: #6b7280;
color: white;
}
.status-errored {
background: #ef4444;
color: white;
animation: pulse 2s infinite;
}
.status-launching {
background: #f59e0b;
color: white;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
/* Metrics */
.agent-metrics {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-bottom: 15px;
}
.metric {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.metric-icon {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.metric-value {
font-weight: 500;
}
.metric-warning {
color: #f59e0b;
}
.metric-critical {
color: #ef4444;
}
.metric-severe {
color: #dc2626;
font-weight: bold;
}
/* Progress Bars */
.progress-bar {
height: 4px;
background: #2a2f4a;
border-radius: 2px;
overflow: hidden;
margin-top: 5px;
}
.progress-fill {
height: 100%;
transition: width 0.3s;
}
.progress-cpu {
background: linear-gradient(90deg, #10b981 0%, #f59e0b 50%, #ef4444 100%);
}
.progress-memory {
background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 50%, #ef4444 100%);
}
/* Actions */
.agent-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
padding-top: 10px;
border-top: 1px solid #2a2f4a;
}
.action-btn {
padding: 6px 12px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
background: #2a2f4a;
color: #e4e4e7;
}
.action-btn:hover {
background: #3a3f5a;
}
/* Error Logs */
.error-logs {
margin-top: 10px;
padding: 10px;
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 6px;
font-size: 12px;
font-family: monospace;
max-height: 100px;
overflow-y: auto;
}
/* Auto-refresh indicator */
.refresh-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(0, 0, 0, 0.2);
border-radius: 20px;
font-size: 14px;
}
.refresh-countdown {
font-weight: bold;
color: #10b981;
}
/* Loading overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(10, 14, 39, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.loading-overlay.active {
opacity: 1;
pointer-events: all;
}
.loader {
width: 50px;
height: 50px;
border: 4px solid #2a2f4a;
border-top-color: #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Alerts */
.alert {
position: fixed;
top: 100px;
right: 30px;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
display: none;
animation: slideIn 0.3s;
z-index: 2000;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.alert-success {
background: #10b981;
color: white;
}
.alert-error {
background: #ef4444;
color: white;
}
.alert-warning {
background: #f59e0b;
color: white;
}
</style>
</head>
<body>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loader"></div>
</div>
<!-- Crisis Banner -->
<div class="crisis-banner" id="crisisBanner">
<span id="crisisMessage">🚨 SYSTEM CRISIS: Multiple agents failing! 🚨</span>
<button onclick="emergencyStopAll()">EMERGENCY STOP ALL</button>
<button onclick="autoFixCrisis()">AUTO-FIX CRISIS</button>
<button onclick="dismissCrisis()">DISMISS</button>
</div>
<!-- Alert -->
<div class="alert" id="alert"></div>
<!-- Header -->
<div class="header">
<div class="header-content">
<div class="title">
<span>🎯</span>
<span>DW-Agents Monitor Dashboard</span>
</div>
<div class="health-score">
<div>
<div style="font-size: 12px; color: rgba(255, 255, 255, 0.8);">System Health</div>
<div style="font-size: 14px;">Overall Score</div>
</div>
<div class="score-circle" id="healthScore" style="--score-deg: 0deg">
<div class="score-inner">
<span id="scoreValue">0</span>
</div>
</div>
</div>
<div class="refresh-indicator">
<span>🔄 Auto-refresh in</span>
<span class="refresh-countdown" id="refreshCountdown">10</span>
<span>seconds</span>
</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="controls-content">
<div>
<button class="btn btn-primary" onclick="refreshData()">
<span>🔄</span> Refresh Now
</button>
<button class="btn btn-emergency" onclick="emergencyStopAll()">
<span>🛑</span> EMERGENCY STOP ALL
</button>
<button class="btn btn-warning" onclick="autoFixCommonIssues()">
<span>🔧</span> Auto-Fix Issues
</button>
<button class="btn btn-danger" onclick="restartProblematic()">
<span>⚠️</span> Restart Problematic
</button>
<button class="btn btn-success" onclick="exportReport()">
<span>📊</span> Export Report
</button>
</div>
<div style="display: flex; align-items: center; gap: 20px;">
<div>
<span style="font-size: 14px; color: #9ca3af;">System Status: </span>
<span id="systemStatus" style="font-size: 14px; font-weight: bold;">MONITORING</span>
</div>
<div>
<span style="font-size: 14px; color: #9ca3af;">Last update: </span>
<span id="lastUpdate" style="font-size: 14px;">Never</span>
</div>
<div>
<button class="btn btn-primary" onclick="toggleAudioAlerts()" id="audioToggle">
<span>🔊</span> Audio: ON
</button>
</div>
</div>
</div>
</div>
<!-- Stats Summary -->
<div class="stats-summary">
<div class="stats-grid" id="statsGrid">
<!-- Stats will be populated here -->
</div>
</div>
<!-- Main Content -->
<div class="main-content" id="mainContent">
<!-- Agent categories will be populated here -->
</div>
<script>
let refreshInterval = 10; // seconds
let countdown = refreshInterval;
let autoRefreshTimer;
let countdownTimer;
let audioEnabled = true;
let crisisMode = false;
let alertSound = null;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
// Initialize audio alert
initializeAudioAlert();
refreshData();
startAutoRefresh();
});
// Initialize audio alert system
function initializeAudioAlert() {
// Create audio context for crisis alerts
try {
alertSound = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhCjqL1O7AfCsDJHbJ9N+UQwkSXLLp6qdVFgpGnuDzv2whCzuL1O/AfCsDJHbJ9N+UQgkSXLLq66hWEwlFnePzv2whCzqL1O7AfSsDJHfJ9d+UQQoUXrPp66hWEwlEn+DyvmwhCjqL1O7AfCsDJHfH8N2QQAoUXrTp66hVFApGnuDyvmwcBjiQ1/LNeSsFJIHO8tiINwgZaLvt559NEwxVqOPwtmMcBjiR1/LNeSsFJHfH8N2QQAoUXrTp66hVFApGnuDyvmwcBjiQ1/LNeSsFJIHO8tiINwgZaLvt559NEwxVqOPwtmMcBjiR1/LNeSsFJHfH8N2QQAoUXrTp66hVFApGnuDyvmwcBjiQ1/LNeSsFJIHO8tiINwgZaLvt559NEwxVqOPwtmMcBjiR1/LNeSsFJHfH8N2QQAoUXrTp66hVFApGnuDyvmwcBjiQ1/LNeSsFJIHO8tiINwgZaLvt55');
} catch (e) {
console.warn('Audio alerts not supported in this browser');
audioEnabled = false;
}
}
// Start auto-refresh
function startAutoRefresh() {
countdownTimer = setInterval(() => {
countdown--;
document.getElementById('refreshCountdown').textContent = countdown;
if (countdown <= 0) {
countdown = refreshInterval;
refreshData();
}
}, 1000);
}
// Refresh data
async function refreshData() {
showLoading(true);
try {
const response = await fetch('/api/stats');
const result = await response.json();
if (result.success) {
updateDashboard(result.data);
showAlert('Dashboard updated', 'success');
} else {
showAlert('Failed to fetch data', 'error');
}
} catch (error) {
showAlert('Error: ' + error.message, 'error');
} finally {
showLoading(false);
document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString();
}
}
// Update dashboard
function updateDashboard(data) {
// Check for crisis conditions FIRST
checkCrisisConditions(data);
// Update health score
const score = data.healthScore;
const scoreDeg = (score / 100) * 360;
const scoreElement = document.getElementById('healthScore');
scoreElement.style.setProperty('--score-deg', scoreDeg + 'deg');
document.getElementById('scoreValue').textContent = score;
// Update system status
updateSystemStatus(data);
// Update stats summary
updateStatsSummary(data.summary);
// Update agent categories
updateAgentCategories(data.categorized);
}
// Crisis detection system
function checkCrisisConditions(data) {
const criticalAgents = [];
// Check all agents for crisis conditions
Object.values(data.categorized).forEach(category => {
category.forEach(agent => {
if (agent.restarts > 50 ||
(agent.restarts > 20 && agent.status === 'errored') ||
agent.memory > 800 ||
agent.cpu > 95) {
criticalAgents.push(agent);
}
});
});
const wasInCrisis = crisisMode;
crisisMode = criticalAgents.length > 2; // Crisis if 3+ agents critical
if (crisisMode && !wasInCrisis) {
// Entering crisis mode
activateCrisisMode(criticalAgents);
} else if (!crisisMode && wasInCrisis) {
// Exiting crisis mode
deactivateCrisisMode();
}
// Update crisis banner content if still in crisis
if (crisisMode) {
updateCrisisBanner(criticalAgents);
}
}
// Activate crisis mode
function activateCrisisMode(criticalAgents) {
console.log('🚨 CRISIS MODE ACTIVATED', criticalAgents);
// Show crisis banner
const crisisBanner = document.getElementById('crisisBanner');
crisisBanner.classList.add('active');
// Change header to crisis mode
const header = document.querySelector('.header');
header.classList.add('crisis-mode');
// Update system status
document.getElementById('systemStatus').textContent = '🚨 CRISIS';
document.getElementById('systemStatus').style.color = '#ff0000';
// Play audio alert
if (audioEnabled && alertSound) {
try {
alertSound.play();
} catch (e) {
console.warn('Could not play audio alert');
}
}
// Update page title
document.title = '🚨 CRISIS: DW-Agents Emergency';
}
// Deactivate crisis mode
function deactivateCrisisMode() {
console.log('✅ Crisis resolved');
// Hide crisis banner
const crisisBanner = document.getElementById('crisisBanner');
crisisBanner.classList.remove('active');
// Remove crisis header mode
const header = document.querySelector('.header');
header.classList.remove('crisis-mode');
// Reset system status
document.getElementById('systemStatus').textContent = 'MONITORING';
document.getElementById('systemStatus').style.color = '#00ff00';
// Reset page title
document.title = 'DW-Agents Crisis Monitor | Real-time Emergency Detection';
}
// Update crisis banner
function updateCrisisBanner(criticalAgents) {
const message = `🚨 CRISIS: ${criticalAgents.length} agents failing! ` +
`Worst: ${criticalAgents[0].name} (${criticalAgents[0].restarts} restarts)`;
document.getElementById('crisisMessage').textContent = message;
}
// Update system status
function updateSystemStatus(data) {
if (crisisMode) return; // Don't override crisis status
const statusElement = document.getElementById('systemStatus');
const totalAgents = data.summary.total;
const onlineAgents = data.summary.online;
const erroredAgents = data.summary.errored;
if (erroredAgents > totalAgents * 0.3) {
statusElement.textContent = '⚠️ DEGRADED';
statusElement.style.color = '#ffaa00';
} else if (erroredAgents > 0) {
statusElement.textContent = '⚠️ WARNING';
statusElement.style.color = '#ffaa00';
} else {
statusElement.textContent = '✅ HEALTHY';
statusElement.style.color = '#00ff00';
}
}
// Update stats summary
function updateStatsSummary(summary) {
const statsGrid = document.getElementById('statsGrid');
statsGrid.innerHTML = `
<div class="stat-card">
<div class="stat-value" style="color: #10b981">${summary.online}</div>
<div class="stat-label">Online</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #ef4444">${summary.errored}</div>
<div class="stat-label">Errored</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #6b7280">${summary.stopped}</div>
<div class="stat-label">Stopped</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #f59e0b">${summary.launching}</div>
<div class="stat-label">Launching</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #dc2626">${summary.criticalRestarts}</div>
<div class="stat-label">Critical Restarts</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #8b5cf6">${summary.highMemory}</div>
<div class="stat-label">High Memory</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #f59e0b">${summary.highCpu}</div>
<div class="stat-label">High CPU</div>
</div>
<div class="stat-card">
<div class="stat-value">${summary.total}</div>
<div class="stat-label">Total Agents</div>
</div>
`;
}
// Update agent categories
function updateAgentCategories(categorized) {
const mainContent = document.getElementById('mainContent');
mainContent.innerHTML = '';
for (const [category, agents] of Object.entries(categorized)) {
if (agents.length === 0) continue;
const categoryDiv = document.createElement('div');
categoryDiv.className = 'category';
const categoryIcon = getCategoryIcon(category);
categoryDiv.innerHTML = `
<div class="category-header">
<span>${categoryIcon}</span>
<span>${category} (${agents.length})</span>
</div>
<div class="agents-grid">
${agents.map(agent => createAgentCard(agent)).join('')}
</div>
`;
mainContent.appendChild(categoryDiv);
}
}
// Get category icon
function getCategoryIcon(category) {
const icons = {
'Executive': '👔',
'Operations': '⚙️',
'Customer Service': '🤝',
'Business Intelligence': '📊',
'Infrastructure': '🏗️',
'Support': '💼',
'Other': '📦'
};
return icons[category] || '📦';
}
// Create agent card
function createAgentCard(agent) {
const statusClass = `status-${agent.status}`;
const restartClass = agent.restarts > 50 ? 'metric-severe' :
agent.restarts > 20 ? 'metric-critical' :
agent.restarts > 10 ? 'metric-warning' : '';
const memoryClass = agent.memory > 400 ? 'metric-critical' :
agent.memory > 200 ? 'metric-warning' : '';
const cpuClass = agent.cpu > 80 ? 'metric-critical' :
agent.cpu > 50 ? 'metric-warning' : '';
// Determine agent health class
let agentHealthClass = 'healthy';
if (agent.restarts > 50 || agent.memory > 800 || agent.cpu > 95) {
agentHealthClass = 'critical';
} else if (agent.restarts > 20 || agent.memory > 400 || agent.cpu > 80) {
agentHealthClass = 'warning';
}
// Heartbeat indicator
let heartbeatClass = '';
if (agentHealthClass === 'critical') {
heartbeatClass = 'critical';
} else if (agentHealthClass === 'warning') {
heartbeatClass = 'warning';
}
// Memory leak indicator
const memoryLeak = agent.memory > 600 ?
`<div class="memory-leak-indicator">MEMORY LEAK</div>` : '';
// Restart frequency graph
const restartPercentage = Math.min(100, (agent.restarts / 100) * 100);
const errorLogs = agent.errorLogs && agent.errorLogs.length > 0 ?
`<div class="error-logs">${agent.errorLogs.join('<br>')}</div>` : '';
return `
<div class="agent-card ${agentHealthClass}">
${memoryLeak}
<div class="agent-header">
<div class="agent-name">
<span class="heartbeat ${heartbeatClass}"></span>
${agent.name}
</div>
<div class="agent-status ${statusClass}">${agent.status.toUpperCase()}</div>
</div>
<div class="agent-metrics">
<div class="metric">
<span class="metric-icon">🔄</span>
<span class="metric-value ${restartClass}">Restarts: ${agent.restarts}</span>
</div>
<div class="metric">
<span class="metric-icon">⏱️</span>
<span class="metric-value">Uptime: ${agent.uptimeFormatted}</span>
</div>
<div class="metric">
<span class="metric-icon">💾</span>
<span class="metric-value ${memoryClass}">Memory: ${agent.memory}MB</span>
</div>
<div class="metric">
<span class="metric-icon">⚡</span>
<span class="metric-value ${cpuClass}">CPU: ${agent.cpu}%</span>
</div>
</div>
<!-- Restart Frequency Graph -->
<div class="restart-graph">
<div class="restart-bar" style="width: ${restartPercentage}%">
<div class="restart-label">${agent.restarts} restarts</div>
</div>
</div>
<div class="progress-bar">
<div class="progress-fill progress-cpu" style="width: ${Math.min(100, agent.cpu)}%"></div>
</div>
<div class="progress-bar" style="margin-top: 8px;">
<div class="progress-fill progress-memory" style="width: ${Math.min(100, agent.memory / 5)}%"></div>
</div>
${errorLogs}
<div class="agent-actions">
<button class="action-btn" onclick="resetAndRestart('${agent.name}')">Reset & Restart</button>
<button class="action-btn" onclick="viewLastErrors('${agent.name}')">Last 10 Errors</button>
<button class="action-btn" onclick="restartAgent('${agent.name}')">Restart</button>
<button class="action-btn" onclick="stopAgent('${agent.name}')">Stop</button>
<button class="action-btn" onclick="viewLogs('${agent.name}')">Logs</button>
</div>
</div>
`;
}
// Agent actions
async function restartAgent(name) {
if (!confirm(`Restart agent ${name}?`)) return;
showLoading(true);
try {
const response = await fetch(`/api/restart/${name}`, { method: 'POST' });
const result = await response.json();
if (result.success) {
showAlert(`Agent ${name} restarted`, 'success');
setTimeout(refreshData, 2000);
} else {
showAlert(`Failed to restart ${name}`, 'error');
}
} catch (error) {
showAlert('Error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function stopAgent(name) {
if (!confirm(`Stop agent ${name}?`)) return;
showLoading(true);
try {
const response = await fetch(`/api/stop/${name}`, { method: 'POST' });
const result = await response.json();
if (result.success) {
showAlert(`Agent ${name} stopped`, 'success');
setTimeout(refreshData, 2000);
} else {
showAlert(`Failed to stop ${name}`, 'error');
}
} catch (error) {
showAlert('Error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function viewLogs(name) {
window.open(`/api/logs/${name}`, '_blank');
}
// Restart problematic agents
async function restartProblematic() {
if (!confirm('Restart all problematic agents?')) return;
showLoading(true);
try {
const response = await fetch('/api/restart-problematic', { method: 'POST' });
const result = await response.json();
if (result.success) {
const restarted = result.restarted.filter(r => r.success).length;
showAlert(`Restarted ${restarted} problematic agents`, 'success');
setTimeout(refreshData, 3000);
} else {
showAlert('Failed to restart problematic agents', 'error');
}
} catch (error) {
showAlert('Error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// Export report
function exportReport() {
window.location.href = '/api/export';
}
// EMERGENCY ACTIONS
// Emergency stop all problematic agents
async function emergencyStopAll() {
if (!confirm('⚠️ EMERGENCY STOP ALL problematic agents? This will stop all agents with >20 restarts!')) return;
showLoading(true);
try {
const response = await fetch('/api/emergency-stop-all', { method: 'POST' });
const result = await response.json();
if (result.success) {
showAlert(`Emergency stop completed: ${result.stopped} agents stopped`, 'warning');
setTimeout(refreshData, 2000);
} else {
showAlert('Emergency stop failed', 'error');
}
} catch (error) {
showAlert('Emergency stop error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// Auto-fix crisis (stop problematic agents, clear logs, restart clean ones)
async function autoFixCrisis() {
if (!confirm('🔧 Auto-fix crisis mode? This will stop problematic agents and clear their logs.')) return;
showLoading(true);
try {
const response = await fetch('/api/auto-fix-crisis', { method: 'POST' });
const result = await response.json();
if (result.success) {
showAlert(`Crisis auto-fix completed: ${result.message}`, 'success');
setTimeout(refreshData, 3000);
} else {
showAlert('Auto-fix failed', 'error');
}
} catch (error) {
showAlert('Auto-fix error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// Auto-fix common issues
async function autoFixCommonIssues() {
if (!confirm('🔧 Auto-fix common issues? This will clear logs, restart errored agents, and free memory.')) return;
showLoading(true);
try {
const response = await fetch('/api/auto-fix-common', { method: 'POST' });
const result = await response.json();
if (result.success) {
showAlert(`Auto-fix completed: ${result.message}`, 'success');
setTimeout(refreshData, 2000);
} else {
showAlert('Auto-fix failed', 'error');
}
} catch (error) {
showAlert('Auto-fix error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// Reset and restart agent (clear logs first)
async function resetAndRestart(name) {
if (!confirm(`Reset and restart ${name}? This will clear logs and restart clean.`)) return;
showLoading(true);
try {
const response = await fetch(`/api/reset-restart/${name}`, { method: 'POST' });
const result = await response.json();
if (result.success) {
showAlert(`${name} reset and restarted`, 'success');
setTimeout(refreshData, 2000);
} else {
showAlert(`Failed to reset ${name}`, 'error');
}
} catch (error) {
showAlert('Reset error: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// View last 10 errors in modal
async function viewLastErrors(name) {
try {
const response = await fetch(`/api/last-errors/${name}`);
const result = await response.json();
if (result.success) {
const errors = result.errors.join('\\n');
alert(`Last 10 errors for ${name}:\\n\\n${errors}`);
} else {
showAlert('No recent errors found', 'warning');
}
} catch (error) {
showAlert('Error fetching logs: ' + error.message, 'error');
}
}
// Dismiss crisis banner
function dismissCrisis() {
document.getElementById('crisisBanner').classList.remove('active');
const header = document.querySelector('.header');
header.classList.remove('crisis-mode');
crisisMode = false; // Temporarily dismiss
showAlert('Crisis alert dismissed (will reactivate if conditions persist)', 'warning');
}
// Toggle audio alerts
function toggleAudioAlerts() {
audioEnabled = !audioEnabled;
const button = document.getElementById('audioToggle');
const icon = audioEnabled ? '🔊' : '🔇';
const text = audioEnabled ? 'ON' : 'OFF';
button.innerHTML = `<span>${icon}</span> Audio: ${text}`;
showAlert(`Audio alerts ${text}`, 'success');
}
// Show loading
function showLoading(show) {
document.getElementById('loadingOverlay').classList.toggle('active', show);
}
// Show alert
function showAlert(message, type) {
const alert = document.getElementById('alert');
alert.textContent = message;
alert.className = `alert alert-${type}`;
alert.style.display = 'block';
setTimeout(() => {
alert.style.display = 'none';
}, 3000);
}
</script>
</body>
</html>