job_queue: move the jobs wire types to hive-host-sock
The DagView / NodeView / Source / State / PermPayload types only ever travel on the host admin socket and the dashboard channels hive-c0re serves off the same snapshot; their whole consumer set is hive-c0re, hivectl and the socket protocol crate itself. Living in hive-sh4re made the five other crates that depend on it carry job-queue types they never name. Pure move: git mv of the module plus the import sweep, no type changes. hive-sh4re keeps its own chrono (wire_time still needs it).
This commit is contained in:
parent
8a7a450240
commit
a8728ac532
8 changed files with 39 additions and 27 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1694,6 +1694,7 @@ dependencies = [
|
||||||
name = "hive-host-sock"
|
name = "hive-host-sock"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"hive-sh4re",
|
"hive-sh4re",
|
||||||
"hive-types",
|
"hive-types",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,10 @@ use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use hive_host_sock::jobs::NodeView;
|
||||||
use hive_jobq::resources::ResourceTable;
|
use hive_jobq::resources::ResourceTable;
|
||||||
use hive_jobq::scheduler::{Outcome, Scheduler};
|
use hive_jobq::scheduler::{Outcome, Scheduler};
|
||||||
use hive_jobq::{Dep, DepWhen as JobDepWhen, Graph, NodeId, State as JobState};
|
use hive_jobq::{Dep, DepWhen as JobDepWhen, Graph, NodeId, State as JobState};
|
||||||
use hive_sh4re::jobs::NodeView;
|
|
||||||
use hive_sh4re::wire_time::now_unix;
|
use hive_sh4re::wire_time::now_unix;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
//! Data model for the generic job-DAG queue: node kinds (the primitive
|
//! Data model for the generic job-DAG queue: node kinds (the primitive
|
||||||
//! operations), dependency edges, and the runtime `Dag` / `Node` store.
|
//! operations), dependency edges, and the runtime `Dag` / `Node` store.
|
||||||
//! The serialized *views* — `DagView` / `NodeView` plus the `Template`
|
//! The serialized *views* — `DagView` / `NodeView` plus the `Source` /
|
||||||
//! / `Source` / `State` / `PermPayload` wire enums — live in
|
//! `State` / `PermPayload` wire enums — live in `hive_host_sock::jobs`
|
||||||
//! `hive_sh4re::jobs` (wire types belong to the shared crate) and are
|
//! (they travel on the host admin socket and the dashboard channels
|
||||||
//! re-exported here for the queue's internal use.
|
//! served off the same snapshot, and nowhere else) and are re-exported
|
||||||
|
//! here for the queue's internal use.
|
||||||
//!
|
//!
|
||||||
//! Two levels: the **DAG** is the unit of cancel / approval-resolution
|
//! Two levels: the **DAG** is the unit of cancel / approval-resolution
|
||||||
//! and the dashboard group; the **node** is the unit of scheduling /
|
//! and the dashboard group; the **node** is the unit of scheduling /
|
||||||
|
|
@ -11,7 +12,7 @@
|
||||||
//! DAG can span agents). See `docs/coordinator.md::Job queue` for the
|
//! DAG can span agents). See `docs/coordinator.md::Job queue` for the
|
||||||
//! full design.
|
//! full design.
|
||||||
|
|
||||||
pub use hive_sh4re::jobs::{DagView, NodeId, PermPayload, Source, State};
|
pub use hive_host_sock::jobs::{DagView, NodeId, PermPayload, Source, State};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::coordinator::TransientKind;
|
use crate::coordinator::TransientKind;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ readme = "README.md"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono.workspace = true
|
||||||
hive-sh4re.workspace = true
|
hive-sh4re.workspace = true
|
||||||
hive-types.workspace = true
|
hive-types.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
||||||
201
hive-host-sock/src/jobs.rs
Normal file
201
hive-host-sock/src/jobs.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
//! Wire shapes of hive-c0re's job-DAG queue: what a queued job looks
|
||||||
|
//! like on the dashboard SSE channel (`rebuild_queue_changed`), the
|
||||||
|
//! `/api/state.rebuild_queue` snapshot, and the host admin socket's
|
||||||
|
//! `QueueDag` polling surface (`hivectl`'s wait/progress loop). The
|
||||||
|
//! queue *internals* — node kinds, dependency edges, scheduling state —
|
||||||
|
//! live in `hive-c0re::job_queue`; these are the serialized views it
|
||||||
|
//! produces. Semantics: `docs/coordinator.md::Job queue`.
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Where the submit request originated — drives the "why" chip on the
|
||||||
|
/// dashboard.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum Source {
|
||||||
|
/// Operator action (dashboard button, CLI, manager tool).
|
||||||
|
Manual,
|
||||||
|
/// Meta-update cascade rebuild (grown into the meta-update DAG).
|
||||||
|
MetaUpdate,
|
||||||
|
/// Boot-time submission (the boot sweep DAG + boot reconciles).
|
||||||
|
AutoUpdate,
|
||||||
|
/// Crash recovery path (future use).
|
||||||
|
CrashRecover,
|
||||||
|
/// Operator approved a pending `Approval` row; `approval_id` on
|
||||||
|
/// the DAG points back at the source row.
|
||||||
|
Approval,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Source::Manual => "manual",
|
||||||
|
Source::MetaUpdate => "meta_update",
|
||||||
|
Source::AutoUpdate => "auto_update",
|
||||||
|
Source::CrashRecover => "crash_recover",
|
||||||
|
Source::Approval => "approval",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lifecycle state of a node — and, rolled up, of a DAG.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum State {
|
||||||
|
Queued,
|
||||||
|
Running,
|
||||||
|
Done,
|
||||||
|
Failed,
|
||||||
|
Cancelled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_terminal(self) -> bool {
|
||||||
|
matches!(self, State::Done | State::Failed | State::Cancelled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kind-specific payload for `Template::PermChange` DAGs.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
|
pub enum PermPayload {
|
||||||
|
/// Set the tool groups for one agent (`tool-groups.json`).
|
||||||
|
ToolGroups { groups: Vec<String> },
|
||||||
|
/// Set the capabilities for one agent (`capabilities.json`).
|
||||||
|
Capabilities { caps: Vec<String> },
|
||||||
|
/// Set both perm-types in one entry — the batch
|
||||||
|
/// `POST /api/permissions` path. `None` leaves that file untouched;
|
||||||
|
/// present fields commit together and rebuild once.
|
||||||
|
Combined {
|
||||||
|
groups: Option<Vec<String>>,
|
||||||
|
caps: Option<Vec<String>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node id. Carries the scheduler crate's globally-monotonic node id
|
||||||
|
/// (`hive_jobq::NodeId`) verbatim on the wire — unique across all DAGs, not
|
||||||
|
/// just within one. Consumers treat it opaquely (grouping + dep matching),
|
||||||
|
/// so the widening from the old dag-local `u32` is transparent.
|
||||||
|
pub type NodeId = u64;
|
||||||
|
|
||||||
|
/// One node of a queued DAG, serialized near-raw from the scheduler
|
||||||
|
/// graph. Lifecycle (`state` / `started_at` / `finished_at` / `error`)
|
||||||
|
/// comes straight off the `hive_jobq::Node`. The client derives DAG-level
|
||||||
|
/// roll-ups (label, state, timestamps) from the node set — nothing is
|
||||||
|
/// rolled up host-side. Build logs are fetched on demand by node id
|
||||||
|
/// (`GET /api/build-log/<id>`), not carried inline.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct NodeView {
|
||||||
|
pub id: NodeId,
|
||||||
|
/// The agent whose container (or meta repo, for `hyperhive` meta-level
|
||||||
|
/// nodes) this node operates on. Agent is per-node — a single DAG can
|
||||||
|
/// span multiple agents (e.g. a hive-wide restart), so there is no
|
||||||
|
/// DAG-level agent field; consumers group by this.
|
||||||
|
pub agent: String,
|
||||||
|
/// Node primitive tag: `"prebuild"`, `"stop_for_update"`,
|
||||||
|
/// `"swap"`, `"create"`, `"meta_lock"`, `"reconcile"`, `"signal"`,
|
||||||
|
/// `"drain"`, `"write_dropin"`, `"write_perm_file"`,
|
||||||
|
/// `"approval_deploy"`.
|
||||||
|
pub kind: String,
|
||||||
|
/// Ids of the nodes this one waits for. May reference an already-`Done`
|
||||||
|
/// node that's been filtered out of the wire — the client treats a dep
|
||||||
|
/// on an absent node as satisfied.
|
||||||
|
#[serde(default)]
|
||||||
|
pub deps: Vec<NodeId>,
|
||||||
|
pub state: State,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub started_at: Option<DateTime<Utc>>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub finished_at: Option<DateTime<Utc>>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub error: Option<String>,
|
||||||
|
/// Approval-queue row id — present only on the `approval_deploy` node.
|
||||||
|
/// The client links a DAG to its pending approval through this (it is
|
||||||
|
/// not derivable from the graph, so it rides the node that owns it).
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub approval_id: Option<i64>,
|
||||||
|
/// Meta-flake inputs being bumped — present only on the `meta_lock`
|
||||||
|
/// node. Display-only payload, not derivable from the graph.
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub inputs: Vec<String>,
|
||||||
|
/// Whether this node has a captured build log fetchable at
|
||||||
|
/// `GET /api/build-log/<id>`. 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,
|
||||||
|
/// 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
|
||||||
|
/// recursive tree rather than inferring structure from `deps` alone.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub parent: Option<NodeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A queued / running / failed DAG — a thin projection of one container
|
||||||
|
/// node plus its (non-`Done`) subtree from the scheduler graph. Only
|
||||||
|
/// non-derivable facts live here: `id`, `source`, `reason`, `created_at`,
|
||||||
|
/// and the node set. The client derives the card label, roll-up state, and
|
||||||
|
/// DAG timestamps from `nodes` (per-node `kind` + lifecycle) — nothing is
|
||||||
|
/// rolled up host-side. There is no DAG-level `agent`: a DAG can span
|
||||||
|
/// agents, so agent is per-[`NodeView`]; consumers group by `NodeView::agent`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DagView {
|
||||||
|
pub id: u64,
|
||||||
|
pub source: Source,
|
||||||
|
pub reason: String,
|
||||||
|
/// When the DAG was enqueued.
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
/// When the DAG's first node started (min over *all* its nodes) — computed
|
||||||
|
/// host-side, **not** derived on the client: `Done` nodes are excluded from
|
||||||
|
/// `nodes` below, so the earliest-started node is usually absent from the
|
||||||
|
/// wire and the client can't take the min itself. `None` until a node runs.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub started_at: Option<DateTime<Utc>>,
|
||||||
|
/// When the DAG finished (max `finished_at` over all its nodes), set only
|
||||||
|
/// once the DAG has settled terminal. Host-computed for the same reason as
|
||||||
|
/// `started_at`. `None` while the DAG is still live.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub finished_at: Option<DateTime<Utc>>,
|
||||||
|
/// Nodes of this DAG with `Done` ones excluded. A DAG whose nodes are
|
||||||
|
/// all `Done` is omitted from the snapshot entirely; a `Failed` DAG
|
||||||
|
/// lingers until aged out by the history cap.
|
||||||
|
pub nodes: Vec<NodeView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DagView {
|
||||||
|
/// Roll-up state derived from the node set — the shared derivation every
|
||||||
|
/// Rust consumer (hivectl, the wait loops, tests) uses so the dashboard's
|
||||||
|
/// JS render and the host agree: `Failed` if any node failed, else
|
||||||
|
/// `Running` if any running, else `Queued` if any queued, else
|
||||||
|
/// `Cancelled` if any cancelled, else `Done`. `Done` nodes are excluded
|
||||||
|
/// from the wire, so a DAG that is *entirely* done isn't sent at all —
|
||||||
|
/// its absence from the snapshot is what signals completion.
|
||||||
|
#[must_use]
|
||||||
|
pub fn rollup_state(&self) -> State {
|
||||||
|
let mut any_running = false;
|
||||||
|
let mut any_queued = false;
|
||||||
|
let mut any_cancelled = false;
|
||||||
|
for n in &self.nodes {
|
||||||
|
match n.state {
|
||||||
|
State::Failed => return State::Failed,
|
||||||
|
State::Running => any_running = true,
|
||||||
|
State::Queued => any_queued = true,
|
||||||
|
State::Cancelled => any_cancelled = true,
|
||||||
|
State::Done => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if any_running {
|
||||||
|
State::Running
|
||||||
|
} else if any_queued {
|
||||||
|
State::Queued
|
||||||
|
} else if any_cancelled {
|
||||||
|
State::Cancelled
|
||||||
|
} else {
|
||||||
|
State::Done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,16 +2,22 @@
|
||||||
//!
|
//!
|
||||||
//! The host-control protocol spoken between `hivectl` and the `hive-c0re`
|
//! The host-control protocol spoken between `hivectl` and the `hive-c0re`
|
||||||
//! daemon. Re-homed out of `hive-sh4re` so a standalone `hivectl` can depend
|
//! daemon. Re-homed out of `hive-sh4re` so a standalone `hivectl` can depend
|
||||||
//! on just this protocol crate instead of the whole daemon crate. The shared
|
//! on just this protocol crate instead of the whole daemon crate. The
|
||||||
//! payload types it references (`Approval`, `AgentStatusRow`, `jobs::DagView`)
|
//! job-queue wire types ([`jobs`]) travel on this socket and the dashboard
|
||||||
//! stay in `hive-sh4re`.
|
//! channels `hive-c0re` serves off the same snapshot, and on nothing else —
|
||||||
|
//! their whole consumer set is `hive-c0re` + `hivectl` + this crate — so they
|
||||||
|
//! live here rather than in the shared crate. The payload types genuinely
|
||||||
|
//! shared with the *agent* sockets (`Approval`, `AgentStatusRow`) stay in
|
||||||
|
//! `hive-sh4re`.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use hive_sh4re::{AgentStatusRow, Approval, jobs};
|
use hive_sh4re::{AgentStatusRow, Approval};
|
||||||
use hive_types::Ident;
|
use hive_types::Ident;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod jobs;
|
||||||
|
|
||||||
// ── Shared hive layout facts ──────────────────────────────────────────────
|
// ── Shared hive layout facts ──────────────────────────────────────────────
|
||||||
// Paths + names both the `hive-c0re` daemon and the host-side `hivectl` CLI
|
// Paths + names both the `hive-c0re` daemon and the host-side `hivectl` CLI
|
||||||
// must agree on. Homed here (the protocol crate both sides already depend on)
|
// must agree on. Homed here (the protocol crate both sides already depend on)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use hive_types::Ident;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
pub mod jobs;
|
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod wire_time;
|
pub mod wire_time;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ async fn wait_for_dags_plain(socket: &Path, ids: Vec<u64>) -> Result<()> {
|
||||||
// may still be running — keep watching so the operator
|
// may still be running — keep watching so the operator
|
||||||
// sees whether the agent came back.
|
// sees whether the agent came back.
|
||||||
if d.nodes.iter().all(|n| n.state.is_terminal()) {
|
if d.nodes.iter().all(|n| n.state.is_terminal()) {
|
||||||
if d.rollup_state() == hive_sh4re::jobs::State::Failed {
|
if d.rollup_state() == hive_host_sock::jobs::State::Failed {
|
||||||
failed.push(format!("{} {}", d.source.as_str(), dag_agents(d)));
|
failed.push(format!("{} {}", d.source.as_str(), dag_agents(d)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -104,9 +104,9 @@ async fn wait_for_dags_animated(socket: &Path, ids: Vec<u64>) -> Result<()> {
|
||||||
// insertion order groups a DAG's nodes right under its header.
|
// insertion order groups a DAG's nodes right under its header.
|
||||||
let mut dag_bars: std::collections::HashMap<u64, ProgressBar> =
|
let mut dag_bars: std::collections::HashMap<u64, ProgressBar> =
|
||||||
std::collections::HashMap::new();
|
std::collections::HashMap::new();
|
||||||
let mut node_bars: std::collections::HashMap<(u64, hive_sh4re::jobs::NodeId), ProgressBar> =
|
let mut node_bars: std::collections::HashMap<(u64, hive_host_sock::jobs::NodeId), ProgressBar> =
|
||||||
std::collections::HashMap::new();
|
std::collections::HashMap::new();
|
||||||
let mut node_done: std::collections::HashSet<(u64, hive_sh4re::jobs::NodeId)> =
|
let mut node_done: std::collections::HashSet<(u64, hive_host_sock::jobs::NodeId)> =
|
||||||
std::collections::HashSet::new();
|
std::collections::HashSet::new();
|
||||||
|
|
||||||
let mut pending: std::collections::BTreeSet<u64> = ids.into_iter().collect();
|
let mut pending: std::collections::BTreeSet<u64> = ids.into_iter().collect();
|
||||||
|
|
@ -163,7 +163,7 @@ async fn wait_for_dags_animated(socket: &Path, ids: Vec<u64>) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.nodes.iter().all(|n| n.state.is_terminal()) {
|
if d.nodes.iter().all(|n| n.state.is_terminal()) {
|
||||||
if d.rollup_state() == hive_sh4re::jobs::State::Failed {
|
if d.rollup_state() == hive_host_sock::jobs::State::Failed {
|
||||||
failed.push(format!("{} {}", d.source.as_str(), dag_agents(d)));
|
failed.push(format!("{} {}", d.source.as_str(), dag_agents(d)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -205,7 +205,7 @@ fn finish_wait(mut failed: Vec<String>) -> Result<()> {
|
||||||
/// Distinct agents across a DAG's nodes, comma-joined for display — the
|
/// Distinct agents across a DAG's nodes, comma-joined for display — the
|
||||||
/// per-node replacement for the old DAG-level `agent` field. Single-agent
|
/// per-node replacement for the old DAG-level `agent` field. Single-agent
|
||||||
/// DAGs render one name; a hive-wide DAG lists each.
|
/// DAGs render one name; a hive-wide DAG lists each.
|
||||||
fn dag_agents(d: &hive_sh4re::jobs::DagView) -> String {
|
fn dag_agents(d: &hive_host_sock::jobs::DagView) -> String {
|
||||||
let mut seen: Vec<&str> = Vec::new();
|
let mut seen: Vec<&str> = Vec::new();
|
||||||
for n in &d.nodes {
|
for n in &d.nodes {
|
||||||
if !seen.contains(&n.agent.as_str()) {
|
if !seen.contains(&n.agent.as_str()) {
|
||||||
|
|
@ -227,7 +227,7 @@ fn now_unix() -> i64 {
|
||||||
/// Elapsed seconds for a DAG: `started_at` (falling back to `created_at`)
|
/// Elapsed seconds for a DAG: `started_at` (falling back to `created_at`)
|
||||||
/// through `finished_at` or `now`. The wire carries these as RFC3339
|
/// through `finished_at` or `now`. The wire carries these as RFC3339
|
||||||
/// `DateTime<Utc>`; compare in unix seconds against `now`.
|
/// `DateTime<Utc>`; compare in unix seconds against `now`.
|
||||||
fn dag_elapsed(d: &hive_sh4re::jobs::DagView, now: i64) -> i64 {
|
fn dag_elapsed(d: &hive_host_sock::jobs::DagView, now: i64) -> i64 {
|
||||||
let start = d
|
let start = d
|
||||||
.started_at
|
.started_at
|
||||||
.map_or_else(|| d.created_at.timestamp(), |t| t.timestamp());
|
.map_or_else(|| d.created_at.timestamp(), |t| t.timestamp());
|
||||||
|
|
@ -236,7 +236,7 @@ fn dag_elapsed(d: &hive_sh4re::jobs::DagView, now: i64) -> i64 {
|
||||||
|
|
||||||
/// Elapsed seconds for a node: `started_at` → `finished_at`/`now`, or 0
|
/// Elapsed seconds for a node: `started_at` → `finished_at`/`now`, or 0
|
||||||
/// when it hasn't started.
|
/// when it hasn't started.
|
||||||
fn node_elapsed(n: &hive_sh4re::jobs::NodeView, now: i64) -> i64 {
|
fn node_elapsed(n: &hive_host_sock::jobs::NodeView, now: i64) -> i64 {
|
||||||
match n.started_at {
|
match n.started_at {
|
||||||
Some(start) => (n.finished_at.map_or(now, |t| t.timestamp()) - start.timestamp()).max(0),
|
Some(start) => (n.finished_at.map_or(now, |t| t.timestamp()) - start.timestamp()).max(0),
|
||||||
None => 0,
|
None => 0,
|
||||||
|
|
@ -255,7 +255,11 @@ fn fmt_dur(secs: i64) -> String {
|
||||||
|
|
||||||
/// One animated node line: kind, an `(after …)` marker for a fan-in node
|
/// One animated node line: kind, an `(after …)` marker for a fan-in node
|
||||||
/// (>1 dep), its elapsed timer, and a truncated error tail.
|
/// (>1 dep), its elapsed timer, and a truncated error tail.
|
||||||
fn node_line(d: &hive_sh4re::jobs::DagView, n: &hive_sh4re::jobs::NodeView, now: i64) -> String {
|
fn node_line(
|
||||||
|
d: &hive_host_sock::jobs::DagView,
|
||||||
|
n: &hive_host_sock::jobs::NodeView,
|
||||||
|
now: i64,
|
||||||
|
) -> String {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
let mut s = n.kind.clone();
|
let mut s = n.kind.clone();
|
||||||
if n.deps.len() > 1 {
|
if n.deps.len() > 1 {
|
||||||
|
|
@ -280,13 +284,13 @@ fn node_line(d: &hive_sh4re::jobs::DagView, n: &hive_sh4re::jobs::NodeView, now:
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_glyph(state: hive_sh4re::jobs::State) -> &'static str {
|
fn state_glyph(state: hive_host_sock::jobs::State) -> &'static str {
|
||||||
match state {
|
match state {
|
||||||
hive_sh4re::jobs::State::Queued => "⏸",
|
hive_host_sock::jobs::State::Queued => "⏸",
|
||||||
hive_sh4re::jobs::State::Running => "▶",
|
hive_host_sock::jobs::State::Running => "▶",
|
||||||
hive_sh4re::jobs::State::Done => "✔",
|
hive_host_sock::jobs::State::Done => "✔",
|
||||||
hive_sh4re::jobs::State::Failed => "✖",
|
hive_host_sock::jobs::State::Failed => "✖",
|
||||||
hive_sh4re::jobs::State::Cancelled => "⊘",
|
hive_host_sock::jobs::State::Cancelled => "⊘",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,7 +298,7 @@ fn state_glyph(state: hive_sh4re::jobs::State) -> &'static str {
|
||||||
/// node chain — the CLI twin of the dashboard's queue card. The header shows
|
/// node chain — the CLI twin of the dashboard's queue card. The header shows
|
||||||
/// what the backend sends (`source` + the raw node kinds); only the roll-up
|
/// what the backend sends (`source` + the raw node kinds); only the roll-up
|
||||||
/// state glyph is derived from the node set. Used by the plain (non-TTY) path.
|
/// state glyph is derived from the node set. Used by the plain (non-TTY) path.
|
||||||
fn render_dag_line(d: &hive_sh4re::jobs::DagView) -> String {
|
fn render_dag_line(d: &hive_host_sock::jobs::DagView) -> String {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
let mut out = format!(
|
let mut out = format!(
|
||||||
"{} {} {:<12}",
|
"{} {} {:<12}",
|
||||||
|
|
@ -315,7 +319,7 @@ fn render_dag_line(d: &hive_sh4re::jobs::DagView) -> String {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use hive_sh4re::jobs::{DagView, NodeView, Source, State};
|
use hive_host_sock::jobs::{DagView, NodeView, Source, State};
|
||||||
use hive_sh4re::wire_time::from_secs;
|
use hive_sh4re::wire_time::from_secs;
|
||||||
|
|
||||||
use super::render_dag_line;
|
use super::render_dag_line;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue