Enable template auto-reload and fix scan log UI
TEMPLATES_AUTO_RELOAD=True so index.html volume-mount hot-reloads on browser refresh without a container restart. Also: scan log persists after scan with dismiss button when failures exist; slider renamed from Sessions to Parallel with clarifying tooltip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -254,4 +254,5 @@ def api_export_png():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||||
|
|||||||
+40
-4
@@ -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.ok { color: #3fb950; }
|
||||||
.scan-log-line.fail { color: #f85149; }
|
.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 {
|
.spinner {
|
||||||
width: 14px; height: 14px;
|
width: 14px; height: 14px;
|
||||||
border: 2px solid rgba(79,142,247,.3);
|
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
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scan-settings-wrap">
|
<div class="scan-settings-wrap">
|
||||||
<span>Sessions</span>
|
<span title="Max switches running commands in parallel after login">Parallel</span>
|
||||||
<input type="range" id="workersSlider" min="1" max="10" value="5" oninput="document.getElementById('workersVal').textContent=this.value">
|
<input type="range" id="workersSlider" min="1" max="10" value="5" oninput="document.getElementById('workersVal').textContent=this.value">
|
||||||
<span class="setting-val" id="workersVal">5</span>
|
<span class="setting-val" id="workersVal">5</span>
|
||||||
<div class="scan-settings-sep"></div>
|
<div class="scan-settings-sep"></div>
|
||||||
@@ -585,8 +596,9 @@ var Xr=function(e){if(!(this instanceof Xr))return new Xr(e);this.id="Thenable/1
|
|||||||
<!-- Scan log panel -->
|
<!-- Scan log panel -->
|
||||||
<div class="scan-log" id="statusBar">
|
<div class="scan-log" id="statusBar">
|
||||||
<div class="scan-log-header">
|
<div class="scan-log-header">
|
||||||
<div class="spinner"></div>
|
<div class="spinner" id="scanSpinner"></div>
|
||||||
<span id="statusText" class="scan-log-summary">Scanning...</span>
|
<span id="statusText" class="scan-log-summary">Scanning...</span>
|
||||||
|
<button class="scan-log-dismiss" id="scanLogDismiss" onclick="document.getElementById('statusBar').classList.remove('visible')" style="display:none">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="scan-log-body" id="scanLogBody"></div>
|
<div class="scan-log-body" id="scanLogBody"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -936,6 +948,8 @@ async function pollStatus() {
|
|||||||
document.getElementById('clearScanBtn').disabled = true;
|
document.getElementById('clearScanBtn').disabled = true;
|
||||||
progressWrap.classList.add('visible');
|
progressWrap.classList.add('visible');
|
||||||
statusBar.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;
|
const pct = s.total > 0 ? Math.round((s.done / s.total) * 100) : 0;
|
||||||
progressBar.style.width = pct + '%';
|
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') }));
|
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 }) });
|
await fetch('/api/layout', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ positions }) });
|
||||||
}, 1000);
|
}, 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 =>
|
||||||
|
`<div class="scan-log-line ${l.ok ? 'ok' : 'fail'}"><span style="color:#484f58">${l.ts} </span>${l.text}</div>`
|
||||||
|
).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 — ` +
|
||||||
|
`<span class="ok-count">✓ ${s.ok}</span> ` +
|
||||||
|
`<span class="fail-count">✗ ${s.fail} failed</span>`;
|
||||||
|
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;
|
scanBtn.disabled = false;
|
||||||
document.getElementById('scanElecBtn').disabled = false;
|
document.getElementById('scanElecBtn').disabled = false;
|
||||||
@@ -976,7 +1013,6 @@ async function pollStatus() {
|
|||||||
progressWrap.classList.remove('visible');
|
progressWrap.classList.remove('visible');
|
||||||
progressBar.style.width = '0%';
|
progressBar.style.width = '0%';
|
||||||
}, 800);
|
}, 800);
|
||||||
statusBar.classList.remove('visible');
|
|
||||||
|
|
||||||
clearInterval(pollInterval);
|
clearInterval(pollInterval);
|
||||||
pollInterval = null;
|
pollInterval = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user