diff --git a/public_html/admin/index.php b/public_html/admin/index.php index c4b4259..7c2cbe6 100644 --- a/public_html/admin/index.php +++ b/public_html/admin/index.php @@ -242,9 +242,7 @@ if ($action) { $search = strtolower(trim($_GET['search'] ?? '')); $skipDomains = ['sensor','binary_sensor','button','update','select','number', 'device_tracker','event','image','person','zone','tts','conversation', - 'assist_satellite','input_button','media_player','scene','water_heater', - 'alarm_control_panel','automation','script','calendar','notify', - 'weather','camera','siren','remote','todo','lawn_mower']; + 'assist_satellite','input_button']; $skipKeywords = ['pre_release','_record','_ftp_','_push_','_hub_ringtone', '_siren_on','_email_on','_manual_record','_infrared_', 'do_not_disturb','matter_server','zerotier','mariadb', @@ -489,25 +487,26 @@ if ($action) { $arcCounts = []; foreach ($arcStats as $r) $arcCounts[$r['status']] = (int)$r['cnt']; $cronLast = []; - $cronLog = '/home/jarvis.orbishosting.com/logs/cron.log'; + $cronLog = '/var/log/jarvis/cron.log'; if (file_exists($cronLog)) { - $lines = array_filter(explode("\n", shell_exec("grep -a 'facts\\|stats\\|calendar' " . escapeshellarg($cronLog) . " | tail -60"))); + $lines = array_filter(explode("\n", shell_exec("grep -a 'facts\\|stats\\|calendar\\|intent' " . 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 (preg_match('/^\\[(\\d{2}{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\].*calendar/i', $line, $m)) $cronLast['calendar_sync'] = $m[1]; + if (preg_match('/^\\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\\].*intent/i', $line, $m)) $cronLast['kb_intent_generator'] = $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'; + $deployLog = '/var/log/jarvis/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'; + $wdLog = '/var/log/jarvis/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)) { @@ -520,9 +519,9 @@ if ($action) { break; case 'worker_action': - $wType = $data['worker_type'] ?? ''; - $wId = $data['worker_id'] ?? ''; - $wAction = $data['action'] ?? ''; + $wType = $_REQUEST['worker_type'] ?? ''; + $wId = $_REQUEST['worker_id'] ?? ''; + $wAction = $_REQUEST['waction'] ?? $_REQUEST['action'] ?? ''; if ($wType === 'agent' && $wAction === 'update') { JarvisDB::execute('INSERT INTO agent_commands (agent_id,command_type,command_data,status) VALUES (?,?,?,?)', [$wId,'update','{}','pending']); @@ -535,39 +534,61 @@ if ($action) { 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'], + 'facts_collector' =>[true, '/var/www/jarvis/api/endpoints/facts_collector.php'], + 'stats_cache' =>[true, '/var/www/jarvis/api/endpoints/stats_cache.php'], + 'calendar_sync' =>[true, '/var/www/jarvis/api/endpoints/calendar_sync.php'], + 'kb_intent_generator' =>[true, '/var/www/jarvis/api/endpoints/kb_intent_generator.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 &'; + ? '/usr/bin/php8.3 '.escapeshellarg($path).' >> /var/log/jarvis/cron.log 2>&1 &' + : escapeshellcmd($path).' >> /var/log/jarvis/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('bash -c "mkdir -p /home/jarvis.orbishosting.com/logs; systemctl restart jarvis-arc 2>/dev/null || (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']); + shell_exec('systemctl restart jarvis-arc 2>&1'); + k(['ok'=>true,'msg'=>'Arc Reactor restarting via systemd']); } elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'setup') { - $setupLog = '/home/jarvis.orbishosting.com/logs/arc_reactor.log'; - $cmd = 'bash -c "'. - 'mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs && '. - 'cp /var/www/jarvis/deploy/reactor.py /opt/jarvis-arc/reactor.py && '. - 'cp /var/www/jarvis/deploy/requirements.txt /opt/jarvis-arc/requirements.txt && '. - 'if [ ! -d /opt/jarvis-arc/venv ]; then python3 -m venv /opt/jarvis-arc/venv; fi && '. - '/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt && '. - 'pkill -f reactor.py 2>/dev/null; sleep 1 && '. - 'cd /opt/jarvis-arc && source venv/bin/activate && '. - 'nohup python3 reactor.py >> '.$setupLog.' 2>&1 &'. - '" >> '.$setupLog.' 2>&1'; - shell_exec($cmd); - j(['ok'=>true,'msg'=>'Arc Reactor setup started — check log in ~30s then restart']); + $log = '/var/log/jarvis/arc-setup.log'; + $cmd = implode(' && ', [ + 'mkdir -p /opt/jarvis-arc /var/log/jarvis', + 'cp /var/www/jarvis/deploy/reactor.py /opt/jarvis-arc/reactor.py', + 'cp /var/www/jarvis/deploy/requirements.txt /opt/jarvis-arc/requirements.txt', + '[ ! -f /opt/jarvis-arc/venv/bin/activate ] && python3 -m venv /opt/jarvis-arc/venv || true', + '/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt', + 'cp /var/www/jarvis/deploy/jarvis-arc.service /etc/systemd/system/jarvis-arc.service', + 'systemctl daemon-reload', + 'systemctl enable jarvis-arc', + 'systemctl restart jarvis-arc', + ]); + shell_exec("($cmd) >> " . escapeshellarg($log) . " 2>&1 &"); + k(['ok'=>true,'msg'=>'Arc Reactor setup started — check ' . $log]); + } elseif ($wType === 'agent' && $wAction === 'update_status') { + $ag = JarvisDB::single('SELECT version, status FROM registered_agents WHERE agent_id=?', [$wId]); + j(['ok'=>true,'version'=>$ag['version']??null,'status'=>$ag['status']??'unknown']); } else { bad('Invalid worker action'); } break; + case 'intent_gen_log': + $logFile = '/var/log/jarvis/cron.log'; + $since = max(0, (int)($_GET['since'] ?? 0)); + if (!file_exists($logFile)) { j(['lines'=>[],'next_line'=>0]); } + $allLines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $total = count($allLines); + // snapshot=1 just returns the current line count (call BEFORE triggering run) + if (!empty($_GET['snapshot'])) { j(['next_line'=>$total]); } + // Return only KB Intent Generator lines from position $since onward + $out = []; + for ($i = $since; $i < $total; $i++) { + if (stripos($allLines[$i], 'KB Intent Generator') !== false) { + $out[] = $allLines[$i]; + } + } + j(['lines'=>$out, 'next_line'=>$total]); + 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]); @@ -1442,6 +1463,7 @@ select.filter-sel:focus{border-color:var(--cyan)} +
@@ -2174,7 +2196,8 @@ const CRON_DEFS = [ {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}, + {id:'do_server_backup', label:'DO Server Backup', schedule:'Weekly Sun 4am', host:'jarvis-do', norun:true}, + {id:'kb_intent_generator', label:'KB Intent Generator', schedule:'Daily 3am', host:'jarvis'}, ]; function wBtn(col) { const c={cyan:'var(--cyan)',red:'var(--red)',green:'var(--green)',dim:'var(--dim)'}[col]||'var(--dim)'; @@ -2201,12 +2224,127 @@ function wToast(msg,err=false) { t.style.opacity='1';t.textContent=msg; setTimeout(()=>{t.style.opacity='0';},3000); } +async function runIntentGenerator() { + // Open the working popup + const modalHtml = ` +
+
LAUNCHING...
+
Waiting for first output...
+
+
`; + + openModal('▶ KB INTENT GENERATOR', modalHtml, null, null); + document.getElementById('modalSave').textContent = 'CLOSE'; + document.getElementById('modalSave').onclick = () => { _igStop = true; closeModal(); }; + + const logEl = document.getElementById('ig-log'); + const statEl = document.getElementById('ig-status'); + const statsEl = document.getElementById('ig-stats'); + + let _igStop = false; + let lastLine = 0; + let dotted = 0; + + // Snapshot current log position BEFORE triggering so we only see new lines + const snap = await api('intent_gen_log', {snapshot:1}); + lastLine = snap?.next_line ?? 0; + + // Kick off the generator (fire-and-forget in background on server) + const kick = await api('worker_action', {worker_type:'cron', worker_id:'kb_intent_generator', waction:'run'}); + if (!kick || !kick.ok) { + statEl.style.color = 'var(--red)'; + statEl.textContent = 'LAUNCH FAILED: ' + (kick?.error || 'unknown error'); + document.getElementById('modalSave').textContent = 'CLOSE'; + return; + } + statEl.textContent = 'RUNNING — polling log every 3s...'; + + // Poll the cron log for new output + async function pollLog() { + if (_igStop) return; + try { + const res = await api('intent_gen_log', {since: lastLine}); + if (res && Array.isArray(res.lines) && res.lines.length) { + lastLine = res.next_line; + const isFirst = logEl.textContent === 'Waiting for first output...'; + if (isFirst) logEl.textContent = ''; + res.lines.forEach(line => { + const div = document.createElement('div'); + const lower = line.toLowerCase(); + if (lower.includes('error') || lower.includes('fail')) + div.style.color = 'var(--red)'; + else if (lower.includes('skip') || lower.includes('warn')) + div.style.color = 'var(--yellow)'; + else if (lower.includes('insert') || lower.includes('added') || lower.includes('done') || lower.includes('complete') || lower.includes('cleanup')) + div.style.color = 'var(--green)'; + else + div.style.color = 'var(--text-dim)'; + div.textContent = line; + logEl.appendChild(div); + }); + logEl.scrollTop = logEl.scrollHeight; + + // Detect completion + const lastLines = res.lines.join(' ').toLowerCase(); + if (lastLines.includes('kb intent generator: done') || lastLines.includes('total inserted') || lastLines.includes('cleanup complete')) { + statEl.style.color = 'var(--green)'; + statEl.textContent = 'COMPLETE'; + // Extract stats from log + const total = (res.lines.join('\n').match(/inserted[:\s]+(\d+)/i) || [])[1]; + if (total) statsEl.innerHTML = `+${total} intents added`; + return; // stop polling + } + dotted = 0; + } else { + // No new lines yet — show a waiting dot + dotted++; + if (dotted < 30) { // Stop waiting after ~90s with no output + const waitDiv = document.createElement('div'); + waitDiv.style.color = 'rgba(0,212,255,0.3)'; + waitDiv.textContent = '· waiting for output' + '.'.repeat(dotted % 4); + // Replace last waiting line instead of appending + const last = logEl.lastChild; + if (last && last.textContent && last.textContent.startsWith('· waiting')) { + logEl.replaceChild(waitDiv, last); + } else { + if (logEl.textContent === 'Waiting for first output...') logEl.textContent = ''; + logEl.appendChild(waitDiv); + } + logEl.scrollTop = logEl.scrollHeight; + } else { + statEl.style.color = 'var(--yellow)'; + statEl.textContent = 'RUNNING IN BACKGROUND (check cron log for results)'; + return; + } + } + } catch(e) { + // network error — keep trying + } + if (!_igStop) setTimeout(pollLog, 3000); + } + + setTimeout(pollLog, 2000); // give server 2s head-start +} + +async function arcSetup() { + if (!confirm('Run Arc Reactor setup?\n\nThis will:\n• Copy reactor.py from deploy/\n• Install Python packages\n• Install/update systemd service\n• Restart jarvis-arc')) return; + const btn = event.currentTarget; + btn.disabled = true; btn.textContent = '⏳ SETTING UP...'; + try { + const d = await api('worker', {type:'daemon', id:'arc_reactor', action:'setup'}); + toast(d.msg || (d.ok ? 'Setup started' : 'Setup failed'), d.ok ? 'ok' : 'err'); + setTimeout(() => { loadWorkers(); btn.disabled=false; btn.innerHTML='⚙ SETUP'; }, 5000); + } catch(e) { + toast('Setup failed: ' + e.message, 'err'); + btn.disabled=false; btn.innerHTML='⚙ SETUP'; + } +} async function workerAction(type,id,action) { if (type === 'agent' && action === 'update') { await agentUpdateFlow(id); return; } - const res=await api('worker_action',{worker_type:type,worker_id:id,action}); + const res=await api('worker_action',{worker_type:type,worker_id:id,waction:action}); if(res&&res.ok){wToast(res.msg||'Done');setTimeout(loadWorkers,2500);} else wToast((res&&res.error)||'Action failed',true); } @@ -2231,7 +2369,7 @@ async function agentUpdateFlow(agentId) { }; // Dispatch command - const res = await api('worker_action', {worker_type:'agent', worker_id:agentId, action:'update'}); + const res = await api('worker_action', {worker_type:'agent', worker_id:agentId, waction:'update'}); if (!res || !res.ok) { log('✗ Failed to dispatch: ' + (res?.error||'unknown'), 'var(--red)'); document.getElementById('modalSave').style.display = ''; @@ -2242,7 +2380,7 @@ async function agentUpdateFlow(agentId) { log('✓ Command dispatched — waiting for agent to pick up...', 'var(--cyan)'); // Poll agent_commands for the result (max 90s) - const cmdRes = await api('worker_action', {worker_type:'agent', worker_id:agentId, action:'update_status'}).catch(()=>null); + const cmdRes = await api('worker_action', {worker_type:'agent', worker_id:agentId, waction:'update_status'}).catch(()=>null); // Actually poll via workers_list for version change const deadline = Date.now() + 90000; let done = false; @@ -2346,9 +2484,9 @@ async function loadWorkers() { jarvis-do :7474 ${rdot}${ron?'ONLINE':'OFFLINE'} ${rinfo} - + - + `; }