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>

Files changed (1) hide show
  1. 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>