nova-shell/shell/services/MachinectlService.qml

152 lines
5.5 KiB
QML

pragma Singleton
import QtQuick
import Quickshell.Io
import "." as S
import NovaStats as NS
QtObject {
id: root
property var machines: []
// cache: machineName -> {state, units, loading}
property var _cache: ({})
readonly property bool anyUnhealthy: {
for (const k of Object.keys(_cache)) {
if ((_cache[k]?.units?.length ?? 0) > 0)
return true;
}
return false;
}
function machineState(name) {
return _cache[name]?.state ?? "unknown";
}
function machineUnits(name) {
return _cache[name]?.units ?? [];
}
function machineLoading(name) {
return _cache[name]?.loading ?? false;
}
signal machineReady(string machineName, string state, var units)
signal machineJournalReady(string machineName, string unitName, string text)
// Per-call state: kept on root (not on the inline Process objects) so qmllint can see them.
property string _machineName: ""
property string _journalMachine: ""
property string _journalUnit: ""
property string _restartMachine: ""
function fetchMachine(name) {
const c = Object.assign({}, _cache);
c[name] = Object.assign({}, c[name] ?? {}, {
loading: true
});
_cache = c;
root._machineName = name;
_machineProc.command = ["sh", "-c", "busctl get-property --json=short --machine=" + name + " org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager SystemState 2>/dev/null || echo '{}'; " + "busctl call --json=short --machine=" + name + " org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager ListUnitsFiltered as 1 failed 2>/dev/null || echo '{}'"];
if (_machineProc.running)
_machineProc.running = false;
_machineProc.running = true;
}
function fetchMachineJournal(machineName, unitName) {
root._journalMachine = machineName;
root._journalUnit = unitName;
_machineJournalProc.command = ["journalctl", "-M", machineName, "-u", unitName, "-n", "80", "--no-pager", "--output=short-precise"];
if (_machineJournalProc.running)
_machineJournalProc.running = false;
_machineJournalProc.running = true;
}
function restartMachineUnit(machineName, unitName) {
root._restartMachine = machineName;
_machineRestartProc.command = ["pkexec", "systemctl", "-M", machineName, "restart", unitName];
if (_machineRestartProc.running)
_machineRestartProc.running = false;
_machineRestartProc.running = true;
}
property Timer _poll: Timer {
interval: NS.ModulesService.machinectlInterval ?? 15000
running: NS.ModulesService.machinectlEnable
repeat: true
triggeredOnStart: true
onTriggered: if (!root._listProc.running)
root._listProc.running = true
}
property Process _listProc: Process {
command: ["busctl", "call", "--json=short", "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", "ListMachines"]
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(text.trim());
const newMachines = (parsed.data?.[0] || []).map(m => ({
name: m[0],
class: m[1],
service: m[2]
}));
root.machines = newMachines;
// drop stale cache entries
const names = new Set(newMachines.map(m => m.name));
const c = {};
for (const k of Object.keys(root._cache)) {
if (names.has(k))
c[k] = root._cache[k];
}
root._cache = c;
} catch (e) {
root.machines = [];
}
}
}
}
// 2 lines of output: state, failed-units
property Process _machineProc: Process {
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n");
let state = "unknown";
let units = [];
try {
state = JSON.parse(lines[0] ?? "").data || "unknown";
} catch (e) {}
try {
const parsed = JSON.parse(lines[1] ?? "");
units = (parsed.data?.[0] || []).map(u => ({
name: u[0],
description: u[1],
loadState: u[2],
activeState: u[3],
subState: u[4]
}));
} catch (e) {}
const c = Object.assign({}, root._cache);
c[root._machineName] = {
state: state,
units: units,
loading: false
};
root._cache = c;
root.machineReady(root._machineName, state, units);
}
}
}
property Process _machineJournalProc: Process {
stdout: StdioCollector {
onStreamFinished: root.machineJournalReady(root._journalMachine, root._journalUnit, text)
}
}
property Process _machineRestartProc: Process {
onRunningChanged: if (!running && root._restartMachine !== "")
root.fetchMachine(root._restartMachine)
}
}