70 lines
5.5 KiB
Python
70 lines
5.5 KiB
Python
"""Dependency-free AIOS V2 web console assets.
|
|
|
|
Authentication and tenant identity are injected by the trusted reverse proxy.
|
|
The browser never selects a tenant and never calls OpenClaw or Hermes directly.
|
|
"""
|
|
|
|
CONSOLE_HTML = r'''<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<meta name="referrer" content="no-referrer">
|
|
<title>AIOS V2 Console</title>
|
|
<style>
|
|
:root{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:#152033;background:#f4f7fb}
|
|
*{box-sizing:border-box} body{margin:0} header{background:#111827;color:white;padding:18px 24px;display:flex;justify-content:space-between;align-items:center}
|
|
header h1{font-size:20px;margin:0} header span{font-size:12px;color:#cbd5e1}
|
|
main{padding:24px;max-width:1440px;margin:auto}.notice{background:#fff7ed;border:1px solid #fed7aa;padding:12px 14px;border-radius:10px;margin-bottom:18px;font-size:13px}
|
|
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-bottom:20px}.card,.panel{background:white;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
|
|
.card{padding:16px}.card b{display:block;font-size:28px;margin-top:8px}.card small{color:#64748b}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:16px}.panel{padding:16px;overflow:auto}.panel h2{font-size:16px;margin:0 0 12px}
|
|
table{width:100%;border-collapse:collapse;font-size:12px}th,td{text-align:left;border-bottom:1px solid #edf0f4;padding:8px;vertical-align:top}th{color:#64748b;font-weight:600}.state{font-weight:700}.ok{color:#047857}.bad{color:#b91c1c}.muted{color:#64748b}
|
|
button{border:0;border-radius:8px;padding:9px 13px;background:#2563eb;color:white;cursor:pointer}button:disabled{opacity:.5}.toolbar{display:flex;gap:8px;align-items:center;margin-bottom:16px}.error{color:#b91c1c;white-space:pre-wrap}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header><h1>AIOS V2 Control Plane</h1><span id="updated">Not loaded</span></header>
|
|
<main>
|
|
<div class="notice">Read-only candidate console. Identity and tenant context must be injected by the trusted reverse proxy. The console never calls OpenClaw or Hermes directly.</div>
|
|
<div class="toolbar"><button id="refresh">Refresh</button><span id="error" class="error"></span></div>
|
|
<section class="cards">
|
|
<div class="card"><small>Agents</small><b id="agentCount">—</b></div>
|
|
<div class="card"><small>Tasks</small><b id="taskCount">—</b></div>
|
|
<div class="card"><small>Audit events</small><b id="auditCount">—</b></div>
|
|
<div class="card"><small>Executions</small><b id="executionCount">—</b></div>
|
|
</section>
|
|
<section class="grid">
|
|
<div class="panel"><h2>Runtime health</h2><div id="runtime"></div></div>
|
|
<div class="panel"><h2>Tasks</h2><div id="tasks"></div></div>
|
|
<div class="panel"><h2>Agents</h2><div id="agents"></div></div>
|
|
<div class="panel"><h2>Execution evidence</h2><div id="executions"></div></div>
|
|
<div class="panel"><h2>Audit</h2><div id="audit"></div></div>
|
|
</section>
|
|
</main>
|
|
<script>
|
|
const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
const table=(cols,rows)=>`<table><thead><tr>${cols.map(c=>`<th>${esc(c[0])}</th>`).join('')}</tr></thead><tbody>${rows.map(r=>`<tr>${cols.map(c=>`<td>${esc(c[1](r))}</td>`).join('')}</tr>`).join('')}</tbody></table>`;
|
|
async function api(path){const r=await fetch(path,{credentials:'same-origin',headers:{'Accept':'application/json'}});const data=await r.json().catch(()=>({}));if(!r.ok)throw new Error(`${r.status} ${data.message||data.error||path}`);return data}
|
|
async function refresh(){
|
|
const btn=document.getElementById('refresh');btn.disabled=true;document.getElementById('error').textContent='';
|
|
try{
|
|
const [agents,tasks,audit,runtime,executions]=await Promise.all([
|
|
api('/api/v2/agents'),api('/api/v2/tasks'),api('/api/v2/audit'),api('/api/v2/runtime'),api('/api/v2/executions')
|
|
]);
|
|
document.getElementById('agentCount').textContent=agents.agents.length;
|
|
document.getElementById('taskCount').textContent=tasks.tasks.length;
|
|
document.getElementById('auditCount').textContent=audit.events.length;
|
|
document.getElementById('executionCount').textContent=executions.executions.length;
|
|
document.getElementById('agents').innerHTML=table([['ID',x=>x.agent_id],['Runtime',x=>x.runtime],['Enabled',x=>x.enabled]],agents.agents);
|
|
document.getElementById('tasks').innerHTML=table([['Task',x=>x.task_id],['Action',x=>x.action],['State',x=>x.state],['Rev',x=>x.revision]],tasks.tasks);
|
|
document.getElementById('audit').innerHTML=table([['Time',x=>x.created_at],['Action',x=>x.action],['Outcome',x=>x.outcome],['Resource',x=>x.resource_id]],audit.events.slice(-100).reverse());
|
|
document.getElementById('executions').innerHTML=table([['Executor',x=>x.executor],['Task',x=>x.task_id],['Status',x=>x.status],['Progress',x=>`${x.progress_completed}/${x.progress_total}`],['Commit',x=>x.last_commit||'']],executions.executions);
|
|
document.getElementById('runtime').innerHTML=table([['Runtime',x=>x.name],['Status',x=>x.status],['Details',x=>JSON.stringify(x.details)]],runtime.runtimes);
|
|
document.getElementById('updated').textContent=`Updated ${new Date().toLocaleString()}`;
|
|
}catch(e){document.getElementById('error').textContent=e.message||String(e)}finally{btn.disabled=false}
|
|
}
|
|
document.getElementById('refresh').addEventListener('click',refresh);refresh();setInterval(refresh,30000);
|
|
</script>
|
|
</body>
|
|
</html>'''
|