allowing logs to be read
This commit is contained in:
146
index.html
146
index.html
@@ -198,7 +198,6 @@
|
|||||||
|
|
||||||
.btn-row { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
|
.btn-row { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
|
||||||
|
|
||||||
/* kill button — shown only when running */
|
|
||||||
.kill-btn { display: none; }
|
.kill-btn { display: none; }
|
||||||
.kill-btn.visible { display: inline-flex; }
|
.kill-btn.visible { display: inline-flex; }
|
||||||
|
|
||||||
@@ -214,13 +213,40 @@
|
|||||||
|
|
||||||
/* ── LOG BOX ── */
|
/* ── LOG BOX ── */
|
||||||
.log-box {
|
.log-box {
|
||||||
background: #030810; border: 1px solid var(--border); color: #4ade80;
|
background: #030810;
|
||||||
padding: 1rem; height: 200px; overflow-y: auto; border-radius: 6px;
|
border: 1px solid var(--border);
|
||||||
font-size: 0.73rem; line-height: 1.6; font-family: 'Space Mono', monospace; margin-top: 0.5rem;
|
padding: 1rem;
|
||||||
|
height: 240px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.73rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: 'Space Mono', monospace;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
/* white-space keeps indentation; word-break prevents horizontal scroll */
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
/* default text colour — ANSI spans override this */
|
||||||
|
color: #8ba0b8;
|
||||||
}
|
}
|
||||||
.log-box::-webkit-scrollbar { width: 4px; }
|
.log-box::-webkit-scrollbar { width: 4px; }
|
||||||
.log-box::-webkit-scrollbar-thumb { background: var(--border-hi); border-radius: 2px; }
|
.log-box::-webkit-scrollbar-thumb { background: var(--border-hi); border-radius: 2px; }
|
||||||
|
|
||||||
|
/* Each line is a block-level span so they stack vertically */
|
||||||
|
.log-line { display: block; }
|
||||||
|
|
||||||
|
/* ── ANSI colour classes (set by BuildController.ansiToHtml) ── */
|
||||||
|
.ansi-bold { font-weight: 700; }
|
||||||
|
.ansi-dim { opacity: 0.55; }
|
||||||
|
.ansi-red { color: #f14c4c; }
|
||||||
|
.ansi-green { color: #4ec994; }
|
||||||
|
.ansi-yellow { color: #e5c07b; }
|
||||||
|
.ansi-blue { color: #61afef; }
|
||||||
|
.ansi-magenta { color: #c678dd; }
|
||||||
|
.ansi-cyan { color: #56b6c2; }
|
||||||
|
.ansi-white { color: #d4d4d4; }
|
||||||
|
.ansi-reset { color: inherit; font-weight: inherit; opacity: inherit; }
|
||||||
|
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
/* ── UPTIME DISPLAY ── */
|
/* ── UPTIME DISPLAY ── */
|
||||||
@@ -237,7 +263,7 @@
|
|||||||
.ping-history { display: flex; gap: 3px; margin-top: 0.8rem; align-items: flex-end; height: 32px; }
|
.ping-history { display: flex; gap: 3px; margin-top: 0.8rem; align-items: flex-end; height: 32px; }
|
||||||
.ping-bar { width: 6px; background: var(--accent3); border-radius: 1px; opacity: 0.7; transition: height 0.4s ease, background 0.3s; flex-shrink: 0; }
|
.ping-bar { width: 6px; background: var(--accent3); border-radius: 1px; opacity: 0.7; transition: height 0.4s ease, background 0.3s; flex-shrink: 0; }
|
||||||
|
|
||||||
/* ── COMBINED PROGRESS STEPS ── */
|
/* ── COMBINED PIPELINE STEPS ── */
|
||||||
.pipeline-steps { display: flex; align-items: center; gap: 0; margin: 1rem 0 0.5rem; }
|
.pipeline-steps { display: flex; align-items: center; gap: 0; margin: 1rem 0 0.5rem; }
|
||||||
.pipeline-step { display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: var(--muted); padding: 0.4rem 0.8rem; border: 1px solid var(--border); border-radius: 4px; transition: all 0.3s; }
|
.pipeline-step { display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: var(--muted); padding: 0.4rem 0.8rem; border: 1px solid var(--border); border-radius: 4px; transition: all 0.3s; }
|
||||||
.pipeline-step.active { color: var(--warn); border-color: rgba(245,158,11,0.4); background: rgba(245,158,11,0.07); }
|
.pipeline-step.active { color: var(--warn); border-color: rgba(245,158,11,0.4); background: rgba(245,158,11,0.07); }
|
||||||
@@ -318,7 +344,7 @@
|
|||||||
<span>All Systems Running Normally</span>
|
<span>All Systems Running Normally</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OVERVIEW STATS (4 cols — added uptime) -->
|
<!-- OVERVIEW STATS -->
|
||||||
<div class="overview fade-up" style="animation-delay:0.15s">
|
<div class="overview fade-up" style="animation-delay:0.15s">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span id="serverDot" class="dot green"></span>
|
<span id="serverDot" class="dot green"></span>
|
||||||
@@ -381,15 +407,15 @@
|
|||||||
<div class="card-title">🚀 <span>Quick</span> Links</div>
|
<div class="card-title">🚀 <span>Quick</span> Links</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-links">
|
<div class="quick-links">
|
||||||
<a href="https://notes.zainezq.com" target="_blank" class="link-card"><span class="icon">📚</span>Notes</a>
|
<a href="https://notes.zainezq.com" target="_blank" class="link-card"><span class="icon">📚</span>Notes</a>
|
||||||
<a href="https://zainezq.com" target="_blank" class="link-card"><span class="icon">🌍</span>Website</a>
|
<a href="https://zainezq.com" target="_blank" class="link-card"><span class="icon">🌍</span>Website</a>
|
||||||
<a href="https://filebrowser.zainezq.com" target="_blank" class="link-card"><span class="icon">📂</span>Files</a>
|
<a href="https://filebrowser.zainezq.com" target="_blank" class="link-card"><span class="icon">📂</span>Files</a>
|
||||||
<a href="https://calibre.zainezq.com" target="_blank" class="link-card"><span class="icon">📖</span>Library</a>
|
<a href="https://calibre.zainezq.com" target="_blank" class="link-card"><span class="icon">📖</span>Library</a>
|
||||||
<a href="https://miniflux.zainezq.com" target="_blank" class="link-card"><span class="icon">📰</span>News</a>
|
<a href="https://miniflux.zainezq.com" target="_blank" class="link-card"><span class="icon">📰</span>News</a>
|
||||||
<a href="https://guac.zainezq.com" target="_blank" class="link-card"><span class="icon">🖥️</span>RDP</a>
|
<a href="https://guac.zainezq.com" target="_blank" class="link-card"><span class="icon">🖥️</span>RDP</a>
|
||||||
<a href="https://portainer.zainezq.com" target="_blank" class="link-card"><span class="icon">🐳</span>Portainer</a>
|
<a href="https://portainer.zainezq.com" target="_blank" class="link-card"><span class="icon">🐳</span>Portainer</a>
|
||||||
<a href="https://nextcloud.zainezq.com" target="_blank" class="link-card"><span class="icon">☁️</span>Nextcloud</a>
|
<a href="https://nextcloud.zainezq.com" target="_blank" class="link-card"><span class="icon">☁️</span>Nextcloud</a>
|
||||||
<a href="https://pgadmin.zainezq.com" target="_blank" class="link-card"><span class="icon">🗄️</span>Databases</a>
|
<a href="https://pgadmin.zainezq.com" target="_blank" class="link-card"><span class="icon">🗄️</span>Databases</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -401,15 +427,12 @@
|
|||||||
<div class="card-title">📡 <span>Server</span> Status</div>
|
<div class="card-title">📡 <span>Server</span> Status</div>
|
||||||
<span id="healthBadge" class="badge neutral">Checking</span>
|
<span id="healthBadge" class="badge neutral">Checking</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Uptime breakdown -->
|
|
||||||
<div class="uptime-grid" id="uptimeGrid">
|
<div class="uptime-grid" id="uptimeGrid">
|
||||||
<div class="uptime-item"><div class="uptime-item-label">Days up</div><div class="uptime-item-value" id="uptimeDays">—</div></div>
|
<div class="uptime-item"><div class="uptime-item-label">Days up</div><div class="uptime-item-value" id="uptimeDays">—</div></div>
|
||||||
<div class="uptime-item"><div class="uptime-item-label">Hours</div><div class="uptime-item-value" id="uptimeHours">—</div></div>
|
<div class="uptime-item"><div class="uptime-item-label">Hours</div><div class="uptime-item-value" id="uptimeHours">—</div></div>
|
||||||
<div class="uptime-item"><div class="uptime-item-label">Minutes</div><div class="uptime-item-value" id="uptimeMins">—</div></div>
|
<div class="uptime-item"><div class="uptime-item-label">Minutes</div><div class="uptime-item-value" id="uptimeMins">—</div></div>
|
||||||
<div class="uptime-item"><div class="uptime-item-label">Started at</div><div class="uptime-item-value" id="uptimeStart" style="font-size:0.72rem">—</div></div>
|
<div class="uptime-item"><div class="uptime-item-label">Started at</div><div class="uptime-item-value" id="uptimeStart" style="font-size:0.72rem">—</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="uptime-bar-wrap">
|
<div class="uptime-bar-wrap">
|
||||||
<div class="uptime-label"><span>Ping History (30 checks)</span><span id="uptimePct">—</span></div>
|
<div class="uptime-label"><span>Ping History (30 checks)</span><span id="uptimePct">—</span></div>
|
||||||
<div class="uptime-bar"><div class="uptime-fill" id="uptimeFill"></div></div>
|
<div class="uptime-bar"><div class="uptime-fill" id="uptimeFill"></div></div>
|
||||||
@@ -432,7 +455,8 @@
|
|||||||
<p id="buildResult" class="status-text">💤 Waiting for action</p>
|
<p id="buildResult" class="status-text">💤 Waiting for action</p>
|
||||||
<div class="last-run-meta" id="webLastRunMeta"></div>
|
<div class="last-run-meta" id="webLastRunMeta"></div>
|
||||||
<button class="btn btn-ghost toggleLogs" data-target="logOutput">Show logs</button>
|
<button class="btn btn-ghost toggleLogs" data-target="logOutput">Show logs</button>
|
||||||
<pre id="logOutput" class="log-box hidden"></pre>
|
<!-- div instead of pre — innerHTML is set directly, pre would double-escape -->
|
||||||
|
<div id="logOutput" class="log-box hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- KNOWLEDGE BASE -->
|
<!-- KNOWLEDGE BASE -->
|
||||||
@@ -446,18 +470,15 @@
|
|||||||
<button class="btn btn-combined" id="combinedBtn">⚡▶ Emacs + Notes</button>
|
<button class="btn btn-combined" id="combinedBtn">⚡▶ Emacs + Notes</button>
|
||||||
<button class="btn btn-danger kill-btn" id="killRoamBtn">✕ Cancel</button>
|
<button class="btn btn-danger kill-btn" id="killRoamBtn">✕ Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pipeline progress (shown during combined run) -->
|
|
||||||
<div class="pipeline-steps hidden" id="pipelineSteps">
|
<div class="pipeline-steps hidden" id="pipelineSteps">
|
||||||
<div class="pipeline-step" id="pipeStep1">⚡ Emacs</div>
|
<div class="pipeline-step" id="pipeStep1">⚡ Emacs</div>
|
||||||
<div class="pipeline-arrow">→</div>
|
<div class="pipeline-arrow">→</div>
|
||||||
<div class="pipeline-step" id="pipeStep2">▶ Notes</div>
|
<div class="pipeline-step" id="pipeStep2">▶ Notes</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="orgRoamResult" class="status-text">💤 Waiting for action</p>
|
<p id="orgRoamResult" class="status-text">💤 Waiting for action</p>
|
||||||
<div class="last-run-meta" id="roamLastRunMeta"></div>
|
<div class="last-run-meta" id="roamLastRunMeta"></div>
|
||||||
<button class="btn btn-ghost toggleLogs" data-target="orgRoamLogs">Show logs</button>
|
<button class="btn btn-ghost toggleLogs" data-target="orgRoamLogs">Show logs</button>
|
||||||
<pre id="orgRoamLogs" class="log-box hidden"></pre>
|
<div id="orgRoamLogs" class="log-box hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -534,7 +555,6 @@ function showConfirm(title, body){
|
|||||||
/* ── PING HISTORY ── */
|
/* ── PING HISTORY ── */
|
||||||
const PING_MAX = 30;
|
const PING_MAX = 30;
|
||||||
let pingResults = [];
|
let pingResults = [];
|
||||||
|
|
||||||
function addPingResult(ok){
|
function addPingResult(ok){
|
||||||
pingResults.push(ok);
|
pingResults.push(ok);
|
||||||
if(pingResults.length > PING_MAX) pingResults.shift();
|
if(pingResults.length > PING_MAX) pingResults.shift();
|
||||||
@@ -560,7 +580,7 @@ async function fetchUptime(){
|
|||||||
document.getElementById('uptimeMins').textContent = m;
|
document.getElementById('uptimeMins').textContent = m;
|
||||||
document.getElementById('uptimeStart').textContent = new Date(data.serverStart).toLocaleTimeString('en-GB',{hour:'2-digit',minute:'2-digit'});
|
document.getElementById('uptimeStart').textContent = new Date(data.serverStart).toLocaleTimeString('en-GB',{hour:'2-digit',minute:'2-digit'});
|
||||||
document.getElementById('uptimeStat').textContent = `${d}d ${h}h ${m}m`;
|
document.getElementById('uptimeStat').textContent = `${d}d ${h}h ${m}m`;
|
||||||
} catch { /* server offline, health check handles UI */ }
|
} catch { /* server offline */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── LAST RUN META ── */
|
/* ── LAST RUN META ── */
|
||||||
@@ -572,7 +592,6 @@ async function fetchLastRuns(){
|
|||||||
renderLastRun('roamLastRunMeta', data.roam);
|
renderLastRun('roamLastRunMeta', data.roam);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLastRun(elId, info){
|
function renderLastRun(elId, info){
|
||||||
const el = document.getElementById(elId);
|
const el = document.getElementById(elId);
|
||||||
if(!el) return;
|
if(!el) return;
|
||||||
@@ -621,9 +640,53 @@ document.querySelectorAll('.toggleLogs').forEach(btn=>{
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ── STREAM HELPERS ── */
|
/* ── LOG STREAMING ─────────────────────────────────────────────────────────
|
||||||
|
The server sends two named SSE event types:
|
||||||
|
"log" – one line already converted from ANSI → HTML <span> elements
|
||||||
|
"done" – build finished; close the EventSource
|
||||||
|
|
||||||
|
Each line is appended as a block-level <span> inside a div.log-box so
|
||||||
|
that innerHTML renders the colour spans correctly. The old approach used
|
||||||
|
pre.textContent += line which (a) stripped the spans and (b) couldn't
|
||||||
|
show colours, and (c) used the default onmessage which only fires for
|
||||||
|
unnamed events — it would never have received our named "log" events.
|
||||||
|
────────────────────────────────────────────────────────────────────────── */
|
||||||
let emacsES=null, webES=null, roamES=null, combinedES=null;
|
let emacsES=null, webES=null, roamES=null, combinedES=null;
|
||||||
function startLogStream(url,el,setter){ const s=new EventSource(url); s.onmessage=e=>{el.textContent+=e.data+'\n';el.scrollTop=el.scrollHeight;}; s.onerror=()=>s.close(); setter(s); }
|
|
||||||
|
function startLogStream(url, container, setter) {
|
||||||
|
// Clear previous content
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
const es = new EventSource(url);
|
||||||
|
|
||||||
|
// Named "log" event — server sends one line of ANSI→HTML converted output
|
||||||
|
es.addEventListener('log', e => {
|
||||||
|
const line = document.createElement('span');
|
||||||
|
line.className = 'log-line';
|
||||||
|
line.innerHTML = e.data; // safe: server HTML-escaped raw text first
|
||||||
|
container.appendChild(line);
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Named "done" event — stream has ended, close cleanly
|
||||||
|
es.addEventListener('done', () => {
|
||||||
|
es.close();
|
||||||
|
setter(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Network error / server closed without "done"
|
||||||
|
es.onerror = () => {
|
||||||
|
es.close();
|
||||||
|
setter(null);
|
||||||
|
const line = document.createElement('span');
|
||||||
|
line.className = 'log-line ansi-dim';
|
||||||
|
line.textContent = '— stream closed —';
|
||||||
|
container.appendChild(line);
|
||||||
|
};
|
||||||
|
|
||||||
|
setter(es);
|
||||||
|
}
|
||||||
|
|
||||||
function closeStream(s){ if(s) s.close(); }
|
function closeStream(s){ if(s) s.close(); }
|
||||||
|
|
||||||
/* ── KILL HELPERS ── */
|
/* ── KILL HELPERS ── */
|
||||||
@@ -631,7 +694,7 @@ async function killEndpoint(url, label){
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(url, {method:'DELETE'});
|
const res = await fetch(url, {method:'DELETE'});
|
||||||
if(res.ok) showToast(`${label} cancelled`, 'warn');
|
if(res.ok) showToast(`${label} cancelled`, 'warn');
|
||||||
else showToast(`Nothing to cancel`, 'info');
|
else showToast('Nothing to cancel', 'info');
|
||||||
} catch { showToast('Kill request failed','error'); }
|
} catch { showToast('Kill request failed','error'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,14 +708,13 @@ async function runWebBuild(){
|
|||||||
res.textContent='🔄 Updating website...'; res.className='status-text running';
|
res.textContent='🔄 Updating website...'; res.className='status-text running';
|
||||||
document.getElementById('webDot').className='dot yellow';
|
document.getElementById('webDot').className='dot yellow';
|
||||||
document.getElementById('webStatus').textContent='Deploying';
|
document.getElementById('webStatus').textContent='Deploying';
|
||||||
document.getElementById('logOutput').textContent='';
|
|
||||||
showToast('Website build started…','info');
|
showToast('Website build started…','info');
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/build-web',{method:'POST'});
|
const r = await fetch('/api/build-web',{method:'POST'});
|
||||||
if(!r.ok) throw new Error();
|
if(!r.ok) throw new Error();
|
||||||
closeStream(webES);
|
closeStream(webES);
|
||||||
startLogStream('/api/build-web/logs', document.getElementById('logOutput'), s=>webES=s);
|
startLogStream('/api/build-web/logs', document.getElementById('logOutput'), s=>webES=s);
|
||||||
pollStatus('/api/build-web/status', 'buildResult', 'buildBtn', 'killWebBtn', 'webDot', 'webStatus', ()=>{closeStream(webES); fetchLastRuns();}, 'Website', null);
|
pollStatus('/api/build-web/status','buildResult','buildBtn','killWebBtn','webDot','webStatus',()=>{closeStream(webES);fetchLastRuns();},'Website',null);
|
||||||
} catch {
|
} catch {
|
||||||
res.textContent='❌ Could not start update'; res.className='status-text failed';
|
res.textContent='❌ Could not start update'; res.className='status-text failed';
|
||||||
document.getElementById('buildBtn').disabled=false;
|
document.getElementById('buildBtn').disabled=false;
|
||||||
@@ -672,14 +734,13 @@ async function runOrgRoamBuild(){
|
|||||||
res.textContent='🔄 Refreshing notes...'; res.className='status-text running';
|
res.textContent='🔄 Refreshing notes...'; res.className='status-text running';
|
||||||
document.getElementById('roamDot').className='dot yellow';
|
document.getElementById('roamDot').className='dot yellow';
|
||||||
document.getElementById('roamStatus').textContent='Syncing';
|
document.getElementById('roamStatus').textContent='Syncing';
|
||||||
document.getElementById('orgRoamLogs').textContent='';
|
|
||||||
showToast('Knowledge base refresh started…','info');
|
showToast('Knowledge base refresh started…','info');
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/build-roam',{method:'POST'});
|
const r = await fetch('/api/build-roam',{method:'POST'});
|
||||||
if(!r.ok) throw new Error();
|
if(!r.ok) throw new Error();
|
||||||
closeStream(roamES);
|
closeStream(roamES);
|
||||||
startLogStream('/api/build-roam/logs', document.getElementById('orgRoamLogs'), s=>roamES=s);
|
startLogStream('/api/build-roam/logs', document.getElementById('orgRoamLogs'), s=>roamES=s);
|
||||||
pollStatus('/api/build-roam/status','orgRoamResult','orgRoamBtn','killRoamBtn','roamDot','roamStatus',()=>{closeStream(roamES); fetchLastRuns();},'Notes',null);
|
pollStatus('/api/build-roam/status','orgRoamResult','orgRoamBtn','killRoamBtn','roamDot','roamStatus',()=>{closeStream(roamES);fetchLastRuns();},'Notes',null);
|
||||||
} catch {
|
} catch {
|
||||||
res.textContent='❌ Could not start refresh'; res.className='status-text failed';
|
res.textContent='❌ Could not start refresh'; res.className='status-text failed';
|
||||||
document.getElementById('orgRoamBtn').disabled=false;
|
document.getElementById('orgRoamBtn').disabled=false;
|
||||||
@@ -699,7 +760,6 @@ async function rerunEmacs(){
|
|||||||
res.textContent='🔄 Running Emacs...'; res.className='status-text running';
|
res.textContent='🔄 Running Emacs...'; res.className='status-text running';
|
||||||
document.getElementById('roamDot').className='dot yellow';
|
document.getElementById('roamDot').className='dot yellow';
|
||||||
document.getElementById('roamStatus').textContent='Running';
|
document.getElementById('roamStatus').textContent='Running';
|
||||||
document.getElementById('orgRoamLogs').textContent='';
|
|
||||||
showToast('Emacs starting…','info');
|
showToast('Emacs starting…','info');
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/rerun-emacs',{method:'POST'});
|
const r = await fetch('/api/rerun-emacs',{method:'POST'});
|
||||||
@@ -720,25 +780,17 @@ async function rerunEmacs(){
|
|||||||
async function runCombined(){
|
async function runCombined(){
|
||||||
const ok = await showConfirm('Emacs + Notes Refresh','This will run Emacs then rebuild your Org Roam notes sequentially. Continue?');
|
const ok = await showConfirm('Emacs + Notes Refresh','This will run Emacs then rebuild your Org Roam notes sequentially. Continue?');
|
||||||
if(!ok) return;
|
if(!ok) return;
|
||||||
|
|
||||||
// Disable all kb buttons, show kill
|
|
||||||
['orgRoamBtn','orgRoamBtn2','combinedBtn'].forEach(id=>document.getElementById(id).disabled=true);
|
['orgRoamBtn','orgRoamBtn2','combinedBtn'].forEach(id=>document.getElementById(id).disabled=true);
|
||||||
document.getElementById('killRoamBtn').classList.add('visible');
|
document.getElementById('killRoamBtn').classList.add('visible');
|
||||||
|
|
||||||
const res = document.getElementById('orgRoamResult');
|
const res = document.getElementById('orgRoamResult');
|
||||||
res.textContent='⚡▶ Running Emacs → Notes...'; res.className='status-text running';
|
res.textContent='⚡▶ Running Emacs → Notes...'; res.className='status-text running';
|
||||||
document.getElementById('roamDot').className='dot yellow';
|
document.getElementById('roamDot').className='dot yellow';
|
||||||
document.getElementById('roamStatus').textContent='Pipeline';
|
document.getElementById('roamStatus').textContent='Pipeline';
|
||||||
document.getElementById('orgRoamLogs').textContent='';
|
|
||||||
|
|
||||||
// Show pipeline steps
|
|
||||||
const steps = document.getElementById('pipelineSteps');
|
const steps = document.getElementById('pipelineSteps');
|
||||||
steps.classList.remove('hidden');
|
steps.classList.remove('hidden');
|
||||||
document.getElementById('pipeStep1').className='pipeline-step active';
|
document.getElementById('pipeStep1').className='pipeline-step active';
|
||||||
document.getElementById('pipeStep2').className='pipeline-step';
|
document.getElementById('pipeStep2').className='pipeline-step';
|
||||||
|
|
||||||
showToast('Emacs + Notes pipeline started…','warn');
|
showToast('Emacs + Notes pipeline started…','warn');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/run-combined',{method:'POST'});
|
const r = await fetch('/api/run-combined',{method:'POST'});
|
||||||
if(!r.ok) throw new Error();
|
if(!r.ok) throw new Error();
|
||||||
@@ -756,25 +808,20 @@ async function runCombined(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pollCombinedStatus(){
|
function pollCombinedStatus(){
|
||||||
// We watch the log to detect which step we're on
|
|
||||||
const logEl = document.getElementById('orgRoamLogs');
|
const logEl = document.getElementById('orgRoamLogs');
|
||||||
const interval = setInterval(async()=>{
|
const interval = setInterval(async()=>{
|
||||||
const r = await fetch('/api/run-combined/status');
|
const r = await fetch('/api/run-combined/status');
|
||||||
const s = await r.json();
|
const s = await r.json();
|
||||||
|
// Detect step transition from rendered log text
|
||||||
// Update step indicators from log text
|
if(logEl.textContent.includes('[2/2]')){
|
||||||
const log = logEl.textContent;
|
|
||||||
if(log.includes('[2/2]')){
|
|
||||||
document.getElementById('pipeStep1').className='pipeline-step done';
|
document.getElementById('pipeStep1').className='pipeline-step done';
|
||||||
document.getElementById('pipeStep2').className='pipeline-step active';
|
document.getElementById('pipeStep2').className='pipeline-step active';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!s.running){
|
if(!s.running){
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
closeStream(combinedES);
|
closeStream(combinedES);
|
||||||
['orgRoamBtn','orgRoamBtn2','combinedBtn'].forEach(id=>document.getElementById(id).disabled=false);
|
['orgRoamBtn','orgRoamBtn2','combinedBtn'].forEach(id=>document.getElementById(id).disabled=false);
|
||||||
document.getElementById('killRoamBtn').classList.remove('visible');
|
document.getElementById('killRoamBtn').classList.remove('visible');
|
||||||
|
|
||||||
const res = document.getElementById('orgRoamResult');
|
const res = document.getElementById('orgRoamResult');
|
||||||
if(s.lastExitCode===0){
|
if(s.lastExitCode===0){
|
||||||
res.textContent='✅ Emacs + Notes completed'; res.className='status-text success';
|
res.textContent='✅ Emacs + Notes completed'; res.className='status-text success';
|
||||||
@@ -851,7 +898,6 @@ document.getElementById('killWebBtn').addEventListener('click', async()=>{
|
|||||||
const ok = await showConfirm('Cancel Build','Force-kill the running website build?');
|
const ok = await showConfirm('Cancel Build','Force-kill the running website build?');
|
||||||
if(ok) killEndpoint('/api/build-web','Web build');
|
if(ok) killEndpoint('/api/build-web','Web build');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('killRoamBtn').addEventListener('click', async()=>{
|
document.getElementById('killRoamBtn').addEventListener('click', async()=>{
|
||||||
const ok = await showConfirm('Cancel Process','Force-kill the running process?');
|
const ok = await showConfirm('Cancel Process','Force-kill the running process?');
|
||||||
if(ok) killEndpoint('/api/run-combined','Process');
|
if(ok) killEndpoint('/api/run-combined','Process');
|
||||||
@@ -867,4 +913,4 @@ document.getElementById('combinedBtn').addEventListener('click', runCombined);
|
|||||||
fetchHealth();
|
fetchHealth();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user