webui: add the Sources & Config sidebar section
Renames the "Add Source" section to "Sources & Config" and replaces the fire-and-forget "Save list" button with Save and Reload, plus a one-line status area that renders the hub's configSaved/configReloaded ack. onConfigAck replaces the no-op stub Task 7 left behind. It branches on the frame type because configSaved carries a path and configReloaded does not, and surfaces the hub's error text on failure rather than failing silently. The status is kept outside the DOM because buildSidebar() recreates this section on every sources broadcast. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
5917ab1bf2
commit
e37eec0276
@@ -2706,7 +2706,7 @@ function buildSidebar() {
|
|||||||
list.appendChild(empty);
|
list.appendChild(empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.appendChild(makeAddSourceSection());
|
list.appendChild(makeSourcesConfigSection());
|
||||||
}
|
}
|
||||||
function makeDraggable(key, label, typeName, unit) {
|
function makeDraggable(key, label, typeName, unit) {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
@@ -3473,16 +3473,35 @@ function applyCalibrationChanged() {
|
|||||||
if (trig.signal) { refreshTrigThresholdField(); sendTrigConfig(); }
|
if (trig.signal) { refreshTrigThresholdField(); sendTrigConfig(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaced in Task 10 with the Sources & Config status renderer.
|
// Last config acknowledgement, kept outside the DOM because buildSidebar()
|
||||||
function onConfigAck(msg) { /* no-op until Task 10 */ }
|
// discards and recreates this whole section on every `sources` broadcast.
|
||||||
|
let _cfgStatus = null; // {ok: bool, text: string} or null
|
||||||
|
|
||||||
function makeAddSourceSection() {
|
function renderCfgStatus(el) {
|
||||||
|
el.className = 'cfg-status';
|
||||||
|
if (!_cfgStatus) { el.textContent = ''; return; }
|
||||||
|
el.classList.add(_cfgStatus.ok ? 'ok' : 'err');
|
||||||
|
el.textContent = _cfgStatus.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfigAck(msg) {
|
||||||
|
const what = msg.type === 'configSaved' ? 'Saved' : 'Reloaded';
|
||||||
|
if (msg.ok) {
|
||||||
|
_cfgStatus = { ok: true, text: what + ': ' + (msg.path || 'config file') };
|
||||||
|
} else {
|
||||||
|
_cfgStatus = { ok: false, text: what + ' failed: ' + (msg.error || 'unknown error') };
|
||||||
|
}
|
||||||
|
const el = document.getElementById('cfg-status');
|
||||||
|
if (el) renderCfgStatus(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSourcesConfigSection() {
|
||||||
const section = document.createElement('div');
|
const section = document.createElement('div');
|
||||||
section.className = 'add-source-section';
|
section.className = 'add-source-section';
|
||||||
|
|
||||||
const title = document.createElement('div');
|
const title = document.createElement('div');
|
||||||
title.className = 'add-source-title';
|
title.className = 'add-source-title';
|
||||||
title.innerHTML = '<span class="add-src-arrow">▶</span> Add Source';
|
title.innerHTML = '<span class="add-src-arrow">▶</span> Sources & Config';
|
||||||
|
|
||||||
const body = document.createElement('div');
|
const body = document.createElement('div');
|
||||||
body.className = 'add-source-body';
|
body.className = 'add-source-body';
|
||||||
@@ -3515,12 +3534,37 @@ function makeAddSourceSection() {
|
|||||||
});
|
});
|
||||||
addrInput.addEventListener('keydown', e => { if (e.key === 'Enter') addBtn.click(); });
|
addrInput.addEventListener('keydown', e => { if (e.key === 'Enter') addBtn.click(); });
|
||||||
|
|
||||||
|
const btnRow = document.createElement('div');
|
||||||
|
btnRow.className = 'cfg-btn-row';
|
||||||
|
|
||||||
const saveBtn = document.createElement('button');
|
const saveBtn = document.createElement('button');
|
||||||
saveBtn.className = 'add-src-btn save-src-btn';
|
saveBtn.className = 'add-src-btn save-src-btn';
|
||||||
saveBtn.textContent = 'Save list'; saveBtn.title = 'Save source list to file';
|
saveBtn.textContent = 'Save';
|
||||||
saveBtn.addEventListener('click', saveConfigWS);
|
saveBtn.title = 'Write the source list and all signal calibration to the hub\u2019s config file';
|
||||||
|
saveBtn.addEventListener('click', () => {
|
||||||
|
_cfgStatus = null;
|
||||||
|
const el = document.getElementById('cfg-status'); if (el) renderCfgStatus(el);
|
||||||
|
saveConfigWS();
|
||||||
|
});
|
||||||
|
|
||||||
body.append(addrInput, labelInput, mcastInput, dataPortInput, addBtn, saveBtn);
|
const reloadBtn = document.createElement('button');
|
||||||
|
reloadBtn.className = 'add-src-btn reload-src-btn';
|
||||||
|
reloadBtn.textContent = 'Reload';
|
||||||
|
reloadBtn.title = 'Re-read the config file: calibration is replaced wholesale, '
|
||||||
|
+ 'missing sources are added, and no running source is stopped';
|
||||||
|
reloadBtn.addEventListener('click', () => {
|
||||||
|
_cfgStatus = null;
|
||||||
|
const el = document.getElementById('cfg-status'); if (el) renderCfgStatus(el);
|
||||||
|
reloadConfigWS();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnRow.append(saveBtn, reloadBtn);
|
||||||
|
|
||||||
|
const status = document.createElement('div');
|
||||||
|
status.id = 'cfg-status';
|
||||||
|
renderCfgStatus(status);
|
||||||
|
|
||||||
|
body.append(addrInput, labelInput, mcastInput, dataPortInput, addBtn, btnRow, status);
|
||||||
section.append(title, body);
|
section.append(title, body);
|
||||||
|
|
||||||
title.addEventListener('click', () => {
|
title.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -478,6 +478,16 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
.add-src-btn:hover { background:rgba(137,180,250,0.15); border-color:var(--accent); }
|
.add-src-btn:hover { background:rgba(137,180,250,0.15); border-color:var(--accent); }
|
||||||
.save-src-btn { color:var(--green); }
|
.save-src-btn { color:var(--green); }
|
||||||
.save-src-btn:hover { background:rgba(166,227,161,0.1); border-color:var(--green); }
|
.save-src-btn:hover { background:rgba(166,227,161,0.1); border-color:var(--green); }
|
||||||
|
.cfg-btn-row { display:flex; gap:6px; }
|
||||||
|
.cfg-btn-row .add-src-btn { flex:1; }
|
||||||
|
.reload-src-btn { color:var(--peach); }
|
||||||
|
.reload-src-btn:hover { background:rgba(250,179,135,0.1); border-color:var(--peach); }
|
||||||
|
.cfg-status {
|
||||||
|
font-size:10px; line-height:1.3; padding:2px 0; min-height:13px;
|
||||||
|
overflow-wrap:anywhere;
|
||||||
|
}
|
||||||
|
.cfg-status.ok { color:var(--green); }
|
||||||
|
.cfg-status.err { color:var(--red); }
|
||||||
|
|
||||||
/* ── Stats panel ─────────────────────────────────────────────── */
|
/* ── Stats panel ─────────────────────────────────────────────── */
|
||||||
#stats-panel {
|
#stats-panel {
|
||||||
|
|||||||
Reference in New Issue
Block a user