systemd applet: aggregate counts + lazy running list (step 2)

This commit is contained in:
Damocles 2026-05-07 19:01:42 +02:00
parent 6fc7e8bc8a
commit 3fb9a36f3b
4 changed files with 210 additions and 123 deletions

View file

@ -13,38 +13,31 @@ Column {
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 {
model: S.SystemdService.containers
model: S.SystemdService.machines
delegate: Column {
id: _row
required property var modelData
required property int index
width: root.width
Separator {}
Separator {
visible: _row.index > 0
}
SystemdMachineSection {
width: _row.width
accentColor: root.accentColor
machineName: _row.modelData.name
machineName: _row.modelData.isLocal ? "" : _row.modelData.name
title: _row.modelData.name
marker: ""
marker: _row.modelData.marker ?? ""
systemState: _row.modelData.systemState ?? "unknown"
units: _row.modelData.failedUnits ?? []
startExpanded: (_row.modelData.failedUnits ?? []).length > 0
runningCount: _row.modelData.runningCount ?? 0
totalCount: _row.modelData.totalCount ?? 0
failedUnits: _row.modelData.failedUnits ?? []
runningUnits: _row.modelData.runningUnits ?? []
onContentResized: root.contentResized()
}
}
}

View file

@ -4,8 +4,9 @@ import QtQuick
import "../services" as S
import NovaStats as NS
// One section of the systemd applet: a header with title + state chip,
// expandable list of failed units underneath.
// One section of the systemd applet: a header with title, aggregate counts,
// state chip; an auto-expanded list of failed units (hidden when empty); a
// lazy-loaded, collapsed-by-default list of running units.
Column {
id: root
@ -14,13 +15,21 @@ Column {
required property string title
required property string marker
required property string systemState
required property var units
property bool startExpanded: false
required property int runningCount
required property int totalCount
required property var failedUnits
required property var runningUnits
signal contentResized
onHeightChanged: root.contentResized()
width: parent?.width ?? 0
property bool _expanded: startExpanded
property bool _runningExpanded: false
readonly property int _failedCount: (failedUnits ?? []).length
// Header
Item {
width: root.width
height: 32
@ -51,10 +60,29 @@ Column {
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 {
id: _stateChip
anchors.right: _chevron.left
anchors.rightMargin: 8
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
visible: root.systemState !== "unknown"
color: {
@ -79,25 +107,71 @@ Column {
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 {
id: _chevron
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._expanded ? "" : ""
text: root._runningExpanded ? "" : ""
color: NS.ThemeService.base04
font.pixelSize: NS.ThemeService.fontSize - 2
font.family: NS.ThemeService.iconFontFamily
}
TapHandler {
onTapped: root._expanded = !root._expanded
onTapped: root._runningExpanded = !root._runningExpanded
}
}
// Lazy-loaded running units list. Repeater materializes rows only when the
// model is non-empty, so feeding `[]` while collapsed avoids per-row cost.
Repeater {
model: root._expanded ? root.units : []
model: root._runningExpanded ? root.runningUnits : []
delegate: SystemdUnitRow {
required property var modelData
unitName: modelData.name

View file

@ -4,29 +4,18 @@ import QtQuick
import NovaStats as NS
// Thin wrapper around NS.SystemdService: drives the poll Timer and parses the
// JSON-encoded list properties into JS arrays for QML consumers. Restart helper
// proxies through to the Rust singleton.
// JSON-encoded machine tree into a JS array for QML consumers.
QtObject {
id: root
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
// Parsed [{ name, description, subState, scope, machine }, ...]
readonly property var failedUnits: {
// [{ name, isLocal, marker, systemState, runningCount, totalCount,
// failedUnits: [...], runningUnits: [...] }, ...]
readonly property var machines: {
try {
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);
return JSON.parse(NS.SystemdService.machinesJson);
} catch (e) {
return [];
}