import QtQuick import qs.config import qs.services SettingsPage { id: root objectName: "system-health-page" title: "System Health" lede: "Panama checks the parts of your desktop it owns and explains what needs attention." property var pendingConfirmation: null property string instructionTarget: "" readonly property var issueChecks: Health.checks.filter(check => check.status === "warning" || check.status === "error") readonly property var groups: [ { group: "desktop-foundation", title: "Desktop foundation", subtitle: "Compositor, shell, portals, wallpaper, idle policy, and launcher." }, { group: "input-media", title: "Input & media", subtitle: "Sound, clipboard, capture, OCR, and display controls." }, { group: "integrations", title: "Integrations", subtitle: "Only configured integrations affect health." }, { group: "panama-tools", title: "Panama tools", subtitle: "Tracked links, launcher commands, apps, and inhibitors." } ] readonly property var applicationTargets: ({ "integration.nextcloud": "nextcloud", "integration.rustdesk": "rustdesk", "integration.kdeconnect": "kdeconnect", "integration.bluebubbles": "bluebubbles" }) function statusLabel(status: string): string { if (status === "ok") return "Healthy"; if (status === "warning") return "Needs attention"; if (status === "error") return "Action required"; return "Not set up"; } function checksForGroup(group: string): var { return Health.checks.filter(check => check.group === group); } function handleAction(check: var): void { if (!check || !check.action) return; if (check.action.kind === "open") { if (check.action.target) { ShellState.openSettings(check.action.target); return; } const application = root.applicationTargets[check.id]; if (application) SystemSettings.openApplication(application); return; } if (check.action.kind === "instructions") { root.instructionTarget = check.action.target || ""; return; } if (check.action.kind !== "repair") return; if (check.action.confirm) { root.pendingConfirmation = check; return; } Health.repair(check.id, false); } function confirmRepair(): void { const check = root.pendingConfirmation; root.pendingConfirmation = null; if (check) Health.repair(check.id, false); } function descendants(item: var, prefix: string): var { let matches = []; if (!item) return matches; if (String(item.objectName || "").indexOf(prefix) === 0) matches.push(item); for (const child of item.children || []) matches = matches.concat(root.descendants(child, prefix)); return matches; } // Deterministic, read-only fixture seam used by the offscreen contract. function uiDiagnostics(): var { const summaries = root.descendants(root, "health-summary"); const rows = root.descendants(root, "health-check-row:").filter(row => row.visible); const copyButtons = root.descendants(root, "health-copy-report-button"); const refreshButtons = root.descendants(root, "health-refresh-button"); const rowActions = root.descendants(root, "health-row-action:").filter(button => button.visible); const checkingLabels = root.descendants(root, "health-checking-label").filter(label => label.visible); return { issueIds: root.issueChecks.map(check => check.id), desktopIds: root.checksForGroup("desktop-foundation").map(check => check.id), integrationIds: root.checksForGroup("integrations").map(check => check.id), statusLabels: Health.checks.map(check => root.statusLabel(check.status)), summaryHeight: summaries.length > 0 ? summaries[0].height : 0, rowHeights: rows.map(row => row.height), checking: Health.busy, checkingText: checkingLabels.length > 0 ? checkingLabels[0].text : "", copyFocusable: copyButtons.length > 0 && copyButtons[0].activeFocusOnTab, refreshFocusable: refreshButtons.length > 0 && refreshButtons[0].activeFocusOnTab, rowActionFocusable: rowActions.length > 0 && rowActions[0].activeFocusOnTab, confirmationVisible: root.pendingConfirmation !== null, confirmationId: root.pendingConfirmation ? root.pendingConfirmation.id : "" }; } Component.onCompleted: Health.refresh() header: Component { Rectangle { objectName: "health-confirmation-sheet" visible: root.pendingConfirmation !== null implicitHeight: visible ? confirmRow.implicitHeight + 28 : 0 radius: Theme.cardRadius color: Theme.mix(Theme.bgPanel, Theme.warn, 0.1) border.width: 1 border.color: Theme.alpha(Theme.warn, 0.34) Row { id: confirmRow anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.margins: 16 spacing: 14 Column { width: parent.width - cancelButton.width - repairButton.width - 28 anchors.verticalCenter: parent.verticalCenter spacing: 3 Text { width: parent.width text: root.pendingConfirmation ? `${root.pendingConfirmation.action.label}?` : "Restart Panama?" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSize font.weight: Font.DemiBold } Text { width: parent.width text: "The desktop chrome will disappear briefly and return when Quickshell restarts." color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall wrapMode: Text.WordWrap } } SettingsButton { id: cancelButton text: "Cancel" activeFocusOnTab: true border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08) onClicked: root.pendingConfirmation = null Keys.onReturnPressed: root.pendingConfirmation = null Keys.onSpacePressed: root.pendingConfirmation = null } SettingsButton { id: repairButton text: root.pendingConfirmation ? root.pendingConfirmation.action.label : "Restart Panama" activeFocusOnTab: true border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08) onClicked: root.confirmRepair() Keys.onReturnPressed: root.confirmRepair() Keys.onSpacePressed: root.confirmRepair() } } } } HealthSummary {} SettingsCard { visible: root.issueChecks.length > 0 title: Health.status === "error" ? "Action required" : "Needs attention" subtitle: Health.status === "error" ? "Resolve these items first. Healthy systems remain listed below." : "Nothing here prevents you from using the desktop." Item { width: parent.width implicitHeight: issueRows.implicitHeight Rectangle { anchors.left: parent.left anchors.leftMargin: 4 anchors.top: parent.top anchors.topMargin: 25 anchors.bottom: parent.bottom anchors.bottomMargin: 25 width: 1 visible: root.issueChecks.length > 1 color: Theme.alpha(Health.status === "error" ? Theme.danger : Theme.warn, 0.44) } Column { id: issueRows width: parent.width Repeater { id: issueRepeater model: root.issueChecks HealthCheckRow { required property var modelData required property int index width: issueRows.width check: modelData issue: true divider: index < issueRepeater.count - 1 onActionRequested: check => root.handleAction(check) } } } } } SettingsCard { visible: root.instructionTarget === "ddc-permissions" title: "External monitor brightness" subtitle: "Panama can see DDC/CI support, but this session cannot access the monitor bus." Item { width: parent.width implicitHeight: Math.max(instructionCopy.implicitHeight, doneButton.implicitHeight) + 8 Text { id: instructionCopy anchors.left: parent.left anchors.right: doneButton.left anchors.rightMargin: 18 anchors.verticalCenter: parent.verticalCenter text: "Reload the installed udev rules and trigger the i2c-dev and DRM devices. Sign out and back in if monitor access is still unavailable." color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall wrapMode: Text.WordWrap } SettingsButton { id: doneButton anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Done" activeFocusOnTab: true border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08) onClicked: root.instructionTarget = "" Keys.onReturnPressed: root.instructionTarget = "" Keys.onSpacePressed: root.instructionTarget = "" } } } Grid { id: groupGrid width: parent.width columns: width >= 700 ? 2 : 1 columnSpacing: 16 rowSpacing: 16 Repeater { model: root.groups SettingsCard { required property var modelData width: groupGrid.columns === 2 ? (groupGrid.width - groupGrid.columnSpacing) / 2 : groupGrid.width title: modelData.title subtitle: modelData.subtitle Column { id: groupRows width: parent.width Repeater { id: groupRepeater model: root.checksForGroup(modelData.group) HealthCheckRow { required property var modelData required property int index width: groupRows.width check: modelData divider: index < groupRepeater.count - 1 onActionRequested: check => root.handleAction(check) } } } } } } SettingsCard { title: "Fedora system settings" subtitle: "Network accounts, printers, users, and Fedora updates remain owned by system tools." Item { width: parent.width implicitHeight: 42 Text { anchors.left: parent.left anchors.right: gnomeSettingsButton.left anchors.rightMargin: 18 anchors.verticalCenter: parent.verticalCenter text: "Use GNOME Settings for the parts of the system Panama does not manage." color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall wrapMode: Text.WordWrap } SettingsButton { id: gnomeSettingsButton anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Open GNOME Settings" activeFocusOnTab: true border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08) onClicked: SystemSettings.openGnomePanel("network") Keys.onReturnPressed: SystemSettings.openGnomePanel("network") Keys.onSpacePressed: SystemSettings.openGnomePanel("network") } } } }