Arijit-07 Claude Sonnet 4.6 commited on
Commit
f949e30
·
1 Parent(s): fb88ccb

fix: /live dashboard JS — state field names, memory field, alert severity

Browse files

Three bugs prevented the service grid and alerts from rendering:

1. state.observation → state.current_observation
State model serialises the observation under current_observation,
not observation. Services and alerts were never rendered.

2. s.memory_utilization → s.memory_percent
ServiceStatus model field is memory_percent (0-100 float).
All MEM values showed as NaN.

3. a.severity === 'CRITICAL' → a.severity === 'critical'
Alert model severity is lowercase ("critical"/"warning"/"info").
Alert severity colours never applied; critical flash never fired.

Also fixed:
- services rendered via Array.slice().sort() instead of Object.entries()
(services is a JSON array, not a dict)
- max_steps is now dynamic from observation, not hardcoded 15
- rollback_service/block_ip → rollback/block_ip_range (correct action names)
- "WS DISCONNECTED" initial label → "API DISCONNECTED" (REST polling, not WS)

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

Files changed (1) hide show
  1. server/app.py +36 -33
server/app.py CHANGED
@@ -467,7 +467,7 @@ async def live_dashboard():
467
  <div id="bottom-bar">
468
  <div class="ws-status mono">
469
  <div class="status-dot dot-grey" id="btm-dot"></div>
470
- <span id="btm-text" style="color: var(--text-dim)">○ WS DISCONNECTED</span>
471
  </div>
472
  <div class="tip-text" id="tip-text">ⓘ Agents must read_logs before acting — blind remediation triggers -0.10 penalty</div>
473
  <div class="footer-right mono">ARIA v2.0 · OpenEnv Compliant &nbsp;&nbsp; 🤗 Arijit-07</div>
@@ -608,7 +608,11 @@ async def live_dashboard():
608
 
609
  function handleState(state) {{
610
  if (!state.episode_id) return;
611
-
 
 
 
 
612
  if (state.episode_id !== currentEpisodeId) {{
613
  currentEpisodeId = state.episode_id;
614
  lastStep = -1;
@@ -624,57 +628,55 @@ async def live_dashboard():
624
  for (let i = Math.max(0, lastStep); i < state.action_history.length; i++) {{
625
  const hist = state.action_history[i];
626
  const act = hist.action;
627
-
628
  if(act.action_type === 'diagnose') addLog('DIAGNOSE', act.root_cause);
629
- else if(act.action_type === 'restart_service' || act.action_type === 'rollback_service' || act.action_type === 'block_ip')
630
- addLog('FIX', act.action_type, act.service || act.ip);
631
  else addLog('ACTION', act);
632
-
633
  if(hist.reward !== undefined) {{
634
  rewardHistory.push(hist.reward);
635
  addLog('REWARD', hist.reward);
636
  }}
637
  }}
638
-
639
  lastStep = state.step;
640
  totalScore = state.total_reward;
641
-
642
- document.getElementById('top-step').textContent = `${{state.step.toString().padStart(2,'0')}} / 15`;
643
- document.getElementById('stat-step').textContent = `STEP ${{state.step}}/15`;
644
  updateScoreDisplay();
645
  updateSparkline();
646
-
647
- if (state.done || state.incident_resolved) {{
648
  addLog('EPISODE_END', state.total_reward, state.step);
649
- // Ensure we don't duplicate end logs
650
  lastStep = 99999;
651
  }}
652
  }}
653
 
654
- if (state.observation) {{
655
- const obs = state.observation;
656
- if (obs.services) {{
657
- const svcs = Object.entries(obs.services).map(([name, s]) => ({{name, ...s}}));
658
- svcs.sort((a, b) => {{
659
  const val = (st) => st === 'down' ? 0 : (st === 'degraded' ? 1 : 2);
660
  return val(a.status) - val(b.status);
661
  }});
662
-
663
  const list = document.getElementById('service-list');
664
  list.innerHTML = '';
665
  document.getElementById('svc-count').textContent = svcs.length;
666
-
667
  svcs.forEach(s => {{
668
  let bcol = 'var(--border)', bgcol = 'var(--surface)', tcol = 'var(--text-dim)', stxt = '○ UNKNOWN';
669
  if(s.status === 'down') {{ bcol = 'var(--red)'; bgcol = 'var(--red-dim)'; tcol = 'var(--red)'; stxt = '● DOWN'; }}
670
  else if(s.status === 'degraded') {{ bcol = 'var(--yellow)'; bgcol = 'var(--yellow-dim)'; tcol = 'var(--yellow)'; stxt = '◐ DEGRADED'; }}
671
  else if(s.status === 'healthy') {{ bcol = 'var(--green)'; bgcol = 'var(--green-dim)'; tcol = 'var(--green)'; stxt = '○ HEALTHY'; }}
672
-
673
  let errRate = (s.error_rate * 100).toFixed(1);
674
- let memUtil = (s.memory_utilization * 100).toFixed(1);
675
  let errCol = s.error_rate > 0.3 ? 'var(--red)' : (s.error_rate > 0.1 ? 'var(--yellow)' : 'var(--green)');
676
- let memCol = s.memory_utilization > 0.9 ? 'var(--red)' : (s.memory_utilization > 0.7 ? 'var(--yellow)' : 'var(--green)');
677
-
678
  list.innerHTML += `
679
  <div class="service-item mono" style="border-left: 3px solid ${{bcol}}; background: ${{bgcol}}">
680
  <div>
@@ -683,33 +685,34 @@ async def live_dashboard():
683
  </div>
684
  <div class="svc-stats">
685
  <div class="svc-stat-line" style="color:${{errCol}}">ERR ${{errRate}}%</div>
686
- <div class="svc-stat-line" style="color:${{memCol}}">MEM ${{memUtil}}%</div>
687
  </div>
688
  </div>
689
  `;
690
  }});
691
  }}
692
-
693
  if (obs.active_alerts) {{
694
  const alist = document.getElementById('alerts-list');
695
  alist.innerHTML = '';
696
  document.getElementById('alert-count').textContent = obs.active_alerts.length;
697
  document.getElementById('alert-count').style.background = obs.active_alerts.length > 0 ? 'var(--red)' : 'var(--surface2)';
698
-
699
  if(obs.active_alerts.length === 0) {{
700
  alist.innerHTML = '<div class="no-alerts mono">◎ ALL SYSTEMS NOMINAL</div>';
701
  }} else {{
702
  let critFound = false;
703
  obs.active_alerts.slice(0, 5).forEach(a => {{
704
  let bg = 'var(--surface)', border = 'var(--border)', txtCol = '#000';
705
- if(a.severity === 'CRITICAL') {{ border = 'var(--red)'; bg = 'var(--red)'; critFound = true; }}
706
- else if(a.severity === 'HIGH') {{ border = '#ff6600'; bg = '#ff6600'; }}
707
- else if(a.severity === 'WARNING') {{ border = 'var(--yellow)'; bg = 'var(--yellow)'; }}
708
- else {{ border = 'var(--blue)'; bg = 'var(--blue)'; }}
709
-
 
710
  alist.innerHTML += `
711
  <div class="alert-strip mono" style="border-left: 3px solid ${{border}}; background: ${{bg}}20">
712
- <div class="alert-badge" style="background:${{bg}}; color:${{txtCol}}">${{a.severity}}</div>
713
  <div class="alert-text">[${{a.service}}] ${{a.message}}</div>
714
  </div>
715
  `;
 
467
  <div id="bottom-bar">
468
  <div class="ws-status mono">
469
  <div class="status-dot dot-grey" id="btm-dot"></div>
470
+ <span id="btm-text" style="color: var(--text-dim)">○ API DISCONNECTED</span>
471
  </div>
472
  <div class="tip-text" id="tip-text">ⓘ Agents must read_logs before acting — blind remediation triggers -0.10 penalty</div>
473
  <div class="footer-right mono">ARIA v2.0 · OpenEnv Compliant &nbsp;&nbsp; 🤗 Arijit-07</div>
 
608
 
609
  function handleState(state) {{
610
  if (!state.episode_id) return;
611
+
612
+ // API returns current_observation not observation
613
+ const obs = state.current_observation || state.observation;
614
+ const maxSteps = (obs && obs.max_steps) || 15;
615
+
616
  if (state.episode_id !== currentEpisodeId) {{
617
  currentEpisodeId = state.episode_id;
618
  lastStep = -1;
 
628
  for (let i = Math.max(0, lastStep); i < state.action_history.length; i++) {{
629
  const hist = state.action_history[i];
630
  const act = hist.action;
631
+
632
  if(act.action_type === 'diagnose') addLog('DIAGNOSE', act.root_cause);
633
+ else if(act.action_type === 'restart_service' || act.action_type === 'rollback' || act.action_type === 'block_ip_range')
634
+ addLog('FIX', act.action_type, act.service || act.ip_range);
635
  else addLog('ACTION', act);
636
+
637
  if(hist.reward !== undefined) {{
638
  rewardHistory.push(hist.reward);
639
  addLog('REWARD', hist.reward);
640
  }}
641
  }}
642
+
643
  lastStep = state.step;
644
  totalScore = state.total_reward;
645
+
646
+ document.getElementById('top-step').textContent = `${{state.step.toString().padStart(2,'0')}} / ${{maxSteps}}`;
647
+ document.getElementById('stat-step').textContent = `STEP ${{state.step}}/${{maxSteps}}`;
648
  updateScoreDisplay();
649
  updateSparkline();
650
+
651
+ if (state.incident_resolved) {{
652
  addLog('EPISODE_END', state.total_reward, state.step);
 
653
  lastStep = 99999;
654
  }}
655
  }}
656
 
657
+ if (obs) {{
658
+ if (obs.services && obs.services.length > 0) {{
659
+ // services is a JSON array — sort in place
660
+ const svcs = obs.services.slice().sort((a, b) => {{
 
661
  const val = (st) => st === 'down' ? 0 : (st === 'degraded' ? 1 : 2);
662
  return val(a.status) - val(b.status);
663
  }});
664
+
665
  const list = document.getElementById('service-list');
666
  list.innerHTML = '';
667
  document.getElementById('svc-count').textContent = svcs.length;
668
+
669
  svcs.forEach(s => {{
670
  let bcol = 'var(--border)', bgcol = 'var(--surface)', tcol = 'var(--text-dim)', stxt = '○ UNKNOWN';
671
  if(s.status === 'down') {{ bcol = 'var(--red)'; bgcol = 'var(--red-dim)'; tcol = 'var(--red)'; stxt = '● DOWN'; }}
672
  else if(s.status === 'degraded') {{ bcol = 'var(--yellow)'; bgcol = 'var(--yellow-dim)'; tcol = 'var(--yellow)'; stxt = '◐ DEGRADED'; }}
673
  else if(s.status === 'healthy') {{ bcol = 'var(--green)'; bgcol = 'var(--green-dim)'; tcol = 'var(--green)'; stxt = '○ HEALTHY'; }}
674
+
675
  let errRate = (s.error_rate * 100).toFixed(1);
676
+ let memPct = (s.memory_percent || 0).toFixed(1);
677
  let errCol = s.error_rate > 0.3 ? 'var(--red)' : (s.error_rate > 0.1 ? 'var(--yellow)' : 'var(--green)');
678
+ let memCol = (s.memory_percent || 0) > 90 ? 'var(--red)' : ((s.memory_percent || 0) > 70 ? 'var(--yellow)' : 'var(--green)');
679
+
680
  list.innerHTML += `
681
  <div class="service-item mono" style="border-left: 3px solid ${{bcol}}; background: ${{bgcol}}">
682
  <div>
 
685
  </div>
686
  <div class="svc-stats">
687
  <div class="svc-stat-line" style="color:${{errCol}}">ERR ${{errRate}}%</div>
688
+ <div class="svc-stat-line" style="color:${{memCol}}">MEM ${{memPct}}%</div>
689
  </div>
690
  </div>
691
  `;
692
  }});
693
  }}
694
+
695
  if (obs.active_alerts) {{
696
  const alist = document.getElementById('alerts-list');
697
  alist.innerHTML = '';
698
  document.getElementById('alert-count').textContent = obs.active_alerts.length;
699
  document.getElementById('alert-count').style.background = obs.active_alerts.length > 0 ? 'var(--red)' : 'var(--surface2)';
700
+
701
  if(obs.active_alerts.length === 0) {{
702
  alist.innerHTML = '<div class="no-alerts mono">◎ ALL SYSTEMS NOMINAL</div>';
703
  }} else {{
704
  let critFound = false;
705
  obs.active_alerts.slice(0, 5).forEach(a => {{
706
  let bg = 'var(--surface)', border = 'var(--border)', txtCol = '#000';
707
+ // Alert severity is lowercase: "critical", "warning", "info"
708
+ if(a.severity === 'critical') {{ border = 'var(--red)'; bg = 'var(--red)'; critFound = true; }}
709
+ else if(a.severity === 'high') {{ border = '#ff6600'; bg = '#ff6600'; }}
710
+ else if(a.severity === 'warning') {{ border = 'var(--yellow)'; bg = 'var(--yellow)'; }}
711
+ else {{ border = 'var(--blue)'; bg = 'var(--blue)'; txtCol = '#fff'; }}
712
+
713
  alist.innerHTML += `
714
  <div class="alert-strip mono" style="border-left: 3px solid ${{border}}; background: ${{bg}}20">
715
+ <div class="alert-badge" style="background:${{bg}}; color:${{txtCol}}">${{a.severity.toUpperCase()}}</div>
716
  <div class="alert-text">[${{a.service}}] ${{a.message}}</div>
717
  </div>
718
  `;