horto_os_ui_shared/remote/runner/
status.rs1use super::super::process::ProcessRunner;
4use super::options::RemoteOptions;
5use super::probe::remote_box_snapshot;
6use crate::error::Result;
7
8pub fn parse_remote_json<T: serde::de::DeserializeOwned>(raw: &str) -> Result<T> {
14 let start = raw.find('{').or_else(|| raw.find('[')).ok_or_else(|| {
15 crate::error::HortoError::msg("remote output had no JSON object or array")
16 })?;
17 serde_json::from_str(raw[start..].trim())
18 .map_err(|e| crate::error::HortoError::msg(format!("parse remote JSON: {e}")))
19}
20
21pub fn parse_setup_status_text(raw: &str) -> Result<crate::ops::status::SetupStatusReport> {
23 use crate::ops::status::{SetupStatusReport, StepStatusRow};
24 let mut kind = "full".to_owned();
25 let mut steps = Vec::new();
26 for line in raw.lines() {
27 let line = line.trim();
28 if let Some(rest) = line.strip_prefix("Setup kind:") {
29 rest.trim().clone_into(&mut kind);
30 continue;
31 }
32 let Some(rest) = line.strip_prefix('[') else {
33 continue;
34 };
35 let Some((status_part, after_status)) = rest.split_once(']') else {
36 continue;
37 };
38 let status = status_part.trim().to_owned();
39 let after_status = after_status.trim();
40 let Some((id, after_id)) = after_status.split_once(" - ") else {
41 continue;
42 };
43 let (title, ver_flags) = after_id
44 .rfind(" (v")
45 .map_or((after_id, ""), |i| (&after_id[..i], &after_id[i..]));
46 let step_version = ver_flags
47 .trim_start_matches(" (v")
48 .split(')')
49 .next()
50 .and_then(|s| s.parse().ok())
51 .unwrap_or(0);
52 steps.push(StepStatusRow {
53 id: id.trim().to_owned(),
54 title: title.trim().to_owned(),
55 status,
56 step_version,
57 destructive: ver_flags.contains("[destructive]"),
58 needs_reboot_after: ver_flags.contains("[reboot]"),
59 });
60 }
61 if steps.is_empty() {
62 return Err(crate::error::HortoError::msg(
63 "remote setup status produced no step lines",
64 ));
65 }
66 Ok(SetupStatusReport { kind, steps })
67}
68
69pub fn remote_setup_status(
75 runner: &dyn ProcessRunner,
76 opts: &RemoteOptions,
77 full: bool,
78) -> Result<crate::ops::status::SetupStatusReport> {
79 let snap = remote_box_snapshot(runner, opts, full)?;
80 snap.setup.ok_or_else(|| {
81 crate::error::HortoError::msg(
82 "box CLI missing or outdated; sync CLI (s0) before setup status",
83 )
84 })
85}
86
87pub fn remote_doctor(
93 runner: &dyn ProcessRunner,
94 opts: &RemoteOptions,
95) -> Result<crate::ops::doctor::DoctorReport> {
96 let snap = remote_box_snapshot(runner, opts, true)?;
97 snap.doctor.ok_or_else(|| {
98 crate::error::HortoError::msg("box CLI missing or outdated; sync CLI (s0) before doctor")
99 })
100}