This repository has been archived on 2026-07-13. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nova-shell/shell/applets/SystemdMachineSection.qml

269 lines
9.3 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import "../services" as S
import NovaStats as NS
// 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
required property color accentColor
required property string machineName
required property string title
required property string marker
required property string systemState
required property int runningCount
required property int totalCount
required property var failedUnits
required property var runningUnits
property string errorKind: ""
property string errorReason: ""
property int lastSeen: 0
property var containers: []
property int depth: 0
signal contentResized
onHeightChanged: root.contentResized()
width: parent?.width ?? 0
property bool _runningExpanded: false
readonly property int _failedCount: (failedUnits ?? []).length
// Header
Item {
width: root.width
height: 32
Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
color: _hdrHover.hovered ? NS.ThemeService.base02 : "transparent"
radius: NS.ThemeService.radius
z: -1
}
HoverHandler {
id: _hdrHover
}
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: _stateChip.left
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
text: root.title + (root.marker !== "" ? " " + root.marker : "")
color: NS.ThemeService.base05
font.pixelSize: NS.ThemeService.fontSize
font.family: NS.ThemeService.fontFamily
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: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
visible: root.systemState !== "unknown"
color: {
const st = root.systemState;
if (root.errorKind === "permanent")
return NS.ThemeService.base08;
if (root.errorKind === "transient")
return NS.ThemeService.base04;
if (st === "running")
return NS.ThemeService.base0B;
if (st === "degraded")
return NS.ThemeService.base0A;
if (st === "pending")
return NS.ThemeService.base04;
if (st === "unreachable")
return NS.ThemeService.base04;
return NS.ThemeService.base08;
}
opacity: 0.85
radius: 3
width: _stateLbl.width + 8
height: 14
Text {
id: _stateLbl
anchors.centerIn: parent
text: root.systemState
color: NS.ThemeService.base00
font.pixelSize: NS.ThemeService.fontSize - 3
font.family: NS.ThemeService.fontFamily
}
}
}
// Error / last-seen line for remote machines. Hidden when there's no
// error and the machine is reachable (lastSeen > 0 with no errorKind).
Item {
visible: root.errorKind !== "" || (root.lastSeen > 0 && root.systemState === "unreachable")
width: root.width
height: visible ? 22 : 0
Text {
anchors.left: parent.left
anchors.leftMargin: 24
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: {
const ago = root.lastSeen > 0 ? " (last seen " + _agoString(root.lastSeen) + ")" : "";
if (root.errorReason !== "")
return root.errorReason + ago;
return ago.replace(/^\s+/, "");
}
color: root.errorKind === "permanent" ? NS.ThemeService.base08 : NS.ThemeService.base03
font.pixelSize: NS.ThemeService.fontSize - 3
font.family: NS.ThemeService.fontFamily
elide: Text.ElideRight
}
}
function _agoString(unix) {
const now = Math.floor(Date.now() / 1000);
const d = Math.max(0, now - unix);
if (d < 60)
return d + "s ago";
if (d < 3600)
return Math.floor(d / 60) + "m ago";
if (d < 86400)
return Math.floor(d / 3600) + "h ago";
return Math.floor(d / 86400) + "d ago";
}
// 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 {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: root._runningExpanded ? "" : ""
color: NS.ThemeService.base04
font.pixelSize: NS.ThemeService.fontSize - 2
font.family: NS.ThemeService.iconFontFamily
}
TapHandler {
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._runningExpanded ? root.runningUnits : []
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
}
}
// 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()
}
}
}
}