Monitor: drill-down pipeline view with step timeline
Highlander pattern — one view at each level, clean transitions: Level 1 - Run List: - Active runs (live, with progress bars) - Recent runs (in-memory session runs) - History from DB (all saved runs, click to drill down) Level 2 - Pipeline Detail (click any DB run): - Breadcrumb nav: Monitor → mode #id - Header card with mode, models, timestamp, full prompt - Step timeline with dot indicators on a vertical line - Each step shows: model, role tag, character count, token estimate - Green dots for completed, red for errors Level 3 - Response Text (click any step): - Accordion expand/collapse on click - Full response text in monospace scrollable container - Smooth max-height transition Architecture ready for Level 4 (future AI comparison): - Responses are individually addressable by step index - Role-based grouping visible in timeline - Side-by-side view can be added per-step Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9af071df6c
commit
21c8c2a3e5
242
llm_team_ui.py
242
llm_team_ui.py
@ -3436,23 +3436,27 @@ canvas#bg-grid{position:fixed;inset:0;z-index:0;pointer-events:none}
|
||||
header{display:flex;align-items:center;gap:14px;padding-bottom:18px;border-bottom:2px solid var(--border);margin-bottom:24px}
|
||||
h1{font-family:'JetBrains Mono',monospace;font-size:18px;font-weight:700}
|
||||
h1 span{color:var(--accent)}
|
||||
.back{color:var(--text2);text-decoration:none;font-size:10px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:1px;border:2px solid var(--border);padding:5px 12px;border-radius:2px;margin-left:auto}
|
||||
.back{color:var(--text2);text-decoration:none;font-size:10px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:1px;border:2px solid var(--border);padding:5px 12px;border-radius:2px}
|
||||
.back:hover{border-color:var(--accent);color:var(--accent)}
|
||||
.live-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);animation:pulse-dot 2s ease-in-out infinite}
|
||||
@keyframes pulse-dot{0%,100%{opacity:1}50%{opacity:0.5}}
|
||||
.section{margin-bottom:28px}
|
||||
.section-title{font-family:'JetBrains Mono',monospace;font-size:10px;text-transform:uppercase;letter-spacing:2px;color:var(--accent);margin-bottom:12px;font-weight:700}
|
||||
.card{background:var(--surface);border:2px solid var(--border);border-radius:2px;padding:16px;margin-bottom:8px;backdrop-filter:blur(16px)}
|
||||
.card{background:var(--surface);border:2px solid var(--border);border-radius:2px;padding:16px;margin-bottom:8px;backdrop-filter:blur(16px);cursor:pointer;transition:border-color 0.15s}
|
||||
.card:hover{border-color:rgba(226,181,90,0.4)}
|
||||
.card.active{border-color:var(--accent);box-shadow:0 0 20px rgba(226,181,90,0.05)}
|
||||
.card.error{border-color:var(--red)}
|
||||
.card-row{display:flex;align-items:center;gap:12px;margin-bottom:6px;flex-wrap:wrap}
|
||||
.card.no-click{cursor:default}
|
||||
.card.no-click:hover{border-color:var(--border)}
|
||||
.card-row{display:flex;align-items:center;gap:8px;margin-bottom:4px;flex-wrap:wrap}
|
||||
.tag{font-family:'JetBrains Mono',monospace;font-size:9px;text-transform:uppercase;letter-spacing:1px;padding:3px 8px;border:1px solid;border-radius:1px;font-weight:600}
|
||||
.tag-mode{color:var(--accent);border-color:rgba(226,181,90,0.3)}
|
||||
.tag-user{color:var(--blue);border-color:rgba(91,156,245,0.3)}
|
||||
.tag-time{color:var(--text2);border-color:var(--border)}
|
||||
.tag-err{color:var(--red);border-color:rgba(224,82,82,0.3)}
|
||||
.tag-ok{color:var(--green);border-color:rgba(74,222,128,0.3)}
|
||||
.prompt-text{font-size:12px;color:var(--text2);margin:4px 0 8px;font-style:italic;max-width:600px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.tag-role{color:#c084fc;border-color:rgba(192,132,252,0.3)}
|
||||
.prompt-text{font-size:12px;color:var(--text2);margin:4px 0;font-style:italic;max-width:600px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.mini-progress{height:4px;background:rgba(0,0,0,0.4);border-radius:1px;overflow:hidden;margin:6px 0}
|
||||
.mini-fill{height:100%;background:var(--accent);transition:width 0.5s}
|
||||
.substep{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text2)}
|
||||
@ -3462,6 +3466,28 @@ h1 span{color:var(--accent)}
|
||||
.stat-val{font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent)}
|
||||
.stat-label{font-family:'JetBrains Mono',monospace;font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--text2);margin-top:4px}
|
||||
.empty{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text2);padding:20px;text-align:center;opacity:0.5}
|
||||
.breadcrumb{font-family:'JetBrains Mono',monospace;font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--text2);margin-bottom:16px;display:flex;align-items:center;gap:6px}
|
||||
.breadcrumb a{color:var(--accent);text-decoration:none;cursor:pointer}
|
||||
.breadcrumb a:hover{text-decoration:underline}
|
||||
.breadcrumb .sep{opacity:0.3}
|
||||
.detail-panel{display:none}
|
||||
.detail-panel.open{display:block}
|
||||
.detail-header{background:var(--surface);border:2px solid var(--accent);border-radius:2px;padding:18px;margin-bottom:16px;backdrop-filter:blur(16px)}
|
||||
.detail-prompt{font-size:13px;color:var(--text);margin:8px 0;line-height:1.6}
|
||||
.step-timeline{position:relative;padding-left:24px;margin-bottom:16px}
|
||||
.step-timeline::before{content:'';position:absolute;left:7px;top:4px;bottom:4px;width:2px;background:var(--border)}
|
||||
.step-item{position:relative;margin-bottom:12px;cursor:pointer}
|
||||
.step-dot{position:absolute;left:-20px;top:4px;width:10px;height:10px;border-radius:2px;border:2px solid var(--border);background:var(--bg)}
|
||||
.step-item.done .step-dot{background:var(--accent);border-color:var(--accent)}
|
||||
.step-item.error .step-dot{background:var(--red);border-color:var(--red)}
|
||||
.step-head{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600}
|
||||
.step-meta{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text2);margin-top:2px}
|
||||
.step-preview{font-size:11px;color:var(--text2);margin-top:4px;max-height:0;overflow:hidden;transition:max-height 0.3s;line-height:1.5}
|
||||
.step-item.expanded .step-preview{max-height:2000px}
|
||||
.step-text{background:rgba(0,0,0,0.3);border:1px solid var(--border);border-radius:2px;padding:12px;margin-top:6px;white-space:pre-wrap;font-size:12px;line-height:1.6;max-height:400px;overflow-y:auto}
|
||||
.step-text::-webkit-scrollbar{width:3px}
|
||||
.step-text::-webkit-scrollbar-thumb{background:rgba(226,181,90,0.15)}
|
||||
.click-hint{font-family:'JetBrains Mono',monospace;font-size:8px;text-transform:uppercase;letter-spacing:1.5px;color:var(--text2);opacity:0.4;margin-top:4px}
|
||||
@media(max-width:768px){.stats-grid{grid-template-columns:repeat(2,1fr)}.card-row{gap:6px}}
|
||||
</style>
|
||||
</head><body>
|
||||
@ -3471,8 +3497,11 @@ h1 span{color:var(--accent)}
|
||||
<header>
|
||||
<div class="live-dot"></div>
|
||||
<h1><span>Monitor</span> // Process View</h1>
|
||||
<a class="back" href="/">← Back to Team</a>
|
||||
<a class="back" href="/admin">Admin</a>
|
||||
<nav style="margin-left:auto;display:flex;gap:6px">
|
||||
<a class="back" href="/">Team</a>
|
||||
<a class="back" href="/logs">Logs</a>
|
||||
<a class="back" href="/admin">Admin</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-box"><div class="stat-val" id="s-active">0</div><div class="stat-label">Active Runs</div></div>
|
||||
@ -3480,90 +3509,203 @@ h1 span{color:var(--accent)}
|
||||
<div class="stat-box"><div class="stat-val" id="s-errors">0</div><div class="stat-label">Errors</div></div>
|
||||
<div class="stat-box"><div class="stat-val" id="s-avgtime">—</div><div class="stat-label">Avg Duration</div></div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Active Runs</div>
|
||||
<div id="active-runs"><div class="empty">No active runs</div></div>
|
||||
|
||||
<!-- Level 1: Run list -->
|
||||
<div id="view-list">
|
||||
<div class="section">
|
||||
<div class="section-title">Active Runs</div>
|
||||
<div id="active-runs"><div class="empty">No active runs</div></div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Recent Runs</div>
|
||||
<div id="recent-runs"><div class="empty">No recent runs</div></div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">History (from DB)</div>
|
||||
<div id="db-runs"><div class="empty">Loading...</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="section-title">Recent Runs (last 20)</div>
|
||||
<div id="recent-runs"><div class="empty">No recent runs</div></div>
|
||||
|
||||
<!-- Level 2: Pipeline detail -->
|
||||
<div id="view-detail" class="detail-panel">
|
||||
<div class="breadcrumb">
|
||||
<a onclick="backToList()">Monitor</a>
|
||||
<span class="sep">→</span>
|
||||
<span id="detail-breadcrumb">Run</span>
|
||||
</div>
|
||||
<div id="detail-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
!function(){const c=document.getElementById('bg-grid');if(!c)return;const x=c.getContext('2d');function resize(){c.width=window.innerWidth;c.height=window.innerHeight}resize();window.addEventListener('resize',resize);let t=0;function draw(){x.clearRect(0,0,c.width,c.height);const s=50,ox=(t*0.2)%s,oy=(t*0.1)%s;x.fillStyle='rgba(226,181,90,0.025)';for(let gx=-s+ox;gx<c.width+s;gx+=s){for(let gy=-s+oy;gy<c.height+s;gy+=s){x.beginPath();x.arc(gx,gy,0.7,0,Math.PI*2);x.fill()}}t++;requestAnimationFrame(draw)}draw()}();
|
||||
!function(){var c=document.getElementById('bg-grid');if(!c)return;var x=c.getContext('2d');function resize(){c.width=window.innerWidth;c.height=window.innerHeight}resize();window.addEventListener('resize',resize);var t=0;function draw(){x.clearRect(0,0,c.width,c.height);var s=50,ox=(t*0.2)%s,oy=(t*0.1)%s;x.fillStyle='rgba(226,181,90,0.025)';for(var gx=-s+ox;gx<c.width+s;gx+=s)for(var gy=-s+oy;gy<c.height+s;gy+=s){x.beginPath();x.arc(gx,gy,0.7,0,Math.PI*2);x.fill()}t++;requestAnimationFrame(draw)}draw()}();
|
||||
|
||||
function fmt(s){if(!s&&s!==0)return'—';if(s<60)return Math.round(s)+'s';return Math.floor(s/60)+'m '+Math.round(s%60)+'s'}
|
||||
function esc(t){const d=document.createElement('div');d.textContent=t;return d.innerHTML;}
|
||||
function esc(t){var d=document.createElement('span');d.textContent=t;return d.innerHTML}
|
||||
function tag(text,cls){var t=document.createElement('span');t.className='tag '+cls;t.textContent=text;return t}
|
||||
function truncate(t,n){return t&&t.length>n?t.substring(0,n)+'...':t||''}
|
||||
|
||||
function backToList(){
|
||||
document.getElementById('view-list').style.display='';
|
||||
document.getElementById('view-detail').className='detail-panel';
|
||||
}
|
||||
|
||||
function renderActive(runs){
|
||||
const el=document.getElementById('active-runs');
|
||||
if(!runs.length){el.textContent='';const e=document.createElement('div');e.className='empty';e.textContent='No active runs';el.appendChild(e);return}
|
||||
var el=document.getElementById('active-runs');
|
||||
if(!runs.length){el.textContent='';var e=document.createElement('div');e.className='empty';e.textContent='No active runs';el.appendChild(e);return}
|
||||
el.textContent='';
|
||||
runs.forEach(function(r){
|
||||
const c=document.createElement('div');
|
||||
c.className='card active';
|
||||
const row=document.createElement('div');row.className='card-row';
|
||||
row.appendChild(tag(r.mode,'tag-mode'));
|
||||
row.appendChild(tag(r.user,'tag-user'));
|
||||
row.appendChild(tag(fmt(r.elapsed),'tag-time'));
|
||||
row.appendChild(tag(r.events+' events','tag-time'));
|
||||
var c=document.createElement('div');c.className='card active no-click';
|
||||
var row=document.createElement('div');row.className='card-row';
|
||||
row.appendChild(tag(r.mode,'tag-mode'));row.appendChild(tag(r.user,'tag-user'));
|
||||
row.appendChild(tag(fmt(r.elapsed),'tag-time'));row.appendChild(tag(r.events+' events','tag-time'));
|
||||
if(r.errors>0)row.appendChild(tag(r.errors+' errors','tag-err'));
|
||||
c.appendChild(row);
|
||||
const p=document.createElement('div');p.className='prompt-text';p.textContent=r.prompt;c.appendChild(p);
|
||||
if(r.total_steps>0){
|
||||
const mp=document.createElement('div');mp.className='mini-progress';
|
||||
const mf=document.createElement('div');mf.className='mini-fill';
|
||||
const pct=r.total_steps>0?Math.round((r.step/r.total_steps)*100):0;
|
||||
mf.style.width=Math.max(5,pct)+'%';mp.appendChild(mf);c.appendChild(mp);
|
||||
}
|
||||
if(r.substep){const s=document.createElement('div');s.className='substep';s.textContent=r.substep;c.appendChild(s)}
|
||||
if(r.error_details){r.error_details.forEach(function(e){
|
||||
const el2=document.createElement('div');el2.className='error-line';el2.textContent=e.model+': '+e.error;c.appendChild(el2);
|
||||
})}
|
||||
var p=document.createElement('div');p.className='prompt-text';p.textContent=r.prompt;c.appendChild(p);
|
||||
if(r.total_steps>0){var mp=document.createElement('div');mp.className='mini-progress';var mf=document.createElement('div');mf.className='mini-fill';mf.style.width=Math.max(5,Math.round((r.step/r.total_steps)*100))+'%';mp.appendChild(mf);c.appendChild(mp)}
|
||||
if(r.substep){var s=document.createElement('div');s.className='substep';s.textContent=r.substep;c.appendChild(s)}
|
||||
el.appendChild(c);
|
||||
});
|
||||
}
|
||||
|
||||
function renderRecent(runs){
|
||||
const el=document.getElementById('recent-runs');
|
||||
if(!runs.length){el.textContent='';const e=document.createElement('div');e.className='empty';e.textContent='No recent runs';el.appendChild(e);return}
|
||||
var el=document.getElementById('recent-runs');
|
||||
if(!runs.length){el.textContent='';var e=document.createElement('div');e.className='empty';e.textContent='No recent runs (this session)';el.appendChild(e);return}
|
||||
el.textContent='';
|
||||
runs.forEach(function(r){
|
||||
const c=document.createElement('div');
|
||||
c.className='card'+(r.errors&&r.errors.length?' error':'');
|
||||
const row=document.createElement('div');row.className='card-row';
|
||||
row.appendChild(tag(r.mode,'tag-mode'));
|
||||
row.appendChild(tag(r.user,'tag-user'));
|
||||
var c=document.createElement('div');c.className='card'+(r.errors&&r.errors.length?' error':'');
|
||||
var row=document.createElement('div');row.className='card-row';
|
||||
row.appendChild(tag(r.mode,'tag-mode'));row.appendChild(tag(r.user||'?','tag-user'));
|
||||
row.appendChild(tag(fmt(r.duration),'tag-time'));
|
||||
row.appendChild(tag((r.response_count||0)+' responses','tag-time'));
|
||||
row.appendChild(tag((r.response_count||0)+' resp','tag-time'));
|
||||
if(r.errors&&r.errors.length)row.appendChild(tag(r.errors.length+' errors','tag-err'));
|
||||
else row.appendChild(tag('ok','tag-ok'));
|
||||
c.appendChild(row);
|
||||
const p=document.createElement('div');p.className='prompt-text';p.textContent=r.prompt;c.appendChild(p);
|
||||
if(r.errors&&r.errors.length){r.errors.slice(-2).forEach(function(e){
|
||||
const el2=document.createElement('div');el2.className='error-line';el2.textContent=(e.model||'?')+': '+(e.error||'unknown');c.appendChild(el2);
|
||||
var p=document.createElement('div');p.className='prompt-text';p.textContent=r.prompt;c.appendChild(p);
|
||||
if(r.errors&&r.errors.length){r.errors.slice(-1).forEach(function(e){
|
||||
var el2=document.createElement('div');el2.className='error-line';el2.textContent=(e.model||'?')+': '+truncate(e.error,80);c.appendChild(el2);
|
||||
})}
|
||||
var hint=document.createElement('div');hint.className='click-hint';hint.textContent='No DB entry — in-memory only';
|
||||
c.appendChild(hint);
|
||||
c.style.cursor='default';
|
||||
el.appendChild(c);
|
||||
});
|
||||
}
|
||||
|
||||
function tag(text,cls){const t=document.createElement('span');t.className='tag '+cls;t.textContent=text;return t}
|
||||
function renderDBRuns(runs){
|
||||
var el=document.getElementById('db-runs');
|
||||
if(!runs.length){el.textContent='';var e=document.createElement('div');e.className='empty';e.textContent='No saved runs';el.appendChild(e);return}
|
||||
el.textContent='';
|
||||
runs.forEach(function(r){
|
||||
var c=document.createElement('div');c.className='card';
|
||||
c.onclick=function(){openRun(r.id)};
|
||||
var row=document.createElement('div');row.className='card-row';
|
||||
row.appendChild(tag(r.mode,'tag-mode'));
|
||||
row.appendChild(tag(r.models_used?r.models_used.length+' models':'?','tag-time'));
|
||||
var ts=r.created_at?new Date(r.created_at).toLocaleString():'?';
|
||||
row.appendChild(tag(ts,'tag-time'));
|
||||
c.appendChild(row);
|
||||
var p=document.createElement('div');p.className='prompt-text';p.textContent=truncate(r.prompt,100);c.appendChild(p);
|
||||
var hint=document.createElement('div');hint.className='click-hint';hint.textContent='Click to drill down →';c.appendChild(hint);
|
||||
el.appendChild(c);
|
||||
});
|
||||
}
|
||||
|
||||
async function openRun(id){
|
||||
document.getElementById('view-list').style.display='none';
|
||||
document.getElementById('view-detail').className='detail-panel open';
|
||||
var content=document.getElementById('detail-content');
|
||||
content.textContent='Loading...';
|
||||
try{
|
||||
var r=await fetch('/api/runs/'+id);
|
||||
var run=await r.json();
|
||||
if(run.error){content.textContent='Error: '+run.error;return}
|
||||
document.getElementById('detail-breadcrumb').textContent=run.mode+' #'+id;
|
||||
renderRunDetail(run,content);
|
||||
}catch(e){content.textContent='Error: '+e.message}
|
||||
}
|
||||
|
||||
function renderRunDetail(run,el){
|
||||
el.textContent='';
|
||||
// Header card
|
||||
var header=document.createElement('div');header.className='detail-header';
|
||||
var row=document.createElement('div');row.className='card-row';
|
||||
row.appendChild(tag(run.mode,'tag-mode'));
|
||||
if(run.models_used)row.appendChild(tag(run.models_used.length+' models','tag-time'));
|
||||
var ts=run.created_at?new Date(run.created_at).toLocaleString():'';
|
||||
if(ts)row.appendChild(tag(ts,'tag-time'));
|
||||
header.appendChild(row);
|
||||
var prompt=document.createElement('div');prompt.className='detail-prompt';prompt.textContent=run.prompt||'';header.appendChild(prompt);
|
||||
el.appendChild(header);
|
||||
|
||||
// Step timeline from responses
|
||||
var responses=run.responses||[];
|
||||
if(!responses.length){var empty=document.createElement('div');empty.className='empty';empty.textContent='No responses recorded';el.appendChild(empty);return}
|
||||
|
||||
var title=document.createElement('div');title.className='section-title';title.textContent='Pipeline Steps ('+responses.length+' responses)';el.appendChild(title);
|
||||
|
||||
var timeline=document.createElement('div');timeline.className='step-timeline';
|
||||
var lastRole='';
|
||||
responses.forEach(function(resp,i){
|
||||
var isError=resp.role==='error';
|
||||
var isNewPhase=resp.role!==lastRole;
|
||||
lastRole=resp.role;
|
||||
|
||||
var item=document.createElement('div');
|
||||
item.className='step-item'+(isError?' error':' done');
|
||||
var dot=document.createElement('div');dot.className='step-dot';item.appendChild(dot);
|
||||
|
||||
var head=document.createElement('div');head.className='step-head';
|
||||
var modelSpan=document.createElement('span');modelSpan.textContent=resp.model||'unknown';head.appendChild(modelSpan);
|
||||
head.appendChild(tag(resp.role||'response','tag-role'));
|
||||
var sizeTag=tag(resp.text?resp.text.length+' chars':'empty','tag-time');head.appendChild(sizeTag);
|
||||
item.appendChild(head);
|
||||
|
||||
var meta=document.createElement('div');meta.className='step-meta';
|
||||
meta.textContent='~'+Math.round((resp.text||'').length/4)+' tokens';
|
||||
item.appendChild(meta);
|
||||
|
||||
// Collapsible preview
|
||||
var preview=document.createElement('div');preview.className='step-preview';
|
||||
var textBox=document.createElement('div');textBox.className='step-text';
|
||||
textBox.textContent=resp.text||'(empty)';
|
||||
preview.appendChild(textBox);
|
||||
item.appendChild(preview);
|
||||
|
||||
item.onclick=function(e){
|
||||
e.stopPropagation();
|
||||
item.classList.toggle('expanded');
|
||||
};
|
||||
|
||||
timeline.appendChild(item);
|
||||
});
|
||||
el.appendChild(timeline);
|
||||
}
|
||||
|
||||
async function loadDBRuns(){
|
||||
try{
|
||||
var r=await fetch('/api/runs');
|
||||
var d=await r.json();
|
||||
renderDBRuns(d.runs||[]);
|
||||
}catch(e){document.getElementById('db-runs').textContent='Error: '+e.message}
|
||||
}
|
||||
|
||||
async function poll(){
|
||||
try{
|
||||
const r=await fetch('/api/admin/monitor');
|
||||
const d=await r.json();
|
||||
var r=await fetch('/api/admin/monitor');
|
||||
var d=await r.json();
|
||||
document.getElementById('s-active').textContent=d.active.length;
|
||||
document.getElementById('s-total').textContent=d.recent.length;
|
||||
const errs=d.recent.reduce(function(a,r){return a+((r.errors&&r.errors.length)||0)},0);
|
||||
var errs=d.recent.reduce(function(a,r){return a+((r.errors&&r.errors.length)||0)},0);
|
||||
document.getElementById('s-errors').textContent=errs;
|
||||
const durations=d.recent.filter(function(r){return r.duration}).map(function(r){return r.duration});
|
||||
var durations=d.recent.filter(function(r){return r.duration}).map(function(r){return r.duration});
|
||||
document.getElementById('s-avgtime').textContent=durations.length?fmt(durations.reduce(function(a,b){return a+b},0)/durations.length):'—';
|
||||
renderActive(d.active);
|
||||
renderRecent(d.recent);
|
||||
}catch(e){console.error('Monitor poll error:',e)}
|
||||
}
|
||||
poll();
|
||||
loadDBRuns();
|
||||
setInterval(poll,3000);
|
||||
</script>
|
||||
</body></html>"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user