mirror of
https://github.com/myronblair/jarvis
synced 2026-07-28 00:34:35 -05:00
feat(admin): Arc Reactor SETUP button — live log popup
- Replace arcSetup() confirm/toast with openModal() live popup - Add arc_setup_log PHP backend (polls /var/log/jarvis/arc-setup.log) - Color-coded line output: green=ok/done/active, red=error/fail, yellow=warn - Auto-detects completion keywords and updates status header - CLOSE button stops polling and refreshes worker table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014p87VFec84hNaf2WpvmLrW
This commit is contained in:
+66
-10
@@ -589,6 +589,14 @@ if ($action) {
|
||||
}
|
||||
j(['lines'=>$out, 'next_line'=>$total]);
|
||||
|
||||
case 'arc_setup_log':
|
||||
$logFile = '/var/log/jarvis/arc-setup.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);
|
||||
if (!empty($_GET['snapshot'])) { j(['next_line'=>$total]); }
|
||||
j(['lines'=>array_values(array_slice($allLines, $since)), '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]);
|
||||
@@ -2327,18 +2335,66 @@ async function runIntentGenerator() {
|
||||
}
|
||||
|
||||
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';
|
||||
const modalHtml = `
|
||||
<div style="font-family:var(--mono);font-size:0.72rem;line-height:1.8">
|
||||
<div id="as-status" style="color:var(--cyan);margin-bottom:8px;font-size:0.65rem;letter-spacing:1px">LAUNCHING...</div>
|
||||
<div id="as-log" style="background:#060a0e;border:1px solid var(--border);border-radius:3px;padding:10px 12px;min-height:160px;max-height:360px;overflow-y:auto;white-space:pre-wrap;font-size:0.62rem;line-height:1.7;color:var(--text-dim)">Waiting for output...</div>
|
||||
</div>`;
|
||||
|
||||
openModal("⚙ ARC REACTOR SETUP", modalHtml, null, null);
|
||||
document.getElementById("modalSave").textContent = "CLOSE";
|
||||
let _stop = false;
|
||||
document.getElementById("modalSave").onclick = () => { _stop = true; closeModal(); loadWorkers(); };
|
||||
document.getElementById("modalClose").onclick = () => { _stop = true; closeModal(); loadWorkers(); };
|
||||
|
||||
const logEl = document.getElementById("as-log");
|
||||
const statEl = document.getElementById("as-status");
|
||||
|
||||
const snap = await api("arc_setup_log", {snapshot:1});
|
||||
let lastLine = snap?.next_line ?? 0;
|
||||
|
||||
const kick = await api("worker", {type:"daemon", id:"arc_reactor", action:"setup"});
|
||||
if (!kick?.ok) {
|
||||
statEl.style.color = "var(--red)";
|
||||
statEl.textContent = "LAUNCH FAILED: " + (kick?.msg || kick?.error || "unknown");
|
||||
return;
|
||||
}
|
||||
statEl.textContent = "RUNNING — polling every 2s...";
|
||||
|
||||
async function pollLog() {
|
||||
if (_stop) return;
|
||||
try {
|
||||
const res = await api("arc_setup_log", {since: lastLine});
|
||||
if (res?.lines?.length) {
|
||||
lastLine = res.next_line;
|
||||
if (logEl.textContent === "Waiting for output...") logEl.textContent = "";
|
||||
res.lines.forEach(line => {
|
||||
const d = document.createElement("div");
|
||||
const l = line.toLowerCase();
|
||||
if (l.includes("error") || l.includes("fail")) d.style.color = "var(--red)";
|
||||
else if (l.includes("warn") || l.includes("skip")) d.style.color = "var(--yellow)";
|
||||
else if (l.includes("ok") || l.includes("done") || l.includes("active") ||
|
||||
l.includes("success") || l.includes("restart") || l.includes("enabled")) d.style.color = "var(--green)";
|
||||
else d.style.color = "var(--text-dim)";
|
||||
d.textContent = line;
|
||||
logEl.appendChild(d);
|
||||
});
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
const joined = res.lines.join(" ").toLowerCase();
|
||||
if (joined.includes("systemctl restart") || joined.includes("active") ||
|
||||
joined.includes("done") || joined.includes("failed")) {
|
||||
statEl.style.color = (joined.includes("error") || joined.includes("fail")) ? "var(--red)" : "var(--green)";
|
||||
statEl.textContent = (joined.includes("error") || joined.includes("fail")) ? "SETUP FAILED" : "SETUP COMPLETE";
|
||||
document.getElementById("modalSave").textContent = "CLOSE";
|
||||
loadWorkers(); return;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
setTimeout(pollLog, 2000);
|
||||
}
|
||||
setTimeout(pollLog, 1500);
|
||||
}
|
||||
|
||||
async function workerAction(type,id,action) {
|
||||
if (type === 'agent' && action === 'update') {
|
||||
await agentUpdateFlow(id);
|
||||
|
||||
Reference in New Issue
Block a user