Spaces:
Sleeping
Sleeping
Claude Code Claude Opus 4.6 commited on
Commit ·
50b0ecc
1
Parent(s): fcece3a
Claude Code: Add diagnostic heartbeat panel to agent dashboard
Browse filesInject 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>
- 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();
|