Compare commits
5 changed files with 127 additions and 280 deletions
|
|
@ -101,10 +101,9 @@ pub mod qobject {
|
||||||
#[qproperty(bool, dock_applet_mpris, cxx_name = "dockAppletMpris")]
|
#[qproperty(bool, dock_applet_mpris, cxx_name = "dockAppletMpris")]
|
||||||
#[qproperty(bool, dock_applet_notifications, cxx_name = "dockAppletNotifications")]
|
#[qproperty(bool, dock_applet_notifications, cxx_name = "dockAppletNotifications")]
|
||||||
#[qproperty(bool, dock_applet_power, cxx_name = "dockAppletPower")]
|
#[qproperty(bool, dock_applet_power, cxx_name = "dockAppletPower")]
|
||||||
// Unified systemd bar module (covers local + nspawn containers + remotes).
|
// Unified systemd bar module (covers local + nspawn containers, and later remotes).
|
||||||
#[qproperty(bool, systemd_enable, cxx_name = "systemdEnable")]
|
#[qproperty(bool, systemd_enable, cxx_name = "systemdEnable")]
|
||||||
#[qproperty(i32, systemd_interval, cxx_name = "systemdInterval")]
|
#[qproperty(i32, systemd_interval, cxx_name = "systemdInterval")]
|
||||||
#[qproperty(QList_QString, systemd_machines, cxx_name = "systemdMachines")]
|
|
||||||
type ModulesService = super::ModulesServiceRust;
|
type ModulesService = super::ModulesServiceRust;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,34 +443,6 @@ mod data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Systemd-bar group: enable + poll interval + remote SSH targets. Each
|
|
||||||
// `machines` entry is a string like "host" or "user@host"; the local
|
|
||||||
// machine is deduped by hostname even if listed.
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Systemd {
|
|
||||||
#[serde(default = "t")]
|
|
||||||
pub enable: bool,
|
|
||||||
#[serde(default = "Systemd::d_interval")]
|
|
||||||
pub interval: i32,
|
|
||||||
#[serde(default)]
|
|
||||||
pub machines: Vec<String>,
|
|
||||||
}
|
|
||||||
impl Systemd {
|
|
||||||
fn d_interval() -> i32 {
|
|
||||||
15_000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Default for Systemd {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enable: true,
|
|
||||||
interval: Self::d_interval(),
|
|
||||||
machines: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct StatsDaemon {
|
pub struct StatsDaemon {
|
||||||
|
|
@ -547,7 +518,7 @@ mod data {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dock: Dock,
|
pub dock: Dock,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub systemd: Systemd,
|
pub systemd: WithInterval,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub stats_daemon: StatsDaemon,
|
pub stats_daemon: StatsDaemon,
|
||||||
}
|
}
|
||||||
|
|
@ -624,7 +595,6 @@ pub struct ModulesServiceRust {
|
||||||
dock_applet_power: bool,
|
dock_applet_power: bool,
|
||||||
systemd_enable: bool,
|
systemd_enable: bool,
|
||||||
systemd_interval: i32,
|
systemd_interval: i32,
|
||||||
systemd_machines: QList<QString>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ModulesServiceRust {
|
impl Default for ModulesServiceRust {
|
||||||
|
|
@ -708,13 +678,6 @@ impl ModulesServiceRust {
|
||||||
dock_applet_power: d.dock.applets.power,
|
dock_applet_power: d.dock.applets.power,
|
||||||
systemd_enable: d.systemd.enable,
|
systemd_enable: d.systemd.enable,
|
||||||
systemd_interval: d.systemd.interval,
|
systemd_interval: d.systemd.interval,
|
||||||
systemd_machines: {
|
|
||||||
let mut list = QList::<QString>::default();
|
|
||||||
for s in &d.systemd.machines {
|
|
||||||
list.append(QString::from(s.as_str()));
|
|
||||||
}
|
|
||||||
list
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -728,10 +691,6 @@ pub(crate) fn config_path(file: &str) -> PathBuf {
|
||||||
base.join("nova-shell").join(file)
|
base.join("nova-shell").join(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_systemd_machines() -> Vec<String> {
|
|
||||||
load_modules_data().systemd.machines
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_modules_data() -> ModulesData {
|
fn load_modules_data() -> ModulesData {
|
||||||
let path = config_path("modules.json");
|
let path = config_path("modules.json");
|
||||||
let raw = match std::fs::read_to_string(&path) {
|
let raw = match std::fs::read_to_string(&path) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
// In-process systemd state for nova-shell.
|
// In-process systemd state for nova-shell.
|
||||||
//
|
//
|
||||||
// The machine tree is exposed to QML as a JSON-encoded QString (`machinesJson`)
|
// Lists of units and machines are exposed to QML as JSON-encoded QString props
|
||||||
// rather than QList<QVariantMap>. cxx-qt 0.8.1 does not implement QVariantValue
|
// (`failedUnitsJson`, `containersJson`) rather than QList<QVariantMap>. cxx-qt
|
||||||
// for QVariantMap/QVariantList, and cxx-qt main on git regressed qt-build-utils
|
// 0.8.1 does not implement QVariantValue for QVariantMap/QVariantList, and
|
||||||
// to require QuickControls2.prl files that nixpkgs strips. Switch to
|
// cxx-qt main on git regressed qt-build-utils to require QuickControls2.prl
|
||||||
// QList<QVariantMap> when a release ships with both fixes.
|
// files that nixpkgs strips. Switch to QList<QVariantMap> when a release ships
|
||||||
|
// with both fixes.
|
||||||
|
|
||||||
use crate::modules_service;
|
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
use cxx_qt_lib::QString;
|
use cxx_qt_lib::QString;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
@ -26,13 +26,13 @@ pub mod qobject {
|
||||||
#[qml_element]
|
#[qml_element]
|
||||||
#[qml_singleton]
|
#[qml_singleton]
|
||||||
#[qproperty(QString, hostname)]
|
#[qproperty(QString, hostname)]
|
||||||
// Local failed unit count (drives the bar module label).
|
#[qproperty(QString, system_state, cxx_name = "systemState")]
|
||||||
|
#[qproperty(QString, user_state, cxx_name = "userState")]
|
||||||
#[qproperty(i32, failed_count, cxx_name = "failedCount")]
|
#[qproperty(i32, failed_count, cxx_name = "failedCount")]
|
||||||
// JSON array, local first then nspawn containers. Each entry:
|
// JSON array: [{ name, description, subState, scope: "system"|"user", machine: "" | name }]
|
||||||
// { name, isLocal, marker, systemState, runningCount, totalCount,
|
#[qproperty(QString, failed_units_json, cxx_name = "failedUnitsJson")]
|
||||||
// failedUnits: [{name, description, subState, scope, machine}],
|
// JSON array: [{ name, class, service, systemState, failedUnits: [...] }]
|
||||||
// runningUnits: [...] }
|
#[qproperty(QString, containers_json, cxx_name = "containersJson")]
|
||||||
#[qproperty(QString, machines_json, cxx_name = "machinesJson")]
|
|
||||||
type SystemdService = super::SystemdServiceRust;
|
type SystemdService = super::SystemdServiceRust;
|
||||||
|
|
||||||
#[qinvokable]
|
#[qinvokable]
|
||||||
|
|
@ -46,7 +46,7 @@ pub mod qobject {
|
||||||
impl cxx_qt::Initialize for SystemdService {}
|
impl cxx_qt::Initialize for SystemdService {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// systemd1.Manager.ListUnits returns a(ssssssouso): name, description,
|
// systemd1.Manager.ListUnitsFiltered returns a(ssssssouso): name, description,
|
||||||
// load_state, active_state, sub_state, follower, unit_path, job_id, job_type, job_path.
|
// load_state, active_state, sub_state, follower, unit_path, job_id, job_type, job_path.
|
||||||
type UnitTuple = (
|
type UnitTuple = (
|
||||||
String,
|
String,
|
||||||
|
|
@ -70,7 +70,7 @@ trait SystemdManager {
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
fn system_state(&self) -> zbus::Result<String>;
|
fn system_state(&self) -> zbus::Result<String>;
|
||||||
|
|
||||||
fn list_units(&self) -> zbus::Result<Vec<UnitTuple>>;
|
fn list_units_filtered(&self, states: Vec<&str>) -> zbus::Result<Vec<UnitTuple>>;
|
||||||
|
|
||||||
fn restart_unit(&self, name: &str, mode: &str)
|
fn restart_unit(&self, name: &str, mode: &str)
|
||||||
-> zbus::Result<zbus::zvariant::OwnedObjectPath>;
|
-> zbus::Result<zbus::zvariant::OwnedObjectPath>;
|
||||||
|
|
@ -88,45 +88,44 @@ trait Machined {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct UnitJson {
|
struct UnitJson<'a> {
|
||||||
name: String,
|
name: &'a str,
|
||||||
description: String,
|
description: &'a str,
|
||||||
#[serde(rename = "subState")]
|
#[serde(rename = "subState")]
|
||||||
sub_state: String,
|
sub_state: &'a str,
|
||||||
scope: String,
|
scope: &'a str,
|
||||||
machine: String,
|
machine: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct MachineJson {
|
struct ContainerJson<'a> {
|
||||||
name: String,
|
name: &'a str,
|
||||||
#[serde(rename = "isLocal")]
|
class: &'a str,
|
||||||
is_local: bool,
|
service: &'a str,
|
||||||
marker: String,
|
|
||||||
#[serde(rename = "systemState")]
|
#[serde(rename = "systemState")]
|
||||||
system_state: String,
|
system_state: &'a str,
|
||||||
#[serde(rename = "runningCount")]
|
|
||||||
running_count: i32,
|
|
||||||
#[serde(rename = "totalCount")]
|
|
||||||
total_count: i32,
|
|
||||||
#[serde(rename = "failedUnits")]
|
#[serde(rename = "failedUnits")]
|
||||||
failed_units: Vec<UnitJson>,
|
failed_units: Vec<UnitJson<'a>>,
|
||||||
#[serde(rename = "runningUnits")]
|
|
||||||
running_units: Vec<UnitJson>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SystemdServiceRust {
|
pub struct SystemdServiceRust {
|
||||||
hostname: QString,
|
hostname: QString,
|
||||||
|
system_state: QString,
|
||||||
|
user_state: QString,
|
||||||
failed_count: i32,
|
failed_count: i32,
|
||||||
machines_json: QString,
|
failed_units_json: QString,
|
||||||
|
containers_json: QString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SystemdServiceRust {
|
impl Default for SystemdServiceRust {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
hostname: QString::from(read_hostname()),
|
hostname: QString::from(read_hostname()),
|
||||||
|
system_state: QString::from("unknown"),
|
||||||
|
user_state: QString::from("unknown"),
|
||||||
failed_count: 0,
|
failed_count: 0,
|
||||||
machines_json: QString::from("[]"),
|
failed_units_json: QString::from("[]"),
|
||||||
|
containers_json: QString::from("[]"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,47 +146,22 @@ fn rt() -> &'static Runtime {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_units(bus: &Connection) -> (String, Vec<UnitTuple>) {
|
async fn fetch_failed(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 {
|
||||||
if let Ok(s) = mgr.system_state().await {
|
if let Ok(s) = mgr.system_state().await {
|
||||||
state = s;
|
state = s;
|
||||||
}
|
}
|
||||||
if let Ok(u) = mgr.list_units().await {
|
if let Ok(u) = mgr.list_units_filtered(vec!["failed"]).await {
|
||||||
units = u;
|
units = u;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(state, units)
|
(state, units)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Partition a unit list by active_state. (failed, running)
|
|
||||||
fn partition_units(
|
|
||||||
units: Vec<UnitTuple>,
|
|
||||||
scope: &str,
|
|
||||||
machine: &str,
|
|
||||||
) -> (Vec<UnitJson>, Vec<UnitJson>, i32) {
|
|
||||||
let total = units.len() as i32;
|
|
||||||
let mut failed = Vec::new();
|
|
||||||
let mut running = Vec::new();
|
|
||||||
for u in units {
|
|
||||||
let entry = UnitJson {
|
|
||||||
name: u.0,
|
|
||||||
description: u.1,
|
|
||||||
sub_state: u.4,
|
|
||||||
scope: scope.to_string(),
|
|
||||||
machine: machine.to_string(),
|
|
||||||
};
|
|
||||||
match u.3.as_str() {
|
|
||||||
"failed" => failed.push(entry),
|
|
||||||
"active" => running.push(entry),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(failed, running, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn poll_async() -> (
|
async fn poll_async() -> (
|
||||||
|
String,
|
||||||
String,
|
String,
|
||||||
Vec<UnitTuple>,
|
Vec<UnitTuple>,
|
||||||
Vec<UnitTuple>,
|
Vec<UnitTuple>,
|
||||||
|
|
@ -195,11 +169,12 @@ async fn poll_async() -> (
|
||||||
) {
|
) {
|
||||||
let mut sys_state = String::from("unknown");
|
let mut sys_state = String::from("unknown");
|
||||||
let mut sys_units = Vec::new();
|
let mut sys_units = Vec::new();
|
||||||
|
let mut user_state = String::from("unknown");
|
||||||
let mut user_units = Vec::new();
|
let mut user_units = Vec::new();
|
||||||
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_failed(&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 {
|
||||||
|
|
@ -213,11 +188,25 @@ async fn poll_async() -> (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(c) = Connection::session().await {
|
if let Ok(c) = Connection::session().await {
|
||||||
let (_, u) = fetch_units(&c).await;
|
let (s, u) = fetch_failed(&c).await;
|
||||||
|
user_state = s;
|
||||||
user_units = u;
|
user_units = u;
|
||||||
}
|
}
|
||||||
|
|
||||||
(sys_state, sys_units, user_units, machines)
|
(sys_state, user_state, sys_units, user_units, machines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unit_jsons<'a>(units: &'a [UnitTuple], scope: &'a str, machine: &'a str) -> Vec<UnitJson<'a>> {
|
||||||
|
units
|
||||||
|
.iter()
|
||||||
|
.map(|u| UnitJson {
|
||||||
|
name: &u.0,
|
||||||
|
description: &u.1,
|
||||||
|
sub_state: &u.4,
|
||||||
|
scope,
|
||||||
|
machine,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cxx_qt::Initialize for qobject::SystemdService {
|
impl cxx_qt::Initialize for qobject::SystemdService {
|
||||||
|
|
@ -228,76 +217,33 @@ 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, machines) = rt().block_on(poll_async());
|
let (sys_state, user_state, sys_units, user_units, machines) = rt().block_on(poll_async());
|
||||||
|
|
||||||
let (sys_failed, sys_running, sys_total) = partition_units(sys_units, "system", "");
|
let mut all_failed: Vec<UnitJson> = Vec::with_capacity(sys_units.len() + user_units.len());
|
||||||
let (user_failed, user_running, user_total) = partition_units(user_units, "user", "");
|
all_failed.extend(unit_jsons(&sys_units, "system", ""));
|
||||||
|
all_failed.extend(unit_jsons(&user_units, "user", ""));
|
||||||
|
let count = all_failed.len() as i32;
|
||||||
|
let failed_json = serde_json::to_string(&all_failed).unwrap_or_else(|_| "[]".into());
|
||||||
|
|
||||||
let mut failed: Vec<UnitJson> = Vec::with_capacity(sys_failed.len() + user_failed.len());
|
let containers: Vec<ContainerJson> = machines
|
||||||
failed.extend(sys_failed);
|
.iter()
|
||||||
failed.extend(user_failed);
|
.map(|(n, c, s)| ContainerJson {
|
||||||
let mut running: Vec<UnitJson> = Vec::with_capacity(sys_running.len() + user_running.len());
|
name: n,
|
||||||
running.extend(sys_running);
|
class: c,
|
||||||
running.extend(user_running);
|
service: s,
|
||||||
|
system_state: "unknown",
|
||||||
let local_failed_count = failed.len() as i32;
|
|
||||||
let local_running_count = running.len() as i32;
|
|
||||||
let local_total_count = sys_total + user_total;
|
|
||||||
|
|
||||||
let local = MachineJson {
|
|
||||||
name: read_hostname(),
|
|
||||||
is_local: true,
|
|
||||||
marker: "this machine".into(),
|
|
||||||
system_state: sys_state,
|
|
||||||
running_count: local_running_count,
|
|
||||||
total_count: local_total_count,
|
|
||||||
failed_units: failed,
|
|
||||||
running_units: running,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Containers: enumerate only; unit fetching for containers comes in step 5.
|
|
||||||
let mut all_machines = Vec::with_capacity(1 + machines.len());
|
|
||||||
all_machines.push(local);
|
|
||||||
for (name, _class, _service) in &machines {
|
|
||||||
all_machines.push(MachineJson {
|
|
||||||
name: name.clone(),
|
|
||||||
is_local: false,
|
|
||||||
marker: String::new(),
|
|
||||||
system_state: "unknown".into(),
|
|
||||||
running_count: 0,
|
|
||||||
total_count: 0,
|
|
||||||
failed_units: Vec::new(),
|
failed_units: Vec::new(),
|
||||||
running_units: Vec::new(),
|
})
|
||||||
});
|
.collect();
|
||||||
}
|
let containers_json = serde_json::to_string(&containers).unwrap_or_else(|_| "[]".into());
|
||||||
|
|
||||||
// Configured remote machines (placeholders; transport lands in step 4).
|
self.as_mut().set_system_state(QString::from(sys_state));
|
||||||
// Dedup local: drop entries matching the local hostname or `localhost`,
|
self.as_mut().set_user_state(QString::from(user_state));
|
||||||
// with or without a `user@` prefix.
|
|
||||||
let host = read_hostname();
|
|
||||||
let cfg_machines = modules_service::load_systemd_machines();
|
|
||||||
for target in cfg_machines {
|
|
||||||
let host_part = target.rsplit_once('@').map_or(target.as_str(), |(_, h)| h);
|
|
||||||
if host_part == host || host_part == "localhost" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
all_machines.push(MachineJson {
|
|
||||||
name: target.clone(),
|
|
||||||
is_local: false,
|
|
||||||
marker: String::new(),
|
|
||||||
system_state: "pending".into(),
|
|
||||||
running_count: 0,
|
|
||||||
total_count: 0,
|
|
||||||
failed_units: Vec::new(),
|
|
||||||
running_units: Vec::new(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let machines_json = serde_json::to_string(&all_machines).unwrap_or_else(|_| "[]".into());
|
|
||||||
|
|
||||||
self.as_mut().set_failed_count(local_failed_count);
|
|
||||||
self.as_mut()
|
self.as_mut()
|
||||||
.set_machines_json(QString::from(machines_json));
|
.set_failed_units_json(QString::from(failed_json));
|
||||||
|
self.as_mut().set_failed_count(count);
|
||||||
|
self.as_mut()
|
||||||
|
.set_containers_json(QString::from(containers_json));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString) {
|
fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString) {
|
||||||
|
|
|
||||||
|
|
@ -13,31 +13,38 @@ Column {
|
||||||
|
|
||||||
onHeightChanged: root.contentResized()
|
onHeightChanged: root.contentResized()
|
||||||
|
|
||||||
|
// Local machine header + units
|
||||||
|
SystemdMachineSection {
|
||||||
|
width: root.width
|
||||||
|
accentColor: root.accentColor
|
||||||
|
machineName: ""
|
||||||
|
title: S.SystemdService.hostname
|
||||||
|
marker: " this machine"
|
||||||
|
systemState: S.SystemdService.systemState
|
||||||
|
units: S.SystemdService.failedUnits
|
||||||
|
startExpanded: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Containers
|
||||||
Repeater {
|
Repeater {
|
||||||
model: S.SystemdService.machines
|
model: S.SystemdService.containers
|
||||||
|
|
||||||
delegate: Column {
|
delegate: Column {
|
||||||
id: _row
|
id: _row
|
||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
|
||||||
width: root.width
|
width: root.width
|
||||||
|
|
||||||
Separator {
|
Separator {}
|
||||||
visible: _row.index > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemdMachineSection {
|
SystemdMachineSection {
|
||||||
width: _row.width
|
width: _row.width
|
||||||
accentColor: root.accentColor
|
accentColor: root.accentColor
|
||||||
machineName: _row.modelData.isLocal ? "" : _row.modelData.name
|
machineName: _row.modelData.name
|
||||||
title: _row.modelData.name
|
title: _row.modelData.name
|
||||||
marker: _row.modelData.marker ?? ""
|
marker: ""
|
||||||
systemState: _row.modelData.systemState ?? "unknown"
|
systemState: _row.modelData.systemState ?? "unknown"
|
||||||
runningCount: _row.modelData.runningCount ?? 0
|
units: _row.modelData.failedUnits ?? []
|
||||||
totalCount: _row.modelData.totalCount ?? 0
|
startExpanded: (_row.modelData.failedUnits ?? []).length > 0
|
||||||
failedUnits: _row.modelData.failedUnits ?? []
|
|
||||||
runningUnits: _row.modelData.runningUnits ?? []
|
|
||||||
onContentResized: root.contentResized()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import QtQuick
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import NovaStats as NS
|
import NovaStats as NS
|
||||||
|
|
||||||
// One section of the systemd applet: a header with title, aggregate counts,
|
// One section of the systemd applet: a header with title + state chip,
|
||||||
// state chip; an auto-expanded list of failed units (hidden when empty); a
|
// expandable list of failed units underneath.
|
||||||
// lazy-loaded, collapsed-by-default list of running units.
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
|
@ -15,21 +14,13 @@ Column {
|
||||||
required property string title
|
required property string title
|
||||||
required property string marker
|
required property string marker
|
||||||
required property string systemState
|
required property string systemState
|
||||||
required property int runningCount
|
required property var units
|
||||||
required property int totalCount
|
property bool startExpanded: false
|
||||||
required property var failedUnits
|
|
||||||
required property var runningUnits
|
|
||||||
|
|
||||||
signal contentResized
|
|
||||||
onHeightChanged: root.contentResized()
|
|
||||||
|
|
||||||
width: parent?.width ?? 0
|
width: parent?.width ?? 0
|
||||||
|
|
||||||
property bool _runningExpanded: false
|
property bool _expanded: startExpanded
|
||||||
|
|
||||||
readonly property int _failedCount: (failedUnits ?? []).length
|
|
||||||
|
|
||||||
// Header
|
|
||||||
Item {
|
Item {
|
||||||
width: root.width
|
width: root.width
|
||||||
height: 32
|
height: 32
|
||||||
|
|
@ -60,29 +51,10 @@ Column {
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggregate counts: "n running, m/total failed" or "n running" if no failures.
|
|
||||||
Text {
|
|
||||||
id: _counts
|
|
||||||
anchors.right: _stateChip.left
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: {
|
|
||||||
if (root.totalCount === 0)
|
|
||||||
return "";
|
|
||||||
const r = root.runningCount + " running";
|
|
||||||
if (root._failedCount > 0)
|
|
||||||
return r + ", " + root._failedCount + "/" + root.totalCount + " failed";
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
color: NS.ThemeService.base04
|
|
||||||
font.pixelSize: NS.ThemeService.fontSize - 3
|
|
||||||
font.family: NS.ThemeService.fontFamily
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: _stateChip
|
id: _stateChip
|
||||||
anchors.right: parent.right
|
anchors.right: _chevron.left
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 8
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.systemState !== "unknown"
|
visible: root.systemState !== "unknown"
|
||||||
color: {
|
color: {
|
||||||
|
|
@ -91,8 +63,6 @@ Column {
|
||||||
return NS.ThemeService.base0B;
|
return NS.ThemeService.base0B;
|
||||||
if (st === "degraded")
|
if (st === "degraded")
|
||||||
return NS.ThemeService.base0A;
|
return NS.ThemeService.base0A;
|
||||||
if (st === "pending")
|
|
||||||
return NS.ThemeService.base04;
|
|
||||||
return NS.ThemeService.base08;
|
return NS.ThemeService.base08;
|
||||||
}
|
}
|
||||||
opacity: 0.85
|
opacity: 0.85
|
||||||
|
|
@ -109,71 +79,25 @@ Column {
|
||||||
font.family: NS.ThemeService.fontFamily
|
font.family: NS.ThemeService.fontFamily
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Failed units (auto-expanded; entire block hidden when there are none).
|
|
||||||
Repeater {
|
|
||||||
model: root._failedCount > 0 ? root.failedUnits : []
|
|
||||||
delegate: SystemdUnitRow {
|
|
||||||
required property var modelData
|
|
||||||
unitName: modelData.name
|
|
||||||
description: modelData.description ?? ""
|
|
||||||
subState: modelData.subState ?? ""
|
|
||||||
scope: modelData.scope ?? "system"
|
|
||||||
machineName: root.machineName
|
|
||||||
accentColor: root.accentColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Running units toggle row (only meaningful when there are running units to show).
|
|
||||||
Item {
|
|
||||||
visible: (root.runningUnits ?? []).length > 0
|
|
||||||
width: root.width
|
|
||||||
height: 26
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: 4
|
|
||||||
anchors.rightMargin: 4
|
|
||||||
color: _runHdrHover.hovered ? NS.ThemeService.base02 : "transparent"
|
|
||||||
radius: NS.ThemeService.radius
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: _runHdrHover
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 24
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "running units (" + (root.runningUnits ?? []).length + ")"
|
|
||||||
color: NS.ThemeService.base04
|
|
||||||
font.pixelSize: NS.ThemeService.fontSize - 2
|
|
||||||
font.family: NS.ThemeService.fontFamily
|
|
||||||
font.letterSpacing: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
id: _chevron
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: root._runningExpanded ? "" : ""
|
text: root._expanded ? "" : ""
|
||||||
color: NS.ThemeService.base04
|
color: NS.ThemeService.base04
|
||||||
font.pixelSize: NS.ThemeService.fontSize - 2
|
font.pixelSize: NS.ThemeService.fontSize - 2
|
||||||
font.family: NS.ThemeService.iconFontFamily
|
font.family: NS.ThemeService.iconFontFamily
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: root._runningExpanded = !root._runningExpanded
|
onTapped: root._expanded = !root._expanded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy-loaded running units list. Repeater materializes rows only when the
|
|
||||||
// model is non-empty, so feeding `[]` while collapsed avoids per-row cost.
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root._runningExpanded ? root.runningUnits : []
|
model: root._expanded ? root.units : []
|
||||||
delegate: SystemdUnitRow {
|
delegate: SystemdUnitRow {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
unitName: modelData.name
|
unitName: modelData.name
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,29 @@ import QtQuick
|
||||||
import NovaStats as NS
|
import NovaStats as NS
|
||||||
|
|
||||||
// Thin wrapper around NS.SystemdService: drives the poll Timer and parses the
|
// Thin wrapper around NS.SystemdService: drives the poll Timer and parses the
|
||||||
// JSON-encoded machine tree into a JS array for QML consumers.
|
// JSON-encoded list properties into JS arrays for QML consumers. Restart helper
|
||||||
|
// proxies through to the Rust singleton.
|
||||||
QtObject {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property string hostname: NS.SystemdService.hostname
|
readonly property string hostname: NS.SystemdService.hostname
|
||||||
|
readonly property string systemState: NS.SystemdService.systemState
|
||||||
|
readonly property string userState: NS.SystemdService.userState
|
||||||
readonly property int failedCount: NS.SystemdService.failedCount
|
readonly property int failedCount: NS.SystemdService.failedCount
|
||||||
|
|
||||||
// [{ name, isLocal, marker, systemState, runningCount, totalCount,
|
// Parsed [{ name, description, subState, scope, machine }, ...]
|
||||||
// failedUnits: [...], runningUnits: [...] }, ...]
|
readonly property var failedUnits: {
|
||||||
readonly property var machines: {
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(NS.SystemdService.machinesJson);
|
return JSON.parse(NS.SystemdService.failedUnitsJson);
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsed [{ name, class, service, systemState, failedUnits: [...] }, ...]
|
||||||
|
readonly property var containers: {
|
||||||
|
try {
|
||||||
|
return JSON.parse(NS.SystemdService.containersJson);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue