dashboard+agent: agent backend owns its nav links; dashboard proxies
The previous take put a shared NavLink wire type in hive-sh4re and duplicated the link-building logic across crates. Per @mara on #326: that doesn't fit the eventual frontend/backend split goal (#273). The agent backend is the natural source of truth for what links its own page exposes; hive-c0re just passes the list through to the dashboard. * hive-ag3nt/src/web_ui.rs: agent_links now also serves the config-repo link + reads agent-declared dashboardLinks extras from {state_dir}/hyperhive-dashboard-links.json. AgentLink gains a kind enum (Container | Forge | External) so the frontend can build the right href no matter which surface is rendering. The host header is no longer used — URLs are paths for Container/Forge, absolute for External. * hive-c0re/src/dashboard.rs: new GET /api/agent/{name}/links route, a same-origin proxy that fetches the agent's /api/state and forwards just the links field. No shared wire type — hive-c0re treats the payload as opaque JSON (serde_json::Value). All failure modes degrade to an empty list so the dashboard still renders. * hive-c0re/assets/app.js: container card head row gets an async- populated icon-only nav strip from the proxy. The hardcoded stats link, the standalone config-repo trigger, and the extras block are gone. The deployed:<sha> chip stays — the agent harness can't know its own deployed sha, so this chip is how the operator sees what's live alongside the agent's (root-only) config link. * hive-ag3nt/assets/app.js: agent page meta-links rendered via el() / textContent (DOM build) so agent-declared icon / label / url strings never reach innerHTML. kind-based href resolution mirrors the dashboard side. * docs/web-ui.md: dashboard + per-agent sections updated for the new architecture. Closes #262.
This commit is contained in:
parent
e70ae7776c
commit
222a5b4dc6
5 changed files with 243 additions and 71 deletions
|
|
@ -674,14 +674,33 @@
|
|||
const s = await resp.json();
|
||||
if (!headerSet) { setHeader(s.label, s.dashboard_port); headerSet = true; }
|
||||
currentLabel = s.label;
|
||||
// Render server-supplied navigation links (stats, screen, forge, extras).
|
||||
// Render server-supplied navigation links — stats, screen, the
|
||||
// forge profile, the agent-configs mirror, plus any
|
||||
// agent-declared `dashboardLinks` extras (issue #262). Each
|
||||
// NavLink's `kind` says how to resolve `url`: Container →
|
||||
// same-origin path (the agent page is itself container-local);
|
||||
// Forge → `http://<host>:3000<url>`; External → already
|
||||
// absolute. DOM-built via el() — agent-declared icon / label /
|
||||
// url strings must NEVER reach innerHTML.
|
||||
const metaLinks = $('meta-links');
|
||||
if (metaLinks && s.links) {
|
||||
metaLinks.innerHTML = (s.links || []).map((lnk, i) => {
|
||||
const margin = i === 0 ? '' : ' margin-left: 1em;';
|
||||
const target = lnk.url.startsWith('http') ? ' target="_blank" rel="noopener"' : '';
|
||||
return `<a href="${lnk.url}"${target} style="color: var(--cyan); text-decoration: none;${margin}">${lnk.icon} ${lnk.label} →</a>`;
|
||||
}).join('');
|
||||
if (metaLinks && Array.isArray(s.links)) {
|
||||
metaLinks.replaceChildren();
|
||||
const forgeBase = `http://${window.location.hostname}:3000`;
|
||||
s.links.forEach((lnk, i) => {
|
||||
const href = lnk.kind === 'forge' ? forgeBase + (lnk.url || '')
|
||||
: lnk.kind === 'external' ? (lnk.url || '')
|
||||
: /* container */ (lnk.url || '');
|
||||
const a = el('a', {
|
||||
class: 'agent-nav-link',
|
||||
href,
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: lnk.label || '',
|
||||
});
|
||||
if (i > 0) a.style.marginLeft = '1em';
|
||||
a.append(((lnk.icon || '') + ' ' + (lnk.label || '')).trim() + ' →');
|
||||
metaLinks.append(a);
|
||||
});
|
||||
}
|
||||
renderTermInput(s.label, s.status === 'online');
|
||||
renderInbox(s.inbox || []);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue