dashboard: build same-origin /agent/<name>/ links when gateway is up (closes #842)
When `services.hyperhive.gateway.enable` is on (default), the c0re NixOS module now sets `HIVE_GATEWAY_ENABLED=1` on the service env. `/api/state` exposes the flag as `gateway_enabled`; the SW4RM tab's container-row renderer flips three link sites (primary agent-name link, favicon fetch, `container`-kind nav-strip links) from the legacy `http://<host>:<port>/` direct TCP shape to same-origin `/agent/<name>/` URLs — the gateway proxies them to the per-agent harness via `agent-ports.json` or `agent-sockets.json` (#784 / #815). Gateway-off deploys keep the direct TCP fallback so local-dev / operator opt-out keeps working. `forge`-kind nav-strip links still resolve against `:3000` (separate sub-domain transition, tracked by `forge.behindGateway`); `external`-kind links are already absolute. Mirrors the `HIVE_MATRIX_GUI_ENABLED` env→snapshot-flag pattern. Docs updated: `docs/web-ui.md::Container row` + new `docs/gateway.md::Dashboard link shape` section.
This commit is contained in:
parent
b50f182396
commit
fbc41d42a6
5 changed files with 72 additions and 4 deletions
|
|
@ -104,6 +104,22 @@ appear in `agent-ports.json` (the legacy TCP map) and the gateway
|
||||||
falls back to TCP for them. Step 4 of #784 will drop the TCP map +
|
falls back to TCP for them. Step 4 of #784 will drop the TCP map +
|
||||||
the harness's TCP bind once every agent's flipped.
|
the harness's TCP bind once every agent's flipped.
|
||||||
|
|
||||||
|
## Dashboard link shape (gateway vs direct)
|
||||||
|
|
||||||
|
When the gateway is in front, the SW4RM tab builds per-agent links
|
||||||
|
as same-origin `/agent/<name>/…` URLs instead of the legacy direct
|
||||||
|
`http://<host>:<container.port>/` TCP shape. The signal comes from
|
||||||
|
`StateSnapshot.gateway_enabled`, sourced from the
|
||||||
|
`HIVE_GATEWAY_ENABLED` env the c0re NixOS module sets when
|
||||||
|
`services.hyperhive.gateway.enable = true`. Three render sites
|
||||||
|
flip together: the primary agent-name link, the favicon fetch
|
||||||
|
(`<url>/icon`), and the nav-strip `container`-kind links from
|
||||||
|
`/api/agent/<name>/links`. `forge`-kind nav-strip links still
|
||||||
|
resolve against `http://<host>:3000` (separate sub-domain transition
|
||||||
|
tracked by `forge.behindGateway`); `external`-kind links are
|
||||||
|
already absolute. See `docs/web-ui.md::Container row` for the
|
||||||
|
frontend-side derivation.
|
||||||
|
|
||||||
## Sequencing history
|
## Sequencing history
|
||||||
|
|
||||||
- #15 v0 (per-agent routing, #740) — first sub-app behind the gateway, JSON port table from c0re.
|
- #15 v0 (per-agent routing, #740) — first sub-app behind the gateway, JSON port table from c0re.
|
||||||
|
|
|
||||||
|
|
@ -514,8 +514,18 @@ fetch entirely.
|
||||||
`GET /api/agent/{name}/links`, a same-origin passthrough proxy
|
`GET /api/agent/{name}/links`, a same-origin passthrough proxy
|
||||||
that forwards the agent's own link list; the agent backend is
|
that forwards the agent's own link list; the agent backend is
|
||||||
the single source of truth. The frontend resolves each
|
the single source of truth. The frontend resolves each
|
||||||
`AgentLink.kind` (`container` → `http://host:<container.port>`,
|
`AgentLink.kind` against a per-agent base URL that depends on
|
||||||
`forge` → `http://host:3000`, `external` → already absolute).
|
whether hive-gateway is in front (`StateSnapshot.gateway_enabled`,
|
||||||
|
sourced from the `HIVE_GATEWAY_ENABLED` env the c0re NixOS
|
||||||
|
module sets when `services.hyperhive.gateway.enable = true`).
|
||||||
|
Gateway-on (default): `container` → `/agent/<name>/<url>` (same
|
||||||
|
origin, gateway proxies to the per-agent harness — TCP or
|
||||||
|
unix-domain per #784). Gateway-off (legacy / local dev):
|
||||||
|
`container` → `http://<host>:<container.port>/<url>` (direct TCP
|
||||||
|
fallback). Forge links resolve against `http://<host>:3000`,
|
||||||
|
external links are already absolute. The same flag drives the
|
||||||
|
primary agent-name link + favicon fetch (`<url>/icon`), so the
|
||||||
|
whole row routes through the gateway as a unit.
|
||||||
**When the container is stopped** (`ContainerView.running = false`),
|
**When the container is stopped** (`ContainerView.running = false`),
|
||||||
the host clears live-only fields before emitting the state, so
|
the host clears live-only fields before emitting the state, so
|
||||||
the dashboard never renders stale data: the badge chain is
|
the dashboard never renders stale data: the badge chain is
|
||||||
|
|
|
||||||
|
|
@ -471,6 +471,13 @@ window.marked = marked;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostname = (s && s.hostname) || window.location.hostname;
|
const hostname = (s && s.hostname) || window.location.hostname;
|
||||||
|
// When hive-gateway is in front of the dashboard, build same-origin
|
||||||
|
// `/agent/<name>/` URLs instead of the direct `http://<host>:<port>/`
|
||||||
|
// TCP fallback — the gateway proxies the prefix to the per-agent
|
||||||
|
// harness (TCP via `agent-ports.json` or unix-domain via
|
||||||
|
// `agent-sockets.json` per #784 / #815). See
|
||||||
|
// `docs/web-ui.md::Container row` + `docs/gateway.md::Vhost map`.
|
||||||
|
const gatewayLinks = !!(s && s.gateway_enabled);
|
||||||
const ul = el('ul', { class: 'containers' });
|
const ul = el('ul', { class: 'containers' });
|
||||||
const tree = buildAgentTree(containers);
|
const tree = buildAgentTree(containers);
|
||||||
// In-flight rebuild / meta-update / destroy ops per agent name —
|
// In-flight rebuild / meta-update / destroy ops per agent name —
|
||||||
|
|
@ -480,7 +487,9 @@ window.marked = marked;
|
||||||
const inFlight = inFlightOpsByAgent();
|
const inFlight = inFlightOpsByAgent();
|
||||||
for (const node of tree) {
|
for (const node of tree) {
|
||||||
const c = node.container;
|
const c = node.container;
|
||||||
const url = `http://${hostname}:${c.port}/`;
|
const url = gatewayLinks
|
||||||
|
? `/agent/${encodeURIComponent(c.name)}/`
|
||||||
|
: `http://${hostname}:${c.port}/`;
|
||||||
// Pending-state derivation + queued-vs-running split — see
|
// Pending-state derivation + queued-vs-running split — see
|
||||||
// docs/web-ui.md::Container row for the transient → in-flight
|
// docs/web-ui.md::Container row for the transient → in-flight
|
||||||
// queue priority order and the opRunning rationale.
|
// queue priority order and the opRunning rationale.
|
||||||
|
|
@ -572,7 +581,13 @@ window.marked = marked;
|
||||||
const navStrip = el('span', { class: 'nav-strip' });
|
const navStrip = el('span', { class: 'nav-strip' });
|
||||||
head.append(navStrip);
|
head.append(navStrip);
|
||||||
const forgeBase = `http://${hostname}:3000`;
|
const forgeBase = `http://${hostname}:3000`;
|
||||||
const containerBase = `http://${hostname}:${c.port}`;
|
// Container nav-strip links resolve against the same base as the
|
||||||
|
// primary `name` link — gateway-prefixed when the gateway is in
|
||||||
|
// front, direct TCP otherwise. Strips the trailing slash so
|
||||||
|
// `lnk.url` starting with `/` concatenates cleanly.
|
||||||
|
const containerBase = gatewayLinks
|
||||||
|
? `/agent/${encodeURIComponent(c.name)}`
|
||||||
|
: `http://${hostname}:${c.port}`;
|
||||||
if (c.running) {
|
if (c.running) {
|
||||||
fetch(`/api/agent/${encodeURIComponent(c.name)}/links`)
|
fetch(`/api/agent/${encodeURIComponent(c.name)}/links`)
|
||||||
.then((r) => (r.ok ? r.json() : []))
|
.then((r) => (r.ok ? r.json() : []))
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,17 @@ struct StateSnapshot {
|
||||||
/// chrome so the `M4TR1X →` tab doesn't flash when the GUI is off
|
/// chrome so the `M4TR1X →` tab doesn't flash when the GUI is off
|
||||||
/// (#607, #634).
|
/// (#607, #634).
|
||||||
matrix_gui_enabled: bool,
|
matrix_gui_enabled: bool,
|
||||||
|
/// Whether `hive-gateway` is in front of this dashboard. Sourced
|
||||||
|
/// from `HIVE_GATEWAY_ENABLED` env var (set by the c0re NixOS
|
||||||
|
/// module when `services.hyperhive.gateway.enable` is on). When
|
||||||
|
/// true the dashboard frontend builds same-origin
|
||||||
|
/// `/agent/<name>/` links to the per-agent web UI (the gateway
|
||||||
|
/// proxies them via `agent-ports.json` + `agent-sockets.json`);
|
||||||
|
/// when false it falls back to direct
|
||||||
|
/// `http://<hostname>:<port>/` TCP links so gateway-off /
|
||||||
|
/// local-dev deploys keep working. See
|
||||||
|
/// `docs/gateway.md::Vhost map`.
|
||||||
|
gateway_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `OpQuestion` + computed `question_refs` / `answer_refs`. Built
|
/// `OpQuestion` + computed `question_refs` / `answer_refs`. Built
|
||||||
|
|
@ -435,6 +446,13 @@ async fn api_state(headers: HeaderMap, State(state): State<AppState>) -> axum::J
|
||||||
let s = v.to_string_lossy().to_ascii_lowercase();
|
let s = v.to_string_lossy().to_ascii_lowercase();
|
||||||
matches!(s.as_str(), "1" | "true" | "yes")
|
matches!(s.as_str(), "1" | "true" | "yes")
|
||||||
}),
|
}),
|
||||||
|
gateway_enabled: std::env::var_os("HIVE_GATEWAY_ENABLED").is_some_and(|v| {
|
||||||
|
// Same truthy-string parse as `matrix_gui_enabled`; the
|
||||||
|
// env var is set by the c0re NixOS module to the literal
|
||||||
|
// "1" when `services.hyperhive.gateway.enable` is on.
|
||||||
|
let s = v.to_string_lossy().to_ascii_lowercase();
|
||||||
|
matches!(s.as_str(), "1" | "true" | "yes")
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,15 @@ in
|
||||||
# dashboard chrome whether the GUI is reachable so iris's
|
# dashboard chrome whether the GUI is reachable so iris's
|
||||||
# `M4TR1X →` tab doesn't show when the GUI is off.
|
# `M4TR1X →` tab doesn't show when the GUI is off.
|
||||||
HIVE_MATRIX_GUI_ENABLED = "1";
|
HIVE_MATRIX_GUI_ENABLED = "1";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs config.services.hyperhive.gateway.enable {
|
||||||
|
# Availability flag for `/api/state.gateway_enabled`. When
|
||||||
|
# true the dashboard builds same-origin `/agent/<name>/` links
|
||||||
|
# to the per-agent web UI (gateway proxies them); when false
|
||||||
|
# it falls back to direct `http://<hostname>:<port>/` TCP
|
||||||
|
# links (legacy / gateway-disabled ops). See
|
||||||
|
# `docs/gateway.md::Vhost map` for the routing shape.
|
||||||
|
HIVE_GATEWAY_ENABLED = "1";
|
||||||
};
|
};
|
||||||
# Matrix GUI static serving lives entirely on the hive-gateway
|
# Matrix GUI static serving lives entirely on the hive-gateway
|
||||||
# nginx since #609 — when gateway is off the operator opts out
|
# nginx since #609 — when gateway is off the operator opts out
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue