lightshuttle_runtime/lifecycle/view.rs
1//! Lightweight value types exposed by the control plane.
2//!
3//! Built on demand by [`crate::ManagerHandle`] and surfaced through the
4//! [`crate::LifecycleHandle`] trait. The shape is intentionally stable across
5//! runtime backends so callers (REST API, dashboard, CLI) never see
6//! backend-specific types.
7//!
8//! [`ResourceView`] is the main carrier: it flattens [`crate::NodeStatus`]
9//! into the coarser [`ResourceStatus`] and adds display-friendly fields such
10//! as the resolved image reference and a last-error string.
11
12use std::time::SystemTime;
13
14use serde::{Deserialize, Serialize};
15
16use crate::lifecycle::status::NodeStatus;
17use lightshuttle_spec::ImageSource;
18
19/// Dashboard-friendly view of a single managed resource.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct ResourceView {
22 /// Manifest-declared resource name.
23 pub name: String,
24 /// Resource kind discriminant (`postgres`, `redis`, `container`, `dockerfile`).
25 pub kind: String,
26 /// Coarse-grained status suitable for UI rendering.
27 pub status: ResourceStatus,
28 /// Whether the resource passed its healthcheck.
29 pub healthy: bool,
30 /// Container image reference, as resolved at start time.
31 pub image: String,
32 /// Wall-clock time at which the runtime accepted the start request.
33 pub started_at: Option<SystemTime>,
34 /// Last terminal failure reason, when applicable.
35 pub last_error: Option<String>,
36}
37
38/// Coarse-grained resource status, derived from [`NodeStatus`] and
39/// flattened for UI consumption.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41pub enum ResourceStatus {
42 /// Not started yet.
43 Pending,
44 /// Container is booting up.
45 Starting,
46 /// Container is up, with or without a green healthcheck.
47 Running,
48 /// Resource has entered a terminal failure state.
49 Failed,
50 /// Resource has been stopped on request.
51 Stopped,
52}
53
54impl From<&NodeStatus> for ResourceStatus {
55 fn from(status: &NodeStatus) -> Self {
56 match status {
57 NodeStatus::Pending => Self::Pending,
58 NodeStatus::Starting => Self::Starting,
59 NodeStatus::Running | NodeStatus::Healthy => Self::Running,
60 NodeStatus::Failed { .. } => Self::Failed,
61 NodeStatus::Stopped => Self::Stopped,
62 }
63 }
64}
65
66/// Render an [`ImageSource`] as the user-facing image reference: the
67/// pulled image string for `Pull`, the produced tag for `Build`.
68pub(crate) fn image_label(src: &ImageSource) -> String {
69 match src {
70 ImageSource::Pull(s) => s.clone(),
71 ImageSource::Build { tag, .. } => tag.clone(),
72 }
73}
74
75/// Extract the last error message from a [`NodeStatus`], when terminal.
76pub(crate) fn last_error_from(status: &NodeStatus) -> Option<String> {
77 match status {
78 NodeStatus::Failed { reason } => Some(reason.clone()),
79 _ => None,
80 }
81}