Claude Code Claude Opus 4.6 commited on
Commit
774e62c
·
1 Parent(s): f5415ca

Claude Code: Add /api/health endpoint with uptime and active_agents, and footer status polling in dashboard

Browse files
Files changed (2) hide show
  1. app.py +9 -0
  2. frontend/agent-dashboard.html +112 -0
app.py CHANGED
@@ -56,6 +56,15 @@ def get_app():
56
  """Health check endpoint for Docker logs."""
57
  return {"status": "ok"}
58
 
 
 
 
 
 
 
 
 
 
59
  @app.exception_handler(Exception)
60
  async def global_exception_handler(request: Request, exc: Exception):
61
  """Global exception handler to catch all errors."""
 
56
  """Health check endpoint for Docker logs."""
57
  return {"status": "ok"}
58
 
59
+ @app.get("/api/health")
60
+ async def api_health():
61
+ """Health check API endpoint with uptime and agent info."""
62
+ return {
63
+ "status": "ok",
64
+ "uptime_seconds": time.time() - START_TIME,
65
+ "active_agents": 1 # Cain is always active
66
+ }
67
+
68
  @app.exception_handler(Exception)
69
  async def global_exception_handler(request: Request, exc: Exception):
70
  """Global exception handler to catch all errors."""
frontend/agent-dashboard.html CHANGED
@@ -409,6 +409,53 @@
409
  font-size: 14px;
410
  color: #666;
411
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  </style>
413
  </head>
414
  <body>
@@ -560,6 +607,15 @@
560
  <!-- Transfer notification container -->
561
  <div id="notificationContainer"></div>
562
 
 
 
 
 
 
 
 
 
 
563
  <script>
564
  // Configuration
565
  const API_BASE = window.location.origin;
@@ -579,8 +635,10 @@
579
  setInterval(updateStats, 1000);
580
  setInterval(fetchResources, RESOURCE_INTERVAL);
581
  setInterval(fetchDebugHealth, 5000); // Poll /debug/health every 5 seconds
 
582
  fetchResources(); // Initial fetch
583
  fetchDebugHealth(); // Initial health fetch
 
584
  });
585
 
586
  // Connect to event bus
@@ -889,6 +947,60 @@
889
  if (uptimeEl) uptimeEl.textContent = statusCode;
890
  if (memoryEl) memoryEl.textContent = statusCode;
891
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  </script>
893
  </body>
894
  </html>
 
409
  font-size: 14px;
410
  color: #666;
411
  }
412
+
413
+ /* Footer Styles */
414
+ .footer {
415
+ background: white;
416
+ border-radius: 12px;
417
+ padding: 16px 24px;
418
+ margin-top: 20px;
419
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
420
+ display: flex;
421
+ justify-content: space-between;
422
+ align-items: center;
423
+ flex-wrap: wrap;
424
+ gap: 16px;
425
+ }
426
+
427
+ .footer-status {
428
+ display: flex;
429
+ align-items: center;
430
+ gap: 12px;
431
+ }
432
+
433
+ .footer-status-dot {
434
+ width: 10px;
435
+ height: 10px;
436
+ border-radius: 50%;
437
+ background: #28a745;
438
+ animation: pulse 2s infinite;
439
+ }
440
+
441
+ .footer-status-dot.offline {
442
+ background: #dc3545;
443
+ }
444
+
445
+ .footer-status-dot.warning {
446
+ background: #ffc107;
447
+ }
448
+
449
+ .footer-info {
450
+ font-size: 14px;
451
+ color: #666;
452
+ }
453
+
454
+ .footer-uptime {
455
+ font-size: 13px;
456
+ color: #999;
457
+ font-family: monospace;
458
+ }
459
  </style>
460
  </head>
461
  <body>
 
607
  <!-- Transfer notification container -->
608
  <div id="notificationContainer"></div>
609
 
610
+ <!-- Footer -->
611
+ <div class="footer">
612
+ <div class="footer-status">
613
+ <div class="footer-status-dot" id="footerStatusDot"></div>
614
+ <span class="footer-info" id="footerStatusText">Connecting...</span>
615
+ </div>
616
+ <div class="footer-uptime" id="footerUptime">Uptime: --</div>
617
+ </div>
618
+
619
  <script>
620
  // Configuration
621
  const API_BASE = window.location.origin;
 
635
  setInterval(updateStats, 1000);
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
 
947
  if (uptimeEl) uptimeEl.textContent = statusCode;
948
  if (memoryEl) memoryEl.textContent = statusCode;
949
  }
950
+
951
+ // ========== API Health Monitoring ==========
952
+
953
+ // Fetch API health status
954
+ async function fetchApiHealth() {
955
+ try {
956
+ const response = await fetch(`${API_BASE}/api/health`);
957
+ if (response.ok) {
958
+ const data = await response.json();
959
+ updateFooterStatus(true, data);
960
+ } else {
961
+ updateFooterStatus(false, null);
962
+ }
963
+ } catch (error) {
964
+ console.error('Error fetching API health:', error);
965
+ updateFooterStatus(false, null);
966
+ }
967
+ }
968
+
969
+ // Update footer status display
970
+ function updateFooterStatus(isOnline, data) {
971
+ const dot = document.getElementById('footerStatusDot');
972
+ const text = document.getElementById('footerStatusText');
973
+ const uptimeEl = document.getElementById('footerUptime');
974
+
975
+ if (isOnline && data && data.status === 'ok') {
976
+ dot.className = 'footer-status-dot';
977
+ text.textContent = `Online (${data.active_agents || 1} agent${(data.active_agents || 1) !== 1 ? 's' : ''} active)`;
978
+ if (data.uptime_seconds !== undefined) {
979
+ uptimeEl.textContent = `Uptime: ${formatUptime(data.uptime_seconds)}`;
980
+ }
981
+ } else {
982
+ dot.className = 'footer-status-dot offline';
983
+ text.textContent = 'Offline - Reconnecting...';
984
+ }
985
+ }
986
+
987
+ // Format uptime seconds to readable string
988
+ function formatUptime(seconds) {
989
+ const days = Math.floor(seconds / 86400);
990
+ const hours = Math.floor((seconds % 86400) / 3600);
991
+ const minutes = Math.floor((seconds % 3600) / 60);
992
+ const secs = Math.floor(seconds % 60);
993
+
994
+ if (days > 0) {
995
+ return `${days}d ${hours}h ${minutes}m`;
996
+ } else if (hours > 0) {
997
+ return `${hours}h ${minutes}m ${secs}s`;
998
+ } else if (minutes > 0) {
999
+ return `${minutes}m ${secs}s`;
1000
+ } else {
1001
+ return `${secs}s`;
1002
+ }
1003
+ }
1004
  </script>
1005
  </body>
1006
  </html>