mirror of
https://github.com/myronblair/jarvis
synced 2026-07-28 00:34:35 -05:00
Fix Works-tab last-run detection bugs, add generic live-log popup for Facts/Stats/Calendar workers, fix webhook branch check (master not main) and log path
This commit is contained in:
+176
-4
@@ -491,9 +491,9 @@ if ($action) {
|
||||
if (file_exists($cronLog)) {
|
||||
$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{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})\\]?.*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{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\\].*intent/i', $line, $m)) $cronLast['kb_intent_generator'] = $m[1];
|
||||
}
|
||||
}
|
||||
@@ -616,6 +616,56 @@ if ($action) {
|
||||
}
|
||||
j(['lines'=>$out, 'next_line'=>$total]);
|
||||
|
||||
// ── GENERIC WORKER LOG (live-popup support for Facts/Stats/Calendar) ────────
|
||||
case 'worker_log':
|
||||
$wKeywords = [
|
||||
'facts_collector' => '/facts|system:|network:|proxmox:|ha:|do_server:|ollama:|sites:|nmap_scan:/i',
|
||||
'stats_cache' => '/\[cache\]/i',
|
||||
'calendar_sync' => '/calendar/i',
|
||||
];
|
||||
$wid = $_REQUEST['worker_id'] ?? '';
|
||||
if (!isset($wKeywords[$wid])) { bad('Unknown worker'); }
|
||||
$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);
|
||||
if (!empty($_GET['snapshot'])) { j(['next_line'=>$total]); }
|
||||
$out = [];
|
||||
for ($i = $since; $i < $total; $i++) {
|
||||
if (preg_match($wKeywords[$wid], $allLines[$i])) $out[] = $allLines[$i];
|
||||
}
|
||||
j(['lines'=>$out, 'next_line'=>$total]);
|
||||
|
||||
case 'worker_history':
|
||||
$wKeywords = [
|
||||
'facts_collector' => '/facts|system:|network:|proxmox:|ha:|do_server:|ollama:|sites:|nmap_scan:/i',
|
||||
'stats_cache' => '/\[cache\]/i',
|
||||
'calendar_sync' => '/calendar/i',
|
||||
];
|
||||
$wid = $_REQUEST['worker_id'] ?? '';
|
||||
if (!isset($wKeywords[$wid])) { bad('Unknown worker'); }
|
||||
$logFile = '/var/log/jarvis/cron.log';
|
||||
$result = ['last_success'=>null,'last_success_count'=>null,'failures'=>[]];
|
||||
if (file_exists($logFile)) {
|
||||
$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$cutoff = time() - 7 * 86400;
|
||||
foreach (array_reverse($lines) as $line) {
|
||||
if (!preg_match($wKeywords[$wid], $line)) continue;
|
||||
preg_match('/^\[?(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]?/', $line, $tm);
|
||||
$ts = isset($tm[1]) ? strtotime($tm[1]) : 0;
|
||||
if (!$result['last_success'] && preg_match('/done|complete|collected|synced/i', $line)) {
|
||||
$result['last_success'] = $tm[1] ?? null;
|
||||
}
|
||||
if ($ts >= $cutoff && preg_match('/error|fail/i', $line)) {
|
||||
$result['failures'][] = $line;
|
||||
if (count($result['failures']) >= 20) break;
|
||||
}
|
||||
}
|
||||
$result['failures'] = array_reverse($result['failures']);
|
||||
}
|
||||
j($result);
|
||||
|
||||
// ── KB GENERATOR TOPICS ─────────────────────────────────────────────
|
||||
case 'kb_topics':
|
||||
$where = ['1=1']; $params = [];
|
||||
@@ -2461,6 +2511,114 @@ async function runIntentGenerator() {
|
||||
setTimeout(pollLog, 2000);
|
||||
}
|
||||
|
||||
// Generic live-log popup for simple cron workers (Facts Collector, Stats Cache, Calendar Sync).
|
||||
// Same pattern as runIntentGenerator() above, parameterized instead of duplicated per worker.
|
||||
async function runWorkerWithLog(workerId, label, doneRegex) {
|
||||
const modalHtml = `
|
||||
<div style="font-family:var(--mono);font-size:0.72rem;line-height:1.8">
|
||||
<div id="wl-history" style="background:#0a0f14;border:1px solid var(--border);border-radius:3px;padding:8px 12px;margin-bottom:10px;font-size:0.6rem;color:var(--text-dim)">Loading history...</div>
|
||||
<div id="wl-status" style="color:var(--cyan);margin-bottom:8px;font-size:0.65rem;letter-spacing:1px">LAUNCHING...</div>
|
||||
<div id="wl-log" style="background:#060a0e;border:1px solid var(--border);border-radius:3px;padding:10px 12px;min-height:160px;max-height:320px;overflow-y:auto;white-space:pre-wrap;font-size:0.62rem;line-height:1.7;color:var(--text-dim)">Waiting for first output...</div>
|
||||
</div>`;
|
||||
|
||||
openModal(`▶ ${label.toUpperCase()}`, modalHtml, null, null);
|
||||
document.getElementById('modalSave').textContent = 'CLOSE';
|
||||
|
||||
const logEl = document.getElementById('wl-log');
|
||||
const statEl = document.getElementById('wl-status');
|
||||
const historyEl = document.getElementById('wl-history');
|
||||
|
||||
let _stop = false, _done = false, lastLine = 0, dotted = 0;
|
||||
|
||||
const stopAndClose = () => {
|
||||
_stop = true;
|
||||
closeModal();
|
||||
if (!_done) wToast(`${label} running in background`);
|
||||
};
|
||||
document.getElementById('modalSave').onclick = stopAndClose;
|
||||
document.getElementById('modalClose').onclick = stopAndClose;
|
||||
|
||||
api('worker_history', {worker_id: workerId}).then(h => {
|
||||
if (!historyEl) return;
|
||||
let html = '';
|
||||
if (h?.last_success) html += `<span style="color:var(--green)">✓ Last success: ${h.last_success}</span>`;
|
||||
else html += '<span style="color:var(--text-dim)">No recorded successes in log</span>';
|
||||
if (h?.failures?.length) {
|
||||
html += `<br><span style="color:var(--red)">⚠ ${h.failures.length} failure line(s) in last 7 days:</span>`;
|
||||
h.failures.slice(-3).forEach(f => {
|
||||
html += `<div style="color:var(--red);opacity:0.7;margin-left:8px">${f.replace(/</g,'<')}</div>`;
|
||||
});
|
||||
}
|
||||
historyEl.innerHTML = html || '<span>No history found</span>';
|
||||
}).catch(() => { if (historyEl) historyEl.textContent = 'History unavailable'; });
|
||||
|
||||
const snap = await api('worker_log', {worker_id: workerId, snapshot:1});
|
||||
lastLine = snap?.next_line ?? 0;
|
||||
|
||||
const kick = await api('worker_action', {worker_type:'cron', worker_id: workerId, 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...';
|
||||
|
||||
async function pollLog() {
|
||||
if (_stop) return;
|
||||
try {
|
||||
const res = await api('worker_log', {worker_id: workerId, since: lastLine});
|
||||
if (res && Array.isArray(res.lines) && res.lines.length) {
|
||||
lastLine = res.next_line;
|
||||
if (logEl.textContent === 'Waiting for first output...') 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('done') || lower.includes('complete') || lower.includes('ok')) div.style.color = 'var(--green)';
|
||||
else div.style.color = 'var(--text-dim)';
|
||||
div.textContent = line;
|
||||
logEl.appendChild(div);
|
||||
});
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
|
||||
const joined = res.lines.join(' ').toLowerCase();
|
||||
if (doneRegex.test(joined)) {
|
||||
_done = true;
|
||||
statEl.style.color = 'var(--green)';
|
||||
statEl.textContent = 'COMPLETE';
|
||||
document.getElementById('modalSave').textContent = 'CLOSE';
|
||||
return;
|
||||
}
|
||||
dotted = 0;
|
||||
} else {
|
||||
dotted++;
|
||||
if (dotted < 30) {
|
||||
const waitDiv = document.createElement('div');
|
||||
waitDiv.style.color = 'rgba(0,212,255,0.3)';
|
||||
waitDiv.textContent = '· waiting for output' + '.'.repeat(dotted % 4);
|
||||
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) {}
|
||||
if (!_stop) setTimeout(pollLog, 3000);
|
||||
}
|
||||
|
||||
setTimeout(pollLog, 2000);
|
||||
}
|
||||
|
||||
let _topicsData = [];
|
||||
let _topicsCats = [];
|
||||
|
||||
@@ -2870,8 +3028,22 @@ async function loadWorkers() {
|
||||
}
|
||||
// Cron Workers
|
||||
const cl=d.cron_last||{};
|
||||
const LIVE_LOG_WORKERS = {
|
||||
facts_collector: /done|collected/,
|
||||
stats_cache: /done/,
|
||||
calendar_sync: /done/,
|
||||
};
|
||||
document.getElementById('workers-crons').innerHTML=CRON_DEFS.map(c=>{
|
||||
const runBtn=!c.norun?`<button onclick="workerAction('cron','${c.id}','run')" style="${wBtn('cyan')}">▶ RUN</button>`:'<span class="ts">—</span>';
|
||||
let runBtn;
|
||||
if (c.norun) {
|
||||
runBtn = '<span class="ts">—</span>';
|
||||
} else if (c.id === 'kb_intent_generator') {
|
||||
runBtn = `<button onclick="runIntentGenerator()" style="${wBtn('cyan')}">▶ RUN</button>`;
|
||||
} else if (LIVE_LOG_WORKERS[c.id]) {
|
||||
runBtn = `<button onclick="runWorkerWithLog('${c.id}','${c.label}',${LIVE_LOG_WORKERS[c.id]})" style="${wBtn('cyan')}">▶ RUN</button>`;
|
||||
} else {
|
||||
runBtn = `<button onclick="workerAction('cron','${c.id}','run')" style="${wBtn('cyan')}">▶ RUN</button>`;
|
||||
}
|
||||
return `<tr>
|
||||
<td><strong>${c.label}</strong></td>
|
||||
<td class="ts">${c.schedule}</td>
|
||||
|
||||
Reference in New Issue
Block a user