Claude Code Claude Opus 4.6 commited on
Commit
50b0ecc
·
1 Parent(s): fcece3a

Claude Code: Add diagnostic heartbeat panel to agent dashboard

Browse files

Inject simple HTML status panel that polls /status every 5 seconds and
displays brain_pid and health fields. Uses vanilla JS fetch() API with
auto-stop polling if DOM element is removed. Styled with pixel-art border
to match existing theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. frontend/agent-dashboard.html +173 -0
frontend/agent-dashboard.html CHANGED
@@ -566,6 +566,102 @@
566
  margin-top: 8px;
567
  text-align: right;
568
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  </style>
570
  </head>
571
  <body>
@@ -622,6 +718,24 @@
622
  <div id="debug-health" style="color: #6c757d;">health: <span>-</span></div>
623
  </div>
624
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  </div>
626
 
627
  <!-- Main Content -->
@@ -780,6 +894,64 @@
780
  let eventCount = 0;
781
  let lastResources = { adam: 100, eve: 100 };
782
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
  // Initialize with error boundary
784
  document.addEventListener('DOMContentLoaded', () => {
785
  try {
@@ -790,6 +962,7 @@
790
  setInterval(fetchDebugHealth, 5000);
791
  setInterval(fetchApiHealth, 3000);
792
  setInterval(fetchCainState, 2000);
 
793
  fetchResources();
794
  fetchDebugHealth();
795
  fetchApiHealth();
 
566
  margin-top: 8px;
567
  text-align: right;
568
  }
569
+
570
+ /* Diagnostic Heartbeat Panel - Pixel Art Border */
571
+ .diagnostic-heartbeat {
572
+ background: white;
573
+ border-radius: 12px;
574
+ padding: 16px;
575
+ margin-top: 16px;
576
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
577
+ position: relative;
578
+ border: 3px solid #333;
579
+ image-rendering: pixelated;
580
+ }
581
+
582
+ .diagnostic-heartbeat::before {
583
+ content: '';
584
+ position: absolute;
585
+ top: -3px;
586
+ left: -3px;
587
+ right: -3px;
588
+ bottom: -3px;
589
+ border: 3px solid #667eea;
590
+ border-radius: 12px;
591
+ z-index: -1;
592
+ box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.2);
593
+ }
594
+
595
+ .diagnostic-header {
596
+ display: flex;
597
+ align-items: center;
598
+ gap: 8px;
599
+ margin-bottom: 12px;
600
+ font-size: 14px;
601
+ font-weight: bold;
602
+ color: #333;
603
+ text-transform: uppercase;
604
+ letter-spacing: 1px;
605
+ }
606
+
607
+ .heartbeat-indicator {
608
+ width: 12px;
609
+ height: 12px;
610
+ background: #28a745;
611
+ border-radius: 2px;
612
+ animation: heartbeat-pulse 1s infinite;
613
+ }
614
+
615
+ @keyframes heartbeat-pulse {
616
+ 0%, 100% { transform: scale(1); opacity: 1; }
617
+ 50% { transform: scale(1.2); opacity: 0.8; }
618
+ }
619
+
620
+ .diagnostic-grid {
621
+ display: grid;
622
+ grid-template-columns: 1fr 1fr;
623
+ gap: 12px;
624
+ font-family: 'Courier New', monospace;
625
+ font-size: 13px;
626
+ }
627
+
628
+ .diagnostic-item {
629
+ display: flex;
630
+ flex-direction: column;
631
+ gap: 4px;
632
+ }
633
+
634
+ .diagnostic-label {
635
+ color: #666;
636
+ font-size: 11px;
637
+ text-transform: uppercase;
638
+ letter-spacing: 0.5px;
639
+ }
640
+
641
+ .diagnostic-value {
642
+ color: #333;
643
+ font-weight: bold;
644
+ background: #f8f9fa;
645
+ padding: 6px 8px;
646
+ border-radius: 4px;
647
+ border: 1px solid #e0e0e0;
648
+ }
649
+
650
+ .diagnostic-value.loading {
651
+ color: #999;
652
+ }
653
+
654
+ .diagnostic-value.healthy {
655
+ color: #28a745;
656
+ background: #e8f5e9;
657
+ border-color: #c8e6c9;
658
+ }
659
+
660
+ .diagnostic-value.unhealthy {
661
+ color: #dc3545;
662
+ background: #ffebee;
663
+ border-color: #ffcdd2;
664
+ }
665
  </style>
666
  </head>
667
  <body>
 
718
  <div id="debug-health" style="color: #6c757d;">health: <span>-</span></div>
719
  </div>
720
  </div>
721
+
722
+ <!-- Diagnostic Heartbeat Panel -->
723
+ <div class="diagnostic-heartbeat" id="diagnosticHeartbeat">
724
+ <div class="diagnostic-header">
725
+ <div class="heartbeat-indicator"></div>
726
+ <span>System Heartbeat</span>
727
+ </div>
728
+ <div class="diagnostic-grid">
729
+ <div class="diagnostic-item">
730
+ <div class="diagnostic-label">Brain PID</div>
731
+ <div class="diagnostic-value loading" id="brainPid">Loading...</div>
732
+ </div>
733
+ <div class="diagnostic-item">
734
+ <div class="diagnostic-label">Health</div>
735
+ <div class="diagnostic-value loading" id="brainHealth">Loading...</div>
736
+ </div>
737
+ </div>
738
+ </div>
739
  </div>
740
 
741
  <!-- Main Content -->
 
894
  let eventCount = 0;
895
  let lastResources = { adam: 100, eve: 100 };
896
 
897
+ // ========== Diagnostic Heartbeat Monitoring ==========
898
+ let diagnosticInterval = null;
899
+
900
+ async function fetchDiagnosticHeartbeat() {
901
+ const panel = document.getElementById('diagnosticHeartbeat');
902
+ if (!panel) {
903
+ // Auto-stop polling if DOM element is removed
904
+ if (diagnosticInterval) {
905
+ clearInterval(diagnosticInterval);
906
+ diagnosticInterval = null;
907
+ }
908
+ return;
909
+ }
910
+
911
+ try {
912
+ const response = await fetch(`${CONFIG.apiBase}/status`);
913
+ if (response.ok) {
914
+ const data = await response.json();
915
+ updateDiagnosticHeartbeat(data);
916
+ } else {
917
+ console.warn('Diagnostic heartbeat endpoint returned:', response.status);
918
+ }
919
+ } catch (error) {
920
+ console.error('Error fetching diagnostic heartbeat:', error);
921
+ }
922
+ }
923
+
924
+ function updateDiagnosticHeartbeat(data) {
925
+ const brainPidEl = document.getElementById('brainPid');
926
+ const brainHealthEl = document.getElementById('brainHealth');
927
+
928
+ if (brainPidEl) {
929
+ brainPidEl.textContent = data.brain_pid || 'N/A';
930
+ brainPidEl.classList.remove('loading');
931
+ }
932
+
933
+ if (brainHealthEl) {
934
+ const health = data.health || 'unknown';
935
+ brainHealthEl.textContent = health;
936
+ brainHealthEl.classList.remove('loading');
937
+ brainHealthEl.classList.remove('healthy', 'unhealthy');
938
+
939
+ if (health === 'healthy' || health === 'ok') {
940
+ brainHealthEl.classList.add('healthy');
941
+ } else if (health === 'unhealthy' || health === 'error') {
942
+ brainHealthEl.classList.add('unhealthy');
943
+ }
944
+ }
945
+ }
946
+
947
+ // Initialize diagnostic heartbeat polling
948
+ function startDiagnosticHeartbeat() {
949
+ // Initial fetch
950
+ fetchDiagnosticHeartbeat();
951
+ // Poll every 5 seconds
952
+ diagnosticInterval = setInterval(fetchDiagnosticHeartbeat, 5000);
953
+ }
954
+
955
  // Initialize with error boundary
956
  document.addEventListener('DOMContentLoaded', () => {
957
  try {
 
962
  setInterval(fetchDebugHealth, 5000);
963
  setInterval(fetchApiHealth, 3000);
964
  setInterval(fetchCainState, 2000);
965
+ startDiagnosticHeartbeat();
966
  fetchResources();
967
  fetchDebugHealth();
968
  fetchApiHealth();