From 950749323c04b5d1eeec030878f58b7bcef607f7 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Thu, 11 Jun 2026 21:31:50 +0000 Subject: [PATCH] =?UTF-8?q?admin:=20Workers=20page=20=E2=80=94=20consolida?= =?UTF-8?q?ted=20view=20of=20all=20JARVIS=20Agent=20Workers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single tab showing field agents (capabilities, status, last seen, update/screenshot actions), cron workers (schedule, last run, run-now button), and Arc Reactor daemon (handler count, 24h job stats, restart button). wToast for action feedback. --- public_html/admin/index.php | 193 ++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/public_html/admin/index.php b/public_html/admin/index.php index 43dfe23..ce1e171 100644 --- a/public_html/admin/index.php +++ b/public_html/admin/index.php @@ -464,6 +464,86 @@ if ($action) { // ── ARC REACTOR ────────────────────────────────────────────────────── + + case 'workers_list': + $agents = JarvisDB::query( + 'SELECT agent_id, hostname, agent_type, ip_address, status, capabilities, last_seen + FROM registered_agents ORDER BY status DESC, hostname ASC' + ); + $reactorRaw = @file_get_contents('http://127.0.0.1:7474/status'); + $reactor = $reactorRaw ? json_decode($reactorRaw, true) : null; + $arcStats = JarvisDB::query( + 'SELECT status, COUNT(*) as cnt FROM arc_jobs + WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY status' + ); + $arcCounts = []; + foreach ($arcStats as $r) $arcCounts[$r['status']] = (int)$r['cnt']; + $cronLast = []; + $cronLog = '/home/jarvis.orbishosting.com/logs/cron.log'; + if (file_exists($cronLog)) { + $lines = array_filter(explode("\n", shell_exec("grep -a 'facts\\|stats\\|calendar' " . escapeshellarg($cronLog) . " | tail -60"))); + foreach ($lines as $line) { + if (preg_match('/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\].*facts/i', $line, $m)) $cronLast['facts_collector'] = $m[1]; + if (preg_match('/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\].*stats/i', $line, $m)) $cronLast['stats_cache'] = $m[1]; + if (preg_match('/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\].*calendar/i', $line, $m)) $cronLast['calendar_sync'] = $m[1]; + } + } + if (empty($cronLast['stats_cache'])) { + $row = JarvisDB::query('SELECT MAX(updated_at) as t FROM api_cache WHERE cache_key IN ("weather","news")'); + if (!empty($row[0]['t'])) $cronLast['stats_cache'] = $row[0]['t']; + } + $deployLog = '/home/jarvis.orbishosting.com/logs/deploy.log'; + if (file_exists($deployLog)) { + $last = shell_exec("grep -a '\\[' " . escapeshellarg($deployLog) . " | tail -1"); + if (preg_match('/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\]/', trim($last), $m)) $cronLast['jarvis_deploy'] = $m[1]; + } + $wdLog = '/home/jarvis.orbishosting.com/logs/watchdog.log'; + if (file_exists($wdLog)) $cronLast['jarvis_watchdog'] = date('Y-m-d H:i:s', filemtime($wdLog)); + $bkLog = '/var/backups/jarvis/backup.log'; + if (file_exists($bkLog)) { + $last = shell_exec("grep -a '\\[' " . escapeshellarg($bkLog) . " | tail -1"); + if (preg_match('/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\]/', trim($last), $m)) $cronLast['jarvis_backup'] = $m[1]; + } + $doLog = '/var/log/do-server-backup.log'; + if (file_exists($doLog)) $cronLast['do_server_backup'] = date('Y-m-d H:i:s', filemtime($doLog)); + j(['agents'=>$agents,'reactor'=>$reactor,'arc_counts'=>$arcCounts,'cron_last'=>$cronLast]); + break; + + case 'worker_action': + $wType = $data['worker_type'] ?? ''; + $wId = $data['worker_id'] ?? ''; + $wAction = $data['action'] ?? ''; + if ($wType === 'agent' && $wAction === 'update') { + JarvisDB::execute('INSERT INTO agent_commands (agent_id,command_type,command_data,status) VALUES (?,?,?,?)', + [$wId,'update','{}','pending']); + j(['ok'=>true,'msg'=>'Update dispatched to '.$wId]); + } elseif ($wType === 'agent' && $wAction === 'screenshot') { + $ch = curl_init('http://127.0.0.1:7474/job'); + curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true, + CURLOPT_POSTFIELDS=>json_encode(['type'=>'screenshot','payload'=>['agent'=>$wId,'analyze'=>false],'priority'=>8,'created_by'=>'admin:workers']), + CURLOPT_HTTPHEADER=>['Content-Type: application/json'],CURLOPT_TIMEOUT=>5]); + j(json_decode(curl_exec($ch),true)?:['error'=>'reactor unreachable']); + } elseif ($wType === 'cron' && $wAction === 'run') { + $scripts = [ + 'facts_collector'=>[true, '/home/jarvis.orbishosting.com/api/endpoints/facts_collector.php'], + 'stats_cache' =>[true, '/home/jarvis.orbishosting.com/api/endpoints/stats_cache.php'], + 'calendar_sync' =>[true, '/home/jarvis.orbishosting.com/api/endpoints/calendar_sync.php'], + 'jarvis_deploy' =>[false,'/usr/local/bin/jarvis-deploy.sh'], + 'jarvis_watchdog'=>[false,'/usr/local/bin/jarvis-watchdog.sh'], + ]; + if (isset($scripts[$wId])) { + [$isPhp,$path] = $scripts[$wId]; + $cmd = $isPhp + ? '/usr/local/lsws/lsphp85/bin/lsphp '.escapeshellarg($path).' >> /home/jarvis.orbishosting.com/logs/cron.log 2>&1 &' + : escapeshellcmd($path).' >> /home/jarvis.orbishosting.com/logs/deploy.log 2>&1 &'; + shell_exec($cmd); + j(['ok'=>true,'msg'=>ucwords(str_replace('_',' ',$wId)).' triggered']); + } else { bad('Unknown cron worker'); } + } elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'restart') { + shell_exec('pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &'); + j(['ok'=>true,'msg'=>'Arc Reactor restarting']); + } else { bad('Invalid worker action'); } + break; case 'arc_status': $ch = curl_init('http://127.0.0.1:7474/status'); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>5, CURLOPT_CONNECTTIMEOUT=>3]); @@ -1197,6 +1277,7 @@ select.filter-sel:focus{border-color:var(--cyan)} + @@ -1244,6 +1325,24 @@ select.filter-sel:focus{border-color:var(--cyan)} + +
+
⚙ JARVIS AGENT WORKERS + +
+
FIELD AGENTS
+ + +
HOSTNAMETYPEIPSTATUSCAPABILITIESLAST SEENACTIONS
LOADING...
+
CRON WORKERS
+ + +
WORKERSCHEDULEHOSTLAST RUNACTIONS
+
DAEMONS
+ + +
DAEMONHOSTSTATUSINFOACTIONS
+
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;