Spaces:
Sleeping
Sleeping
Claude Code Claude Opus 4.6 commited on
Commit ·
059331e
1
Parent(s): 91954c3
Claude Code: Add Cain state polling to agent dashboard
Browse files- Add /api/state polling every 2 seconds
- Display Cain's health with visual indicator (green/yellow/red dot)
- Show current stage, state, and detail information
- Add last updated timestamp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- frontend/agent-dashboard.html +185 -0
frontend/agent-dashboard.html
CHANGED
|
@@ -456,6 +456,95 @@
|
|
| 456 |
color: #999;
|
| 457 |
font-family: monospace;
|
| 458 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
</style>
|
| 460 |
</head>
|
| 461 |
<body>
|
|
@@ -477,6 +566,33 @@
|
|
| 477 |
<span>Last update: <strong id="lastUpdate">Never</strong></span>
|
| 478 |
</div>
|
| 479 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
</div>
|
| 481 |
|
| 482 |
<!-- Main Content -->
|
|
@@ -636,9 +752,11 @@
|
|
| 636 |
setInterval(fetchResources, RESOURCE_INTERVAL);
|
| 637 |
setInterval(fetchDebugHealth, 5000); // Poll /debug/health every 5 seconds
|
| 638 |
setInterval(fetchApiHealth, 3000); // Poll /api/health every 3 seconds
|
|
|
|
| 639 |
fetchResources(); // Initial fetch
|
| 640 |
fetchDebugHealth(); // Initial health fetch
|
| 641 |
fetchApiHealth(); // Initial API health fetch
|
|
|
|
| 642 |
});
|
| 643 |
|
| 644 |
// Connect to event bus
|
|
@@ -1001,6 +1119,73 @@
|
|
| 1001 |
return `${secs}s`;
|
| 1002 |
}
|
| 1003 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1004 |
</script>
|
| 1005 |
</body>
|
| 1006 |
</html>
|
|
|
|
| 456 |
color: #999;
|
| 457 |
font-family: monospace;
|
| 458 |
}
|
| 459 |
+
|
| 460 |
+
/* Cain State Status */
|
| 461 |
+
.cain-state-panel {
|
| 462 |
+
background: white;
|
| 463 |
+
border-radius: 12px;
|
| 464 |
+
padding: 16px;
|
| 465 |
+
margin-top: 16px;
|
| 466 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
.cain-state-header {
|
| 470 |
+
display: flex;
|
| 471 |
+
align-items: center;
|
| 472 |
+
gap: 12px;
|
| 473 |
+
margin-bottom: 12px;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.cain-state-title {
|
| 477 |
+
font-size: 16px;
|
| 478 |
+
font-weight: bold;
|
| 479 |
+
color: #333;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
.cain-health-dot {
|
| 483 |
+
width: 16px;
|
| 484 |
+
height: 16px;
|
| 485 |
+
border-radius: 50%;
|
| 486 |
+
animation: pulse 2s infinite;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.cain-health-dot.healthy {
|
| 490 |
+
background: #28a745;
|
| 491 |
+
box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
.cain-health-dot.degraded {
|
| 495 |
+
background: #ffc107;
|
| 496 |
+
box-shadow: 0 0 8px rgba(255, 193, 7, 0.6);
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
.cain-health-dot.unhealthy {
|
| 500 |
+
background: #dc3545;
|
| 501 |
+
box-shadow: 0 0 8px rgba(220, 53, 69, 0.6);
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
.cain-state-info {
|
| 505 |
+
display: grid;
|
| 506 |
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
| 507 |
+
gap: 12px;
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
.cain-state-item {
|
| 511 |
+
background: #f8f9fa;
|
| 512 |
+
border-radius: 8px;
|
| 513 |
+
padding: 12px;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.cain-state-label {
|
| 517 |
+
font-size: 11px;
|
| 518 |
+
color: #666;
|
| 519 |
+
text-transform: uppercase;
|
| 520 |
+
margin-bottom: 4px;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.cain-state-value {
|
| 524 |
+
font-size: 14px;
|
| 525 |
+
font-weight: bold;
|
| 526 |
+
color: #333;
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
.cain-state-value.stage {
|
| 530 |
+
color: #667eea;
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
.cain-state-value.state {
|
| 534 |
+
color: #388e3c;
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
.cain-state-value.detail {
|
| 538 |
+
color: #666;
|
| 539 |
+
font-weight: normal;
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
.cain-last-updated {
|
| 543 |
+
font-size: 11px;
|
| 544 |
+
color: #999;
|
| 545 |
+
margin-top: 8px;
|
| 546 |
+
text-align: right;
|
| 547 |
+
}
|
| 548 |
</style>
|
| 549 |
</head>
|
| 550 |
<body>
|
|
|
|
| 566 |
<span>Last update: <strong id="lastUpdate">Never</strong></span>
|
| 567 |
</div>
|
| 568 |
</div>
|
| 569 |
+
|
| 570 |
+
<!-- Cain State Panel -->
|
| 571 |
+
<div class="cain-state-panel">
|
| 572 |
+
<div class="cain-state-header">
|
| 573 |
+
<div class="cain-health-dot" id="cainHealthDot"></div>
|
| 574 |
+
<span class="cain-state-title">Cain Status</span>
|
| 575 |
+
</div>
|
| 576 |
+
<div class="cain-state-info">
|
| 577 |
+
<div class="cain-state-item">
|
| 578 |
+
<div class="cain-state-label">Health</div>
|
| 579 |
+
<div class="cain-state-value" id="cainHealth">Checking...</div>
|
| 580 |
+
</div>
|
| 581 |
+
<div class="cain-state-item">
|
| 582 |
+
<div class="cain-state-label">Stage</div>
|
| 583 |
+
<div class="cain-state-value stage" id="cainStage">-</div>
|
| 584 |
+
</div>
|
| 585 |
+
<div class="cain-state-item">
|
| 586 |
+
<div class="cain-state-label">State</div>
|
| 587 |
+
<div class="cain-state-value state" id="cainState">-</div>
|
| 588 |
+
</div>
|
| 589 |
+
<div class="cain-state-item">
|
| 590 |
+
<div class="cain-state-label">Detail</div>
|
| 591 |
+
<div class="cain-state-value detail" id="cainDetail">-</div>
|
| 592 |
+
</div>
|
| 593 |
+
</div>
|
| 594 |
+
<div class="cain-last-updated" id="cainLastUpdated">Last updated: Never</div>
|
| 595 |
+
</div>
|
| 596 |
</div>
|
| 597 |
|
| 598 |
<!-- Main Content -->
|
|
|
|
| 752 |
setInterval(fetchResources, RESOURCE_INTERVAL);
|
| 753 |
setInterval(fetchDebugHealth, 5000); // Poll /debug/health every 5 seconds
|
| 754 |
setInterval(fetchApiHealth, 3000); // Poll /api/health every 3 seconds
|
| 755 |
+
setInterval(fetchCainState, 2000); // Poll /api/state every 2 seconds
|
| 756 |
fetchResources(); // Initial fetch
|
| 757 |
fetchDebugHealth(); // Initial health fetch
|
| 758 |
fetchApiHealth(); // Initial API health fetch
|
| 759 |
+
fetchCainState(); // Initial state fetch
|
| 760 |
});
|
| 761 |
|
| 762 |
// Connect to event bus
|
|
|
|
| 1119 |
return `${secs}s`;
|
| 1120 |
}
|
| 1121 |
}
|
| 1122 |
+
|
| 1123 |
+
// ========== Cain State Monitoring ==========
|
| 1124 |
+
|
| 1125 |
+
// Fetch Cain state from /api/state
|
| 1126 |
+
async function fetchCainState() {
|
| 1127 |
+
try {
|
| 1128 |
+
const response = await fetch(`${API_BASE}/api/state`);
|
| 1129 |
+
if (response.ok) {
|
| 1130 |
+
const data = await response.json();
|
| 1131 |
+
updateCainState(data);
|
| 1132 |
+
} else {
|
| 1133 |
+
console.error('Failed to fetch Cain state:', response.status, response.statusText);
|
| 1134 |
+
setCainStateError('API Error');
|
| 1135 |
+
}
|
| 1136 |
+
} catch (error) {
|
| 1137 |
+
console.error('Error fetching Cain state:', error);
|
| 1138 |
+
setCainStateError('Network Error');
|
| 1139 |
+
}
|
| 1140 |
+
}
|
| 1141 |
+
|
| 1142 |
+
// Update Cain state display
|
| 1143 |
+
function updateCainState(data) {
|
| 1144 |
+
const healthDot = document.getElementById('cainHealthDot');
|
| 1145 |
+
const healthEl = document.getElementById('cainHealth');
|
| 1146 |
+
const stageEl = document.getElementById('cainStage');
|
| 1147 |
+
const stateEl = document.getElementById('cainState');
|
| 1148 |
+
const detailEl = document.getElementById('cainDetail');
|
| 1149 |
+
const lastUpdatedEl = document.getElementById('cainLastUpdated');
|
| 1150 |
+
|
| 1151 |
+
// Update health indicator
|
| 1152 |
+
const health = (data.health || 'unknown').toLowerCase();
|
| 1153 |
+
healthDot.className = 'cain-health-dot';
|
| 1154 |
+
if (health === 'healthy' || health === 'ok' || health === 'good') {
|
| 1155 |
+
healthDot.classList.add('healthy');
|
| 1156 |
+
healthEl.textContent = 'Healthy';
|
| 1157 |
+
healthEl.style.color = '#28a745';
|
| 1158 |
+
} else if (health === 'degraded' || health === 'warning' || health === 'fair') {
|
| 1159 |
+
healthDot.classList.add('degraded');
|
| 1160 |
+
healthEl.textContent = 'Degraded';
|
| 1161 |
+
healthEl.style.color = '#f57c00';
|
| 1162 |
+
} else {
|
| 1163 |
+
healthDot.classList.add('unhealthy');
|
| 1164 |
+
healthEl.textContent = health === 'unknown' ? 'Unknown' : 'Unhealthy';
|
| 1165 |
+
healthEl.style.color = health === 'unknown' ? '#999' : '#dc3545';
|
| 1166 |
+
}
|
| 1167 |
+
|
| 1168 |
+
// Update stage, state, and detail
|
| 1169 |
+
stageEl.textContent = data.stage || data.current_stage || '-';
|
| 1170 |
+
stateEl.textContent = data.state || 'idle';
|
| 1171 |
+
detailEl.textContent = data.detail || data.message || 'No details';
|
| 1172 |
+
|
| 1173 |
+
// Update last updated timestamp
|
| 1174 |
+
const now = new Date();
|
| 1175 |
+
lastUpdatedEl.textContent = `Last updated: ${now.toLocaleTimeString()}`;
|
| 1176 |
+
}
|
| 1177 |
+
|
| 1178 |
+
// Set error state for Cain state
|
| 1179 |
+
function setCainStateError(errorMsg) {
|
| 1180 |
+
const healthDot = document.getElementById('cainHealthDot');
|
| 1181 |
+
const healthEl = document.getElementById('cainHealth');
|
| 1182 |
+
const lastUpdatedEl = document.getElementById('cainLastUpdated');
|
| 1183 |
+
|
| 1184 |
+
healthDot.className = 'cain-health-dot unhealthy';
|
| 1185 |
+
healthEl.textContent = 'Error';
|
| 1186 |
+
healthEl.style.color = '#dc3545';
|
| 1187 |
+
lastUpdatedEl.textContent = `Error: ${errorMsg} (${new Date().toLocaleTimeString()})`;
|
| 1188 |
+
}
|
| 1189 |
</script>
|
| 1190 |
</body>
|
| 1191 |
</html>
|