dashboard: per-container applied agent.nix viewer
new GET /api/agent-config/{name} returns the contents of
/var/lib/hyperhive/applied/<name>/agent.nix — the file the
container actually builds against. validated against the live
container list to avoid arbitrary filesystem reads.
frontend mirrors the journald viewer: collapsed <details> on each
container row, lazy-fetches on expand, refresh button re-fetches.
restore-keyed (agent-config:<name>) so it survives the dashboard
heartbeat refresh.
read-only — mutating the applied config goes through the existing
request_apply_commit + operator approval flow.
This commit is contained in:
parent
80229c6af9
commit
91c78d626f
3 changed files with 70 additions and 9 deletions
|
|
@ -290,6 +290,9 @@
|
|||
// narrows to the harness service (or empty = full machine).
|
||||
const journalUnit = c.is_manager ? 'hive-m1nd.service' : 'hive-ag3nt.service';
|
||||
li.append(buildJournalDetails(c.container, journalUnit));
|
||||
// Per-container applied config viewer. Shows the agent.nix
|
||||
// the container is actually built against.
|
||||
li.append(buildConfigDetails(c.name));
|
||||
|
||||
ul.append(li);
|
||||
}
|
||||
|
|
@ -348,6 +351,48 @@
|
|||
return details;
|
||||
}
|
||||
|
||||
// Per-container applied-config viewer. Lazy-fetches on expand;
|
||||
// refresh button re-fetches. Read-only — the file is hive-c0re's
|
||||
// applied repo, mutated only via the approval flow.
|
||||
function buildConfigDetails(agentName) {
|
||||
const details = el('details', {
|
||||
class: 'journal',
|
||||
'data-restore-key': 'agent-config:' + agentName,
|
||||
});
|
||||
const summary = el('summary', {}, '↳ agent.nix · ' + agentName);
|
||||
const body = el('div', { class: 'journal-body' });
|
||||
const controls = el('div', { class: 'journal-controls' });
|
||||
const refresh = el('button', { type: 'button', class: 'btn btn-restart journal-refresh' },
|
||||
'↻ refresh');
|
||||
const pre = el('pre', { class: 'journal-output' }, 'fetching…');
|
||||
let fetching = false;
|
||||
async function fetchConfig() {
|
||||
if (fetching) return;
|
||||
fetching = true;
|
||||
pre.textContent = 'fetching…';
|
||||
try {
|
||||
const resp = await fetch('/api/agent-config/' + agentName);
|
||||
const text = await resp.text();
|
||||
if (!resp.ok) {
|
||||
pre.textContent = 'error: ' + resp.status + '\n' + text;
|
||||
} else {
|
||||
pre.textContent = text || '(empty)';
|
||||
pre.scrollTop = 0;
|
||||
}
|
||||
} catch (err) {
|
||||
pre.textContent = 'fetch failed: ' + err;
|
||||
} finally {
|
||||
fetching = false;
|
||||
}
|
||||
}
|
||||
details.addEventListener('toggle', () => { if (details.open) fetchConfig(); });
|
||||
refresh.addEventListener('click', (e) => { e.preventDefault(); fetchConfig(); });
|
||||
controls.append(refresh);
|
||||
body.append(controls, pre);
|
||||
details.append(summary, body);
|
||||
return details;
|
||||
}
|
||||
|
||||
function renderTombstones(s) {
|
||||
const root = $('tombstones-section');
|
||||
root.innerHTML = '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue