systemd: fetch unit state for local + remote nspawn containers (step 5)
This commit is contained in:
parent
2ab2af8a23
commit
5676b1ac62
3 changed files with 192 additions and 70 deletions
|
|
@ -6,11 +6,13 @@
|
||||||
// to require QuickControls2.prl files that nixpkgs strips. Switch to
|
// to require QuickControls2.prl files that nixpkgs strips. Switch to
|
||||||
// QList<QVariantMap> when a release ships with both fixes.
|
// QList<QVariantMap> when a release ships with both fixes.
|
||||||
//
|
//
|
||||||
// Local data uses native zbus over the system + session buses. Remote data is
|
// Local host data uses native zbus over the system + session buses. Everything
|
||||||
// fetched by spawning `busctl --host=<target>`, which tunnels sd-bus through
|
// else (local nspawn containers, configured remote hosts, and remote nspawn
|
||||||
// SSH using whatever the user already has set up in `~/.ssh/config`. We parse
|
// containers) is fetched by spawning `busctl` with the right combination of
|
||||||
// busctl's JSON output with serde_json. Remote fetches only happen while the
|
// `--host=<ssh-target>` and `--machine=<container>` flags. busctl uses
|
||||||
// applet is open (toggled via `setAppletOpen`).
|
// systemd-machined's OpenMachine() to tunnel sd-bus into a container, and ssh
|
||||||
|
// to reach a remote host. Remote fetches only happen while the applet is open
|
||||||
|
// (toggled via `setAppletOpen`).
|
||||||
|
|
||||||
use crate::modules_service;
|
use crate::modules_service;
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
|
|
@ -37,14 +39,15 @@ pub mod qobject {
|
||||||
#[qproperty(QString, hostname)]
|
#[qproperty(QString, hostname)]
|
||||||
// Local failed unit count (drives the bar module label).
|
// Local failed unit count (drives the bar module label).
|
||||||
#[qproperty(i32, failed_count, cxx_name = "failedCount")]
|
#[qproperty(i32, failed_count, cxx_name = "failedCount")]
|
||||||
// JSON array, local first then nspawn containers then configured remotes.
|
// Recursive JSON tree. Top level is [local, remote1, remote2, ...].
|
||||||
// Each entry:
|
// Each entry:
|
||||||
// { name, isLocal, marker, systemState, runningCount, totalCount,
|
// { name, isLocal, marker, systemState, runningCount, totalCount,
|
||||||
// failedUnits: [{name, description, subState, scope, machine}],
|
// failedUnits: [{name, description, subState, scope, machine}],
|
||||||
// runningUnits: [...],
|
// runningUnits: [...],
|
||||||
// errorKind: ""|"transient"|"permanent",
|
// errorKind: ""|"transient"|"permanent",
|
||||||
// errorReason: string,
|
// errorReason: string,
|
||||||
// lastSeen: 0 | unix_seconds }
|
// lastSeen: 0 | unix_seconds,
|
||||||
|
// containers: [<MachineJson>, ...] }
|
||||||
#[qproperty(QString, machines_json, cxx_name = "machinesJson")]
|
#[qproperty(QString, machines_json, cxx_name = "machinesJson")]
|
||||||
type SystemdService = super::SystemdServiceRust;
|
type SystemdService = super::SystemdServiceRust;
|
||||||
|
|
||||||
|
|
@ -136,6 +139,8 @@ struct MachineJson {
|
||||||
error_reason: String,
|
error_reason: String,
|
||||||
#[serde(rename = "lastSeen")]
|
#[serde(rename = "lastSeen")]
|
||||||
last_seen: u64,
|
last_seen: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
containers: Vec<MachineJson>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MachineJson {
|
impl MachineJson {
|
||||||
|
|
@ -152,6 +157,24 @@ impl MachineJson {
|
||||||
error_kind: String::new(),
|
error_kind: String::new(),
|
||||||
error_reason: String::new(),
|
error_reason: String::new(),
|
||||||
last_seen: 0,
|
last_seen: 0,
|
||||||
|
containers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn errored(name: String, kind: &'static str, reason: String, prev_last_seen: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
is_local: false,
|
||||||
|
marker: String::new(),
|
||||||
|
system_state: "unreachable".into(),
|
||||||
|
running_count: 0,
|
||||||
|
total_count: 0,
|
||||||
|
failed_units: Vec::new(),
|
||||||
|
running_units: Vec::new(),
|
||||||
|
error_kind: kind.to_string(),
|
||||||
|
error_reason: reason,
|
||||||
|
last_seen: prev_last_seen,
|
||||||
|
containers: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +184,7 @@ pub struct SystemdServiceRust {
|
||||||
failed_count: i32,
|
failed_count: i32,
|
||||||
machines_json: QString,
|
machines_json: QString,
|
||||||
applet_open: bool,
|
applet_open: bool,
|
||||||
// Last successful or attempted remote fetch per target. Persists across
|
// Last successful or attempted remote fetch per ssh target. Persists across
|
||||||
// polls so the UI can show stale data with a "last seen" timestamp when
|
// polls so the UI can show stale data with a "last seen" timestamp when
|
||||||
// the host goes unreachable.
|
// the host goes unreachable.
|
||||||
remote_cache: HashMap<String, MachineJson>,
|
remote_cache: HashMap<String, MachineJson>,
|
||||||
|
|
@ -202,7 +225,7 @@ fn rt() -> &'static Runtime {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_units(bus: &Connection) -> (String, Vec<UnitTuple>) {
|
async fn fetch_units_zbus(bus: &Connection) -> (String, Vec<UnitTuple>) {
|
||||||
let mut state = String::from("unknown");
|
let mut state = String::from("unknown");
|
||||||
let mut units = Vec::new();
|
let mut units = Vec::new();
|
||||||
if let Ok(mgr) = SystemdManagerProxy::new(bus).await {
|
if let Ok(mgr) = SystemdManagerProxy::new(bus).await {
|
||||||
|
|
@ -242,7 +265,7 @@ fn partition_units(
|
||||||
(failed, running, total)
|
(failed, running, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn poll_local() -> (
|
async fn poll_local_zbus() -> (
|
||||||
String,
|
String,
|
||||||
Vec<UnitTuple>,
|
Vec<UnitTuple>,
|
||||||
Vec<UnitTuple>,
|
Vec<UnitTuple>,
|
||||||
|
|
@ -254,7 +277,7 @@ async fn poll_local() -> (
|
||||||
let mut machines = Vec::new();
|
let mut machines = Vec::new();
|
||||||
|
|
||||||
if let Ok(c) = Connection::system().await {
|
if let Ok(c) = Connection::system().await {
|
||||||
let (s, u) = fetch_units(&c).await;
|
let (s, u) = fetch_units_zbus(&c).await;
|
||||||
sys_state = s;
|
sys_state = s;
|
||||||
sys_units = u;
|
sys_units = u;
|
||||||
if let Ok(m) = MachinedProxy::new(&c).await {
|
if let Ok(m) = MachinedProxy::new(&c).await {
|
||||||
|
|
@ -268,63 +291,74 @@ async fn poll_local() -> (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(c) = Connection::session().await {
|
if let Ok(c) = Connection::session().await {
|
||||||
let (_, u) = fetch_units(&c).await;
|
let (_, u) = fetch_units_zbus(&c).await;
|
||||||
user_units = u;
|
user_units = u;
|
||||||
}
|
}
|
||||||
|
|
||||||
(sys_state, sys_units, user_units, machines)
|
(sys_state, sys_units, user_units, machines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn `busctl --host=<target>` to fetch system_state + units. Returns the
|
// Run busctl with the given prefix args plus the trailing call args. Returns
|
||||||
// MachineJson on success, or an (kind, reason) on failure.
|
// a parsed `data` value or an error classification.
|
||||||
fn fetch_remote(target: &str) -> Result<MachineJson, (&'static str, String)> {
|
fn busctl_call(
|
||||||
let state_out = std::process::Command::new("busctl")
|
prefix: &[&str],
|
||||||
.args([
|
call: &[&str],
|
||||||
"--host",
|
) -> Result<serde_json::Value, (&'static str, String)> {
|
||||||
target,
|
let mut args: Vec<&str> = prefix.to_vec();
|
||||||
|
args.extend_from_slice(call);
|
||||||
|
let out = std::process::Command::new("busctl")
|
||||||
|
.args(&args)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| ("transient", format!("busctl spawn failed: {e}")))?;
|
||||||
|
if !out.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
|
||||||
|
return Err((classify_error(&stderr), stderr));
|
||||||
|
}
|
||||||
|
serde_json::from_slice(&out.stdout)
|
||||||
|
.map_err(|e| ("transient", format!("invalid busctl json: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch SystemState + ListUnits via busctl with the given prefix flags.
|
||||||
|
// `prefix` examples:
|
||||||
|
// [] - local system bus (unused; we use zbus locally)
|
||||||
|
// ["--host", "muede-pc2"] - remote host
|
||||||
|
// ["--machine", "damocles-lab"] - local container
|
||||||
|
// ["--host", "pc2", "--machine", "x"] - container on a remote host
|
||||||
|
fn fetch_via_busctl(
|
||||||
|
name: &str,
|
||||||
|
prefix: &[&str],
|
||||||
|
is_local: bool,
|
||||||
|
marker: &str,
|
||||||
|
) -> Result<MachineJson, (&'static str, String)> {
|
||||||
|
let state_v = busctl_call(
|
||||||
|
prefix,
|
||||||
|
&[
|
||||||
"--json=short",
|
"--json=short",
|
||||||
"get-property",
|
"get-property",
|
||||||
"org.freedesktop.systemd1",
|
"org.freedesktop.systemd1",
|
||||||
"/org/freedesktop/systemd1",
|
"/org/freedesktop/systemd1",
|
||||||
"org.freedesktop.systemd1.Manager",
|
"org.freedesktop.systemd1.Manager",
|
||||||
"SystemState",
|
"SystemState",
|
||||||
])
|
],
|
||||||
.output()
|
)?;
|
||||||
.map_err(|e| ("transient", format!("busctl spawn failed: {e}")))?;
|
|
||||||
|
|
||||||
if !state_out.status.success() {
|
|
||||||
let stderr = String::from_utf8_lossy(&state_out.stderr).into_owned();
|
|
||||||
return Err((classify_error(&stderr), stderr));
|
|
||||||
}
|
|
||||||
let state_v: serde_json::Value = serde_json::from_slice(&state_out.stdout)
|
|
||||||
.map_err(|e| ("transient", format!("invalid SystemState json: {e}")))?;
|
|
||||||
let system_state = state_v
|
let system_state = state_v
|
||||||
.get("data")
|
.get("data")
|
||||||
.and_then(|d| d.as_str())
|
.and_then(|d| d.as_str())
|
||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let units_out = std::process::Command::new("busctl")
|
let units_v = busctl_call(
|
||||||
.args([
|
prefix,
|
||||||
"--host",
|
&[
|
||||||
target,
|
|
||||||
"--json=short",
|
"--json=short",
|
||||||
"call",
|
"call",
|
||||||
"org.freedesktop.systemd1",
|
"org.freedesktop.systemd1",
|
||||||
"/org/freedesktop/systemd1",
|
"/org/freedesktop/systemd1",
|
||||||
"org.freedesktop.systemd1.Manager",
|
"org.freedesktop.systemd1.Manager",
|
||||||
"ListUnits",
|
"ListUnits",
|
||||||
])
|
],
|
||||||
.output()
|
)?;
|
||||||
.map_err(|e| ("transient", format!("busctl spawn failed: {e}")))?;
|
let arr = units_v
|
||||||
|
|
||||||
if !units_out.status.success() {
|
|
||||||
let stderr = String::from_utf8_lossy(&units_out.stderr).into_owned();
|
|
||||||
return Err((classify_error(&stderr), stderr));
|
|
||||||
}
|
|
||||||
let v: serde_json::Value = serde_json::from_slice(&units_out.stdout)
|
|
||||||
.map_err(|e| ("transient", format!("invalid ListUnits json: {e}")))?;
|
|
||||||
let arr = v
|
|
||||||
.get("data")
|
.get("data")
|
||||||
.and_then(|d| d.get(0))
|
.and_then(|d| d.get(0))
|
||||||
.and_then(|d| d.as_array())
|
.and_then(|d| d.as_array())
|
||||||
|
|
@ -344,7 +378,7 @@ fn fetch_remote(target: &str) -> Result<MachineJson, (&'static str, String)> {
|
||||||
description: arr[1].as_str().unwrap_or("").to_string(),
|
description: arr[1].as_str().unwrap_or("").to_string(),
|
||||||
sub_state: arr[4].as_str().unwrap_or("").to_string(),
|
sub_state: arr[4].as_str().unwrap_or("").to_string(),
|
||||||
scope: "system".to_string(),
|
scope: "system".to_string(),
|
||||||
machine: target.to_string(),
|
machine: name.to_string(),
|
||||||
};
|
};
|
||||||
match arr[3].as_str() {
|
match arr[3].as_str() {
|
||||||
Some("failed") => failed.push(entry),
|
Some("failed") => failed.push(entry),
|
||||||
|
|
@ -354,9 +388,9 @@ fn fetch_remote(target: &str) -> Result<MachineJson, (&'static str, String)> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MachineJson {
|
Ok(MachineJson {
|
||||||
name: target.to_string(),
|
name: name.to_string(),
|
||||||
is_local: false,
|
is_local,
|
||||||
marker: String::new(),
|
marker: marker.to_string(),
|
||||||
system_state,
|
system_state,
|
||||||
running_count: running.len() as i32,
|
running_count: running.len() as i32,
|
||||||
total_count: total,
|
total_count: total,
|
||||||
|
|
@ -365,9 +399,42 @@ fn fetch_remote(target: &str) -> Result<MachineJson, (&'static str, String)> {
|
||||||
error_kind: String::new(),
|
error_kind: String::new(),
|
||||||
error_reason: String::new(),
|
error_reason: String::new(),
|
||||||
last_seen: now_unix(),
|
last_seen: now_unix(),
|
||||||
|
containers: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List nspawn containers under the given busctl prefix (empty = local).
|
||||||
|
fn list_containers_busctl(prefix: &[&str]) -> Vec<String> {
|
||||||
|
let v = match busctl_call(
|
||||||
|
prefix,
|
||||||
|
&[
|
||||||
|
"--json=short",
|
||||||
|
"call",
|
||||||
|
"org.freedesktop.machine1",
|
||||||
|
"/org/freedesktop/machine1",
|
||||||
|
"org.freedesktop.machine1.Manager",
|
||||||
|
"ListMachines",
|
||||||
|
],
|
||||||
|
) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return Vec::new(),
|
||||||
|
};
|
||||||
|
let arr = match v
|
||||||
|
.get("data")
|
||||||
|
.and_then(|d| d.get(0))
|
||||||
|
.and_then(|d| d.as_array())
|
||||||
|
{
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Vec::new(),
|
||||||
|
};
|
||||||
|
arr.iter()
|
||||||
|
.filter_map(|m| m.as_array())
|
||||||
|
.filter_map(|a| a.first()?.as_str())
|
||||||
|
.filter(|n| *n != ".host")
|
||||||
|
.map(String::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// Classify an ssh/busctl stderr blob into transient vs permanent so the UI
|
// Classify an ssh/busctl stderr blob into transient vs permanent so the UI
|
||||||
// can pick a color and decide whether to retry aggressively.
|
// can pick a color and decide whether to retry aggressively.
|
||||||
fn classify_error(stderr: &str) -> &'static str {
|
fn classify_error(stderr: &str) -> &'static str {
|
||||||
|
|
@ -392,7 +459,8 @@ impl cxx_qt::Initialize for qobject::SystemdService {
|
||||||
|
|
||||||
impl qobject::SystemdService {
|
impl qobject::SystemdService {
|
||||||
fn poll(mut self: Pin<&mut Self>) {
|
fn poll(mut self: Pin<&mut Self>) {
|
||||||
let (sys_state, sys_units, user_units, containers) = rt().block_on(poll_local());
|
let (sys_state, sys_units, user_units, local_container_names) =
|
||||||
|
rt().block_on(poll_local_zbus());
|
||||||
|
|
||||||
let (sys_failed, sys_running, sys_total) = partition_units(sys_units, "system", "");
|
let (sys_failed, sys_running, sys_total) = partition_units(sys_units, "system", "");
|
||||||
let (user_failed, user_running, user_total) = partition_units(user_units, "user", "");
|
let (user_failed, user_running, user_total) = partition_units(user_units, "user", "");
|
||||||
|
|
@ -408,6 +476,21 @@ impl qobject::SystemdService {
|
||||||
let local_running_count = running.len() as i32;
|
let local_running_count = running.len() as i32;
|
||||||
let local_total_count = sys_total + user_total;
|
let local_total_count = sys_total + user_total;
|
||||||
|
|
||||||
|
let local_containers: Vec<MachineJson> = if self.as_ref().rust().applet_open {
|
||||||
|
local_container_names
|
||||||
|
.iter()
|
||||||
|
.map(|(name, _, _)| {
|
||||||
|
fetch_via_busctl(name, &["--machine", name], false, "")
|
||||||
|
.unwrap_or_else(|(k, r)| MachineJson::errored(name.clone(), k, r, 0))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
local_container_names
|
||||||
|
.iter()
|
||||||
|
.map(|(n, _, _)| MachineJson::placeholder(n.clone(), false))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
let local = MachineJson {
|
let local = MachineJson {
|
||||||
name: read_hostname(),
|
name: read_hostname(),
|
||||||
is_local: true,
|
is_local: true,
|
||||||
|
|
@ -420,16 +503,12 @@ impl qobject::SystemdService {
|
||||||
error_kind: String::new(),
|
error_kind: String::new(),
|
||||||
error_reason: String::new(),
|
error_reason: String::new(),
|
||||||
last_seen: now_unix(),
|
last_seen: now_unix(),
|
||||||
|
containers: local_containers,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut all_machines = Vec::new();
|
let mut all_machines = Vec::new();
|
||||||
all_machines.push(local);
|
all_machines.push(local);
|
||||||
|
|
||||||
// Local nspawn containers (unit fetching for them lands in step 5).
|
|
||||||
for (name, _class, _service) in &containers {
|
|
||||||
all_machines.push(MachineJson::placeholder(name.clone(), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configured remote machines. Dedup local: drop entries matching the
|
// Configured remote machines. Dedup local: drop entries matching the
|
||||||
// local hostname or `localhost`, with or without a `user@` prefix.
|
// local hostname or `localhost`, with or without a `user@` prefix.
|
||||||
let host = read_hostname();
|
let host = read_hostname();
|
||||||
|
|
@ -455,21 +534,28 @@ impl qobject::SystemdService {
|
||||||
.get(target)
|
.get(target)
|
||||||
.map(|m| m.last_seen)
|
.map(|m| m.last_seen)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let entry = match fetch_remote(target) {
|
let entry = match fetch_via_busctl(target, &["--host", target], false, "") {
|
||||||
Ok(m) => m,
|
Ok(mut m) => {
|
||||||
Err((kind, reason)) => MachineJson {
|
// Enumerate + fetch remote nspawn containers via the
|
||||||
name: target.clone(),
|
// remote host's machined.
|
||||||
is_local: false,
|
let names = list_containers_busctl(&["--host", target]);
|
||||||
marker: String::new(),
|
m.containers = names
|
||||||
system_state: "unreachable".into(),
|
.iter()
|
||||||
running_count: 0,
|
.map(|cn| {
|
||||||
total_count: 0,
|
fetch_via_busctl(
|
||||||
failed_units: Vec::new(),
|
cn,
|
||||||
running_units: Vec::new(),
|
&["--host", target, "--machine", cn],
|
||||||
error_kind: kind.to_string(),
|
false,
|
||||||
error_reason: reason,
|
"",
|
||||||
last_seen: prev_last_seen,
|
)
|
||||||
},
|
.unwrap_or_else(|(k, r)| MachineJson::errored(cn.clone(), k, r, 0))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
m
|
||||||
|
}
|
||||||
|
Err((kind, reason)) => {
|
||||||
|
MachineJson::errored(target.clone(), kind, reason, prev_last_seen)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.as_mut()
|
self.as_mut()
|
||||||
.rust_mut()
|
.rust_mut()
|
||||||
|
|
@ -519,7 +605,7 @@ impl qobject::SystemdService {
|
||||||
let machine = machine.to_string();
|
let machine = machine.to_string();
|
||||||
let _ = self;
|
let _ = self;
|
||||||
rt().block_on(async move {
|
rt().block_on(async move {
|
||||||
// Local-only restart for now. Container/remote restart comes later.
|
// Local-only restart for now. Container/remote restart is TODO.
|
||||||
if !machine.is_empty() {
|
if !machine.is_empty() {
|
||||||
tracing::warn!(target: "nova_plugin", machine = %machine, "container/remote restart not yet implemented");
|
tracing::warn!(target: "nova_plugin", machine = %machine, "container/remote restart not yet implemented");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ Column {
|
||||||
errorKind: _row.modelData.errorKind ?? ""
|
errorKind: _row.modelData.errorKind ?? ""
|
||||||
errorReason: _row.modelData.errorReason ?? ""
|
errorReason: _row.modelData.errorReason ?? ""
|
||||||
lastSeen: _row.modelData.lastSeen ?? 0
|
lastSeen: _row.modelData.lastSeen ?? 0
|
||||||
|
containers: _row.modelData.containers ?? []
|
||||||
onContentResized: root.contentResized()
|
onContentResized: root.contentResized()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ Column {
|
||||||
property string errorKind: ""
|
property string errorKind: ""
|
||||||
property string errorReason: ""
|
property string errorReason: ""
|
||||||
property int lastSeen: 0
|
property int lastSeen: 0
|
||||||
|
property var containers: []
|
||||||
|
property int depth: 0
|
||||||
|
|
||||||
signal contentResized
|
signal contentResized
|
||||||
onHeightChanged: root.contentResized()
|
onHeightChanged: root.contentResized()
|
||||||
|
|
@ -231,4 +233,37 @@ Column {
|
||||||
accentColor: root.accentColor
|
accentColor: root.accentColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nested containers running on this machine. Indented to convey hierarchy.
|
||||||
|
Repeater {
|
||||||
|
model: root.containers ?? []
|
||||||
|
delegate: Item {
|
||||||
|
id: _childWrap
|
||||||
|
required property var modelData
|
||||||
|
width: root.width
|
||||||
|
height: _child.height + 4
|
||||||
|
|
||||||
|
SystemdMachineSection {
|
||||||
|
id: _child
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
width: parent.width - 16
|
||||||
|
accentColor: root.accentColor
|
||||||
|
machineName: _childWrap.modelData.name
|
||||||
|
title: _childWrap.modelData.name
|
||||||
|
marker: _childWrap.modelData.marker ?? ""
|
||||||
|
systemState: _childWrap.modelData.systemState ?? "unknown"
|
||||||
|
runningCount: _childWrap.modelData.runningCount ?? 0
|
||||||
|
totalCount: _childWrap.modelData.totalCount ?? 0
|
||||||
|
failedUnits: _childWrap.modelData.failedUnits ?? []
|
||||||
|
runningUnits: _childWrap.modelData.runningUnits ?? []
|
||||||
|
errorKind: _childWrap.modelData.errorKind ?? ""
|
||||||
|
errorReason: _childWrap.modelData.errorReason ?? ""
|
||||||
|
lastSeen: _childWrap.modelData.lastSeen ?? 0
|
||||||
|
containers: _childWrap.modelData.containers ?? []
|
||||||
|
depth: root.depth + 1
|
||||||
|
onContentResized: root.contentResized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue