diff --git a/public_html/admin/index.php b/public_html/admin/index.php
index 0421920..6da58d0 100644
--- a/public_html/admin/index.php
+++ b/public_html/admin/index.php
@@ -4328,6 +4328,69 @@ async function clearanceRuleCreate() {
} catch(e) { toast('Failed', 'err'); }
}
+// ── ARC REACTOR ──────────────────────────────────────────────────────────────
+async function loadArc() {
+ const tbl = document.getElementById('arc-jobs-tbl');
+ if (!tbl) return;
+ tbl.innerHTML = '
LOADING...
';
+
+ const status = document.getElementById('arc-job-filter')?.value || '';
+ const [s, jobs] = await Promise.all([
+ api('arc_status'),
+ api('arc_jobs', {status, limit: 100}),
+ ]);
+
+ // status bar
+ const online = s?.online;
+ document.getElementById('arc-status-val').textContent = online ? '● ONLINE' : '○ OFFLINE';
+ document.getElementById('arc-status-val').style.color = online ? 'var(--green)' : 'var(--red)';
+ document.getElementById('arc-version-val').textContent = s?.version || '—';
+ document.getElementById('arc-done-val').textContent = s?.jobs_done ?? s?.stats?.done ?? '—';
+ document.getElementById('arc-fail-val').textContent = s?.jobs_failed ?? s?.stats?.failed ?? '—';
+ document.getElementById('arc-hb-val').textContent = s?.last_heartbeat ? ts(s.last_heartbeat) : (online ? 'ALIVE' : '—');
+ document.getElementById('arc-caps-val').textContent = Array.isArray(s?.capabilities) ? s.capabilities.join(' · ') : (s?.capabilities || '—');
+
+ const list = Array.isArray(jobs) ? jobs : (jobs?.jobs || []);
+ if (!list.length) {
+ tbl.innerHTML = 'No jobs found.
';
+ return;
+ }
+
+ const STATUS_COLOR = {queued:'var(--cyan)',running:'var(--yellow)',done:'var(--green)',failed:'var(--red)',cancelled:'var(--dim)'};
+ const rows = list.map(j => {
+ const sc = STATUS_COLOR[j.status] || 'var(--text)';
+ return `
+ | #${j.id} |
+ ${esc(j.status||'').toUpperCase()} |
+ ${esc(j.type||'—')} |
+ ${esc(j.created_by||'—')} |
+ ${j.result ? esc(JSON.stringify(j.result).substring(0,120)) : '—'} |
+ ${ts(j.created_at)} |
+
`;
+ }).join('');
+
+ tbl.innerHTML = `
+ | ID | STATUS | TYPE | BY | RESULT | CREATED |
+
${rows}
`;
+}
+
+async function arcTestPing() {
+ const d = await api('arc_ping');
+ if (d?.job_id || d?.id) {
+ toast('Ping job queued — ID #' + (d.job_id || d.id), 'ok');
+ setTimeout(loadArc, 1200);
+ } else {
+ toast('Ping failed: ' + (d?.error || 'no response'), 'err');
+ }
+}
+
+async function arcPurge() {
+ if (!confirm('Purge completed/failed jobs older than 24h?')) return;
+ const d = await api('arc_purge');
+ toast(d?.purged != null ? `Purged ${d.purged} jobs` : 'Purge complete', 'ok');
+ loadArc();
+}
+