fix: load live dashboard data and add terminal actions
All checks were successful
Release Dry Run / release-dry-run (push) Successful in 8s
Codex Template Compliance / template-compliance (push) Successful in 5s
Build / test-and-image (push) Successful in 38s

This commit is contained in:
2026-05-17 13:13:38 +02:00
parent 4262c7e939
commit 53470cc701
5 changed files with 102 additions and 10 deletions

View File

@@ -83,6 +83,13 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
.sensor-actions{display:flex;gap:6px;align-items:center}
.mini-btn{border:1px solid rgba(100,240,200,0.18);background:rgba(100,240,200,0.04);color:var(--dim);font-family:var(--mono);font-size:9px;padding:3px 6px;cursor:pointer}
.mini-btn:hover{color:var(--accent);border-color:rgba(100,240,200,0.4)}
.action-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:10px}
.action-btn{border:1px solid rgba(68,204,255,0.24);background:rgba(68,204,255,0.06);color:var(--text);font-family:var(--mono);font-size:9px;padding:7px 6px;cursor:pointer;text-transform:uppercase;letter-spacing:.08em}
.action-btn:hover{border-color:rgba(68,204,255,0.55);color:var(--accent2);background:rgba(68,204,255,0.12)}
.action-btn[disabled]{opacity:.45;cursor:wait}
.terminal-output{min-height:58px;max-height:180px;overflow:auto;border:1px solid rgba(255,255,255,0.05);background:rgba(0,0,0,0.22);padding:8px;font-family:var(--mono);font-size:10px;line-height:1.45;color:var(--dim);white-space:pre-wrap}
.terminal-output strong{color:var(--accent)}
.terminal-output .err{color:var(--danger)}
.layer-left{display:flex;align-items:center;gap:8px}
.ldot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
.ldot.air{background:var(--accent);box-shadow:0 0 6px rgba(100,240,200,0.4)}
@@ -404,6 +411,8 @@ let lowPerfMode = localStorage.getItem('crucix_low_perf') === 'true';
let isFlat = shouldStartFlat();
let layerModes = JSON.parse(localStorage.getItem('crucix_layer_modes') || '{}');
let spaceDisplayMode = localStorage.getItem('crucix_space_display') || 'icons';
let terminalOutput = 'Ready. Live data is loaded from /api/data in server mode.';
let terminalBusy = false;
let currentRegion = 'world';
let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH;
@@ -1564,6 +1573,46 @@ function renderLower(){
document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}`;
}
async function runTerminalAction(action){
if(terminalBusy) return;
terminalBusy = true;
terminalOutput = `> ${action}\nRunning...`;
renderRight();
try{
const res = await fetch('/api/action', {
method:'POST',
headers:{
'Content-Type':'application/json',
...(localStorage.getItem('crucix_sweep_token') ? {'x-crucix-token': localStorage.getItem('crucix_sweep_token')} : {})
},
body:JSON.stringify({action})
});
const payload = await res.json().catch(()=>({error:'Invalid server response'}));
if(!res.ok) throw new Error(payload.error || `HTTP ${res.status}`);
if(action === 'status'){
const h = payload.health || {};
terminalOutput = [
'> status',
`State: ${h.status || '--'}`,
`Last sweep: ${h.lastSuccessfulSweep || h.lastSweep || '--'}`,
`Data age: ${h.dataAgeSeconds != null ? h.dataAgeSeconds + 's' : '--'}`,
`Sources: ${h.sourcesOk || 0} ok / ${h.sourcesDegraded || 0} degraded / ${h.sourcesFailed || 0} failed`,
`LLM: ${h.llm?.state || '--'}`,
`Sweep active: ${h.sweepInProgress ? 'yes' : 'no'}`
].join('\n');
}else if(action === 'brief'){
terminalOutput = `> brief\n${payload.text || 'No briefing text returned.'}`;
}else if(action === 'sweep'){
terminalOutput = `> sweep\n${payload.status === 'already_running' ? 'Sweep already running.' : 'Sweep accepted. The dashboard will update when the sweep finishes.'}`;
}
}catch(err){
terminalOutput = `> ${action}\nERROR: ${err.message}`;
}finally{
terminalBusy = false;
renderRight();
}
}
// === RIGHT RAIL ===
function renderRight(){
const mobile = isMobileLayout();
@@ -1605,6 +1654,15 @@ function renderRight(){
const deltaHtml = hasDelta ? deltaRows.join('') : `<div style="padding:12px;text-align:center;color:var(--dim);font-family:var(--mono);font-size:10px">${t('delta.noChanges','No changes since last sweep')}</div>`;
document.getElementById('rightRail').innerHTML=`
<div class="g-panel right-actions">
<div class="sec-head"><h3>Terminal Actions</h3><span class="badge">${terminalBusy?'RUNNING':'READY'}</span></div>
<div class="action-grid">
<button class="action-btn" ${terminalBusy?'disabled':''} onclick="runTerminalAction('status')">Status</button>
<button class="action-btn" ${terminalBusy?'disabled':''} onclick="runTerminalAction('sweep')">Sweep</button>
<button class="action-btn" ${terminalBusy?'disabled':''} onclick="runTerminalAction('brief')">Brief</button>
</div>
<div class="terminal-output">${terminalOutput.replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])).replace(/\n/g,'<br>')}</div>
</div>
<div class="g-panel right-signals">
<div class="sec-head"><h3>${t('panels.crossSourceSignals','Cross-Source Signals')}</h3><span class="badge">${t('badges.worldview','WORLDVIEW')}</span></div>
${signals}
@@ -1839,10 +1897,10 @@ document.addEventListener('DOMContentLoaded', () => {
const hasInlineData = !!(D && D.meta);
const canProbeApi = location.protocol !== 'file:';
if (canProbeApi && !hasInlineData) {
if (canProbeApi) {
// Server mode: always fetch live data from API (ignore any stale inline D)
fetch('/api/data')
.then(r => r.json())
.then(r => { if(!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
.then(data => { D = data; init(); connectSSE(); })
.catch(() => {
// Should not reach here — server routes to loading.html when no data