diff --git a/app.py b/app.py
index 67d0014..d5f1553 100644
--- a/app.py
+++ b/app.py
@@ -254,4 +254,5 @@ def api_export_png():
if __name__ == "__main__":
+ app.config['TEMPLATES_AUTO_RELOAD'] = True
app.run(host="0.0.0.0", port=5000, debug=False)
diff --git a/index.html b/index.html
index e9e2ad3..ecf952b 100644
--- a/index.html
+++ b/index.html
@@ -416,6 +416,17 @@ var Xr=function(e){if(!(this instanceof Xr))return new Xr(e);this.id="Thenable/1
}
.scan-log-line.ok { color: #3fb950; }
.scan-log-line.fail { color: #f85149; }
+ .scan-log-dismiss {
+ margin-left: auto;
+ background: none;
+ border: none;
+ color: #484f58;
+ cursor: pointer;
+ font-size: 14px;
+ padding: 0 4px;
+ line-height: 1;
+ }
+ .scan-log-dismiss:hover { color: #8b949e; }
.spinner {
width: 14px; height: 14px;
border: 2px solid rgba(79,142,247,.3);
@@ -485,7 +496,7 @@ var Xr=function(e){if(!(this instanceof Xr))return new Xr(e);this.id="Thenable/1
-
Sessions
+
Parallel
5
@@ -585,8 +596,9 @@ var Xr=function(e){if(!(this instanceof Xr))return new Xr(e);this.id="Thenable/1
@@ -936,6 +948,8 @@ async function pollStatus() {
document.getElementById('clearScanBtn').disabled = true;
progressWrap.classList.add('visible');
statusBar.classList.add('visible');
+ document.getElementById('scanSpinner').style.display = '';
+ document.getElementById('scanLogDismiss').style.display = 'none';
const pct = s.total > 0 ? Math.round((s.done / s.total) * 100) : 0;
progressBar.style.width = pct + '%';
@@ -964,8 +978,31 @@ async function pollStatus() {
const positions = cy.nodes().map(n => ({ id: n.id(), x: n.position('x'), y: n.position('y') }));
await fetch('/api/layout', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ positions }) });
}, 1000);
+
+ // Update log panel to show completed state
+ const spinner = document.getElementById('scanSpinner');
+ const dismissBtn = document.getElementById('scanLogDismiss');
+ const logBody = document.getElementById('scanLogBody');
+ const lines = s.log_lines || [];
+ logBody.innerHTML = lines.map(l =>
+ `
${l.ts} ${l.text}
`
+ ).join('');
+ logBody.scrollTop = logBody.scrollHeight;
+
+ if (s.fail > 0) {
+ // Keep panel open so user can read errors
+ spinner.style.display = 'none';
+ statusText.innerHTML =
+ `Scan complete — ` +
+ `
✓ ${s.ok} ` +
+ `
✗ ${s.fail} failed`;
+ dismissBtn.style.display = 'block';
+ } else {
+ statusBar.classList.remove('visible');
+ spinner.style.display = '';
+ dismissBtn.style.display = 'none';
+ }
}
- // Do NOT call loadTopology here on every poll tick
scanBtn.disabled = false;
document.getElementById('scanElecBtn').disabled = false;
@@ -976,7 +1013,6 @@ async function pollStatus() {
progressWrap.classList.remove('visible');
progressBar.style.width = '0%';
}, 800);
- statusBar.classList.remove('visible');
clearInterval(pollInterval);
pollInterval = null;