NETWORK DEVICES
@@ -1988,6 +2087,7 @@ function loadTab(tab) {
backups: loadBackups,
dashboard: loadDashboard,
agents: loadAgents,
+ workers: loadWorkers,
network: ()=>{ loadNetwork(); _netAutoRefresh = setInterval(loadNetwork, 30000); },
alerts: loadAlerts,
facts: ()=>{ loadFactCategories(); loadFacts(); },
@@ -2016,6 +2116,99 @@ function loadTab(tab) {
function initApp() { loadDashboard(); setInterval(loadDashboard, 15000); }
// ── DASHBOARD ─────────────────────────────────────────────────────────────────
+
+// ── WORKERS ───────────────────────────────────────────────────────────────────
+const CRON_DEFS = [
+ {id:'facts_collector', label:'Facts Collector', schedule:'Every 3 min', host:'jarvis-do'},
+ {id:'stats_cache', label:'Stats Cache', schedule:'Every 5 min', host:'jarvis-do'},
+ {id:'calendar_sync', label:'Calendar Sync', schedule:'Every 15 min', host:'jarvis-do'},
+ {id:'jarvis_deploy', label:'Deploy Runner', schedule:'Every 1 min', host:'jarvis-do'},
+ {id:'jarvis_watchdog', label:'Watchdog', schedule:'Every 5 min', host:'jarvis-do'},
+ {id:'jarvis_backup', label:'JARVIS Backup', schedule:'Daily 2am', host:'jarvis-do', norun:true},
+ {id:'do_server_backup',label:'DO Server Backup', schedule:'Weekly Sun 4am', host:'jarvis-do', norun:true},
+];
+function wBtn(col) {
+ const c={cyan:'var(--cyan)',red:'var(--red)',green:'var(--green)',dim:'var(--dim)'}[col]||'var(--dim)';
+ return `background:none;border:1px solid ${c};color:${c};padding:3px 8px;font-family:var(--font);font-size:0.55rem;letter-spacing:1px;cursor:pointer;border-radius:2px;margin-right:3px`;
+}
+function wAgo(ts) {
+ if (!ts) return 'UNKNOWN';
+ const d=new Date(ts.replace(' ','T')+(ts.includes('T')?'':'Z'));
+ const s=Math.floor((Date.now()-d)/1000);
+ if(isNaN(s)||s<0) return ts;
+ if(s<60) return s+'s ago';
+ if(s<3600) return Math.floor(s/60)+'m ago';
+ if(s<86400) return Math.floor(s/3600)+'h ago';
+ return Math.floor(s/86400)+'d ago';
+}
+function wToast(msg,err=false) {
+ let t=document.getElementById('w-toast');
+ if(!t){t=document.createElement('div');t.id='w-toast';
+ t.style.cssText='position:fixed;bottom:24px;right:24px;padding:10px 18px;border-radius:4px;font-size:0.65rem;letter-spacing:1px;z-index:9999;transition:opacity 0.5s';
+ document.body.appendChild(t);}
+ t.style.background=err?'rgba(255,34,68,0.12)':'rgba(0,212,255,0.12)';
+ t.style.border=err?'1px solid var(--red)':'1px solid var(--cyan)';
+ t.style.color=err?'var(--red)':'var(--cyan)';
+ t.style.opacity='1';t.textContent=msg;
+ setTimeout(()=>{t.style.opacity='0';},3000);
+}
+async function workerAction(type,id,action) {
+ const res=await api('worker_action',{worker_type:type,worker_id:id,action});
+ if(res&&res.ok){wToast(res.msg||'Done');setTimeout(loadWorkers,2500);}
+ else wToast((res&&res.error)||'Action failed',true);
+}
+async function loadWorkers() {
+ const d=await api('workers_list');
+ if(!d||d.error) return;
+ // Field Agents
+ const agTbody=document.getElementById('workers-agents');
+ if(!d.agents||!d.agents.length){
+ agTbody.innerHTML='
| NO AGENTS |
';
+ } else {
+ agTbody.innerHTML=d.agents.map(ag=>{
+ const on=ag.status==='online';
+ const dot=``;
+ const caps=JSON.parse(ag.capabilities||'[]');
+ const capHtml=caps.map(c=>{
+ const col=c==='screenshot'?'var(--cyan)':c==='proxmox'?'var(--orange)':c==='docker'?'var(--green)':c==='ollama'?'var(--yellow)':'rgba(200,230,255,0.3)';
+ return `${c.toUpperCase()}`;
+ }).join('');
+ const shotBtn=caps.includes('screenshot')?``:'';
+ return `
+ | ${dot}${ag.hostname} |
+ ${ag.agent_type||'linux'} |
+ ${ag.ip_address||'—'} |
+ ${dot}${on?'ONLINE':'OFFLINE'} |
+ ${capHtml||'—'} |
+ ${wAgo(ag.last_seen)} |
+ ${shotBtn} |
+
`;
+ }).join('');
+ }
+ // Cron Workers
+ const cl=d.cron_last||{};
+ document.getElementById('workers-crons').innerHTML=CRON_DEFS.map(c=>{
+ const runBtn=!c.norun?``:'—';
+ return `
+ | ${c.label} |
+ ${c.schedule} |
+ ${c.host} |
+ ${wAgo(cl[c.id])} |
+ ${runBtn} |
+
`;
+ }).join('');
+ // Daemons
+ const r=d.reactor,ron=r&&!r.error;
+ const rdot=``;
+ const rinfo=ron?`${r.handlers||'?'} handlers · ${(d.arc_counts||{}).done||0} jobs done (24h) · ${(d.arc_counts||{}).failed||0} failed`:'Not responding on :7474';
+ document.getElementById('workers-daemons').innerHTML=`
+ | ${rdot}Arc Reactor |
+ jarvis-do :7474 |
+ ${rdot}${ron?'ONLINE':'OFFLINE'} |
+ ${rinfo} |
+ |
+
`;
+}
async function loadDashboard() {
const d = await api('dashboard');
const s = d.sys;