diff --git a/frontend/packages/dashboard/src/builds.js b/frontend/packages/dashboard/src/builds.js index 6b161ab7..b3c5fc0f 100644 --- a/frontend/packages/dashboard/src/builds.js +++ b/frontend/packages/dashboard/src/builds.js @@ -376,12 +376,17 @@ function renderQueueEntry(entry) { chip.append(el('span', { class: 'rqe-node-agent' }, ' · ' + n.agent)); } row.append(chip); - if (n.has_log) { + if (n.build_log_id != null) { + // Deep-links into the BUILD L0GS tab's rich view (auto-expands + + // scrolls to this row there, see fetchBuild's `?id=N` handling) + // rather than the raw-text download endpoint — a plain download + // is surprising here since nothing about a printer-glyph icon + // says "this leaves the app". The raw download is still one + // click away once on that row. row.append(el('a', { class: 'rqe-log-link rqe-node-log', - href: '/api/build-log/' + n.id + '/raw', - target: '_blank', - title: 'download build log for ' + n.kind + ' node', + href: '/builds.html?id=' + n.build_log_id + '#buildlogs', + title: 'view build log for ' + n.kind + ' node', }, '⎙')); } treeRoot.append(row); @@ -439,11 +444,11 @@ function clearLiveLogPoll() { } // First (entry, node) pair with a running node that has a log. -// Gate on has_log so lock/noop/store-only nodes don't open a blank panel. +// Gate on build_log_id so lock/noop/store-only nodes don't open a blank panel. function findLiveBuild(queue) { for (const e of queue || []) { if (rollupState(e.nodes || []) !== 'Running') continue; - const node = (e.nodes || []).find((n) => n.state === 'Running' && n.has_log); + const node = (e.nodes || []).find((n) => n.state === 'Running' && n.build_log_id != null); if (node) return { entry: e, node }; } return null; diff --git a/hive-c0re/src/dashboard/build_logs.rs b/hive-c0re/src/dashboard/build_logs.rs index 7c741a52..27489263 100644 --- a/hive-c0re/src/dashboard/build_logs.rs +++ b/hive-c0re/src/dashboard/build_logs.rs @@ -130,9 +130,11 @@ pub(super) async fn get_build_log_full( /// `GET /api/build-log/{node_id}` — the build log for a **queue node**, /// resolved node id → log-row id → full log. Same `BuildLogFull` JSON /// (`stdout` / `stderr` + header) as `get_build_log_full`; HTTP 404 when the -/// node has no linked log (the client gates the request on `NodeView.has_log`, -/// but a vacuum race can still 404). This is the on-demand log fetch the -/// raw-graph dashboard uses instead of an inline `build_log_id` on the wire. +/// node has no linked log (the client gates the request on +/// `NodeView.build_log_id`, but a vacuum race can still 404). This is the +/// on-demand live-log-panel fetch, distinct from the `build_log_id` on the +/// wire — that id is for deep-linking to the BUILD L0GS tab's full history +/// view, not for fetching the log content itself. #[utoipa::path( get, path = "/api/build-log/{node_id}", diff --git a/hive-c0re/src/job_queue/mod.rs b/hive-c0re/src/job_queue/mod.rs index 1bce674f..2af5c84e 100644 --- a/hive-c0re/src/job_queue/mod.rs +++ b/hive-c0re/src/job_queue/mod.rs @@ -559,7 +559,7 @@ impl QueueInner { } else { Vec::new() }; - let has_log = self.node_rt.get(&id).and_then(|r| r.build_log_id).is_some(); + let build_log_id = self.node_rt.get(&id).and_then(|r| r.build_log_id); // `node.parent` is the structural jobq parent. Top-level nodes // have `parent == Some(container)` (direct children of the Dag // container); those become `parent: None` on the wire since the @@ -580,7 +580,7 @@ impl QueueInner { error: node.error.clone(), approval_id, inputs, - has_log, + build_log_id, parent, }); } diff --git a/hive-host-sock/src/jobs.rs b/hive-host-sock/src/jobs.rs index d8d568e4..feba036f 100644 --- a/hive-host-sock/src/jobs.rs +++ b/hive-host-sock/src/jobs.rs @@ -105,12 +105,14 @@ pub struct NodeView { /// node. Display-only payload, not derivable from the graph. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub inputs: Vec, - /// Whether this node has a captured build log fetchable at - /// `GET /api/build-log/`. Only the nix-heavy nodes that stream build - /// output set one; the client gates its log link on this so lock / noop / - /// store-only nodes don't render a link that 404s. - #[serde(default)] - pub has_log: bool, + /// The build-log history row id, when this node has a captured build + /// log fetchable at `GET /api/build-log/` and deep-linkable to + /// `/builds.html?id=#buildlogs`. Only the nix-heavy nodes that + /// stream build output set one; the client gates its log link on + /// `.is_some()` so lock / noop / store-only nodes don't render a link + /// that 404s. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub build_log_id: Option, /// Structural parent in the jobq tree — `None` for top-level nodes /// (direct children of the DAG container). Sub-nodes carry the id of /// their containing parent node. The client uses this to render the diff --git a/hivectl/src/dag_progress.rs b/hivectl/src/dag_progress.rs index cfe658b1..aaca5710 100644 --- a/hivectl/src/dag_progress.rs +++ b/hivectl/src/dag_progress.rs @@ -342,7 +342,7 @@ mod tests { error: None, approval_id: None, inputs: vec![], - has_log: false, + build_log_id: None, } }