repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/SystemPanel.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import "sysinfo.js" as Sys
Item {
id: root
required property Theme theme
readonly property Flickable scrollView: contentScroll
required property Resources resourceService
implicitHeight: contentColumn.implicitHeight
height: parent ? parent.height : implicitHeight
// service handle; identity facts and the live issue ARRAY come straight
// off Resources.qml (no per-frame JSON parse).
readonly property var rs: root.resourceService
readonly property var identity: rs ? rs.identity : ({})
// touch the inputs so the band re-evaluates when the slow/rare loops
// mutate them; issues() itself is a plain function call.
property var issueList: []
readonly property var currentIssues: {
if (!rs)
return [];
rs.failedUnits;
rs.incidents;
rs.cpuTemperatureC;
rs.diskUsage;
rs.updateCount;
rs.memoryUsage;
rs.swapGrowing;
rs.loadOne;
rs.healthPercentage;
return rs.issues();
}
onCurrentIssuesChanged: {
now = Date.now();
if (JSON.stringify(currentIssues) !== JSON.stringify(issueList))
issueList = currentIssues;
}
property real now: Date.now()
Timer {
interval: 30000
running: true
repeat: true
onTriggered: root.now = Date.now()
}
// popoutOpen is a WRITE contract: it gates the slow + rare loops.
Component.onCompleted: {
if (rs)
rs.popoutOpen = true;
}
Component.onDestruction: {
if (rs)
rs.popoutOpen = false;
}
Flickable {
id: contentScroll
anchors.fill: parent
contentHeight: contentColumn.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
function revealItem(item) {
const point = item.mapToItem(contentColumn, 0, 0);
if (point.y < contentY)
contentY = point.y;
else if (point.y + item.height > contentY + height)
contentY = Math.min(point.y + item.height - height, Math.max(0, contentHeight - height));
}
Column {
id: contentColumn
width: contentScroll.width
spacing: 10
// ---- two rounded cards: identity | live, split on the golden ratio
// (live gets the larger ~0.618 share, that's where the live data lives) ----
Row {
id: cardRow
readonly property real avail: Math.max(1, width - spacing)
readonly property real machineW: avail * 0.382
readonly property real liveW: avail * 0.618
width: parent.width
height: 292
spacing: 10
// ===== MACHINE IDENTITY =====
PopupCard {
theme: root.theme
padding: 0
width: cardRow.machineW
height: cardRow.height
Column {
anchors.fill: parent
anchors.margins: 14
spacing: 6
Row {
spacing: 8
Text {
text: ""
color: root.theme ? root.theme.ui.accent : "white"
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 15
}
NuguText {
theme: root.theme
variant: "title"
text: "machine"
}
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.host || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.model || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.distro || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.kernel || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.wm || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.packages ? `${root.identity.packages} packages` : "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.shell || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
value: root.identity.uptime || "—"
}
IdentityRow {
theme: root.theme
glyph: ""
// battery row hidden entirely when there is no laptop battery
visible: root.rs && root.rs.isLaptopBattery
value: {
if (!root.rs)
return "—";
const p = root.rs.batteryPercent;
const st = root.rs.batteryStatus;
const h = root.rs.healthPercentage;
let s = (p === null || p === undefined) ? "battery n/a" : `battery ${p}%`;
if (st)
s += ` · ${st}`;
if (h !== null && h !== undefined)
s += ` · health ${Math.round(Number(h))}%`;
return s;
}
}
}
}
// ===== LIVE STATS =====
PopupCard {
theme: root.theme
padding: 0
width: cardRow.liveW
height: cardRow.height
Column {
anchors.fill: parent
anchors.margins: 14
spacing: 8
Row {
spacing: 8
Text {
text: ""
color: root.theme ? root.theme.ui.accent : "white"
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 15
}
NuguText {
theme: root.theme
variant: "title"
text: "live"
}
}
MetricRow {
theme: root.theme
label: "cpu"
value: root.rs ? `${Math.round(root.rs.cpuUsage * 100)}%` : "—"
fraction: root.rs ? root.rs.cpuUsage : 0
barColor: root.theme ? root.theme.ui.accent : "#d86dd8"
ring: root.rs ? root.rs.cpuRing : []
sparkScaleMax: 1
}
MetricRow {
theme: root.theme
label: "mem"
value: root.rs ? `${Math.round(root.rs.memoryUsage * 100)}% · ${Sys.formatBytes(Math.max(0, root.rs.memTotal - root.rs.memAvailable))}` : "—"
fraction: root.rs ? root.rs.memoryUsage : 0
barColor: root.theme ? root.theme.ui.focus : "#9aabae"
ring: root.rs ? root.rs.memRing : []
sparkScaleMax: 1
}
MetricRow {
theme: root.theme
label: "swap"
value: {
if (!root.rs || root.rs.swapTotal <= 0)
return "off";
return `${Math.round(root.rs.swapUsage * 100)}% · ${Sys.formatBytes(Math.max(0, root.rs.swapTotal - root.rs.swapFree))}`;
}
fraction: root.rs ? root.rs.swapUsage : 0
barColor: root.theme ? root.theme.warning : "#f0ee69"
showSpark: false
ring: []
}
MetricRow {
theme: root.theme
label: "temp"
value: {
if (!root.rs)
return "n/a";
const t = root.rs.cpuTemperatureC;
const f = root.rs.fanRpm;
const ts = (t === null || t === undefined) ? "n/a" : `${Math.round(Number(t))}°C`;
const fs = (f === null || f === undefined) ? "" : ` · ${Math.round(Number(f))} rpm`;
return ts + fs;
}
fraction: {
if (!root.rs)
return 0;
const t = root.rs.cpuTemperatureC;
if (t === null || t === undefined)
return 0;
return Math.max(0, Math.min(1, Number(t) / Math.max(1, root.rs.tempHighC)));
}
barColor: root.theme ? root.theme.error : "#ec6376"
// sparkline hidden when the sensor is unavailable (null)
showSpark: root.rs && root.rs.cpuTemperatureC !== null
ring: root.rs ? root.rs.tempRing : []
sparkScaleMax: root.rs ? Math.max(1, root.rs.tempHighC) : 1
}
MetricRow {
theme: root.theme
label: "net"
value: root.rs ? `↓ ${Sys.formatRate(root.rs.networkRxRate)} ↑ ${Sys.formatRate(root.rs.networkTxRate)}` : "—"
// net/disk have no natural ceiling: scale against the ring's own max
fraction: {
if (!root.rs)
return 0;
const ring = root.rs.netRing || [];
let m = 0;
for (let i = 0; i < ring.length; i++)
m = Math.max(m, Number(ring[i]) || 0);
const cur = root.rs.networkRxRate + root.rs.networkTxRate;
return m > 0 ? Math.min(1, cur / m) : 0;
}
// directional spark: ↑ upload (accent) above the line,
// ↓ download (info) below; fill-bar stays combined.
barColor: root.theme ? root.theme.ui.accent : "#d86dd8"
splitSpark: true
ring: root.rs ? root.rs.netTxRing : []
ringDown: root.rs ? root.rs.netRxRing : []
barColorDown: root.theme ? root.theme.info : "#3ec5e3"
}
MetricRow {
theme: root.theme
label: "disk"
value: root.rs ? `R ${Sys.formatRate(root.rs.diskReadRate)} W ${Sys.formatRate(root.rs.diskWriteRate)}` : "—"
fraction: {
if (!root.rs)
return 0;
const ring = root.rs.diskRing || [];
let m = 0;
for (let i = 0; i < ring.length; i++)
m = Math.max(m, Number(ring[i]) || 0);
const cur = root.rs.diskReadRate + root.rs.diskWriteRate;
return m > 0 ? Math.min(1, cur / m) : 0;
}
// directional spark: R read (accent) above the line,
// W write (info) below; fill-bar stays combined.
barColor: root.theme ? root.theme.ui.accent : "#d86dd8"
splitSpark: true
ring: root.rs ? root.rs.diskReadRing : []
ringDown: root.rs ? root.rs.diskWriteRing : []
barColorDown: root.theme ? root.theme.info : "#3ec5e3"
}
NuguText {
theme: root.theme
variant: "meta"
text: root.rs ? `load ${root.rs.loadOne.toFixed(2)} · ${root.rs.loadFive.toFixed(2)} · ${root.rs.loadFifteen.toFixed(2)} (${root.rs.cores} cores)` : "load —"
}
}
}
}
// ---- compact top-processes row (chips) ----
Flow {
width: parent.width
spacing: 8
Repeater {
model: root.rs ? root.rs.processes : []
delegate: PopupBadge {
id: procChip
required property var modelData
theme: root.theme
text: `${String(modelData.name || "?")} · ${Number(modelData.cpu || 0).toFixed(0)}%`
role: "normal"
}
}
}
// Current conditions and historical events have different meanings.
Repeater {
model: [false, true]
delegate: Column {
id: issueGroup
required property bool modelData
readonly property var entries: root.issueList.filter(issue => !!issue.context === modelData)
width: contentColumn.width
spacing: 8
Row {
width: parent.width
spacing: 12
NuguText {
theme: root.theme
variant: "title"
text: issueGroup.modelData ? "incident history" : "needs attention"
}
NuguText {
theme: root.theme
variant: "meta"
color: root.theme.ui.minor
text: issueGroup.modelData ? issueGroup.entries.reduce((count, issue) => count + (issue.events ? issue.events.length : 1), 0) + " recorded" : issueGroup.entries.length + (issueGroup.entries.length === 1 ? " problem" : " problems")
}
PopupButton {
visible: issueGroup.modelData
theme: root.theme
icon: ""
showText: false
width: 24
height: 24
accessibleName: "incident coverage"
tooltip: "recorded crashes and oom kills · current boot · last 24 hours · latest 100\nnot every app exit produces a record"
}
}
NuguText {
visible: issueGroup.entries.length === 0 || issueGroup.modelData && root.rs && root.rs.incidentStatus !== "ready"
theme: root.theme
width: parent.width
wrapMode: Text.Wrap
variant: "meta"
text: issueGroup.modelData ? (root.rs && root.rs.incidentStatus === "ready" ? "no recent crashes or oom kills recorded" : "incident history unavailable or incomplete") : "disk · memory · battery · updates ok"
}
Repeater {
model: issueGroup.entries
delegate: SystemIssueRow {
id: issueRow
required property var modelData
theme: root.theme
width: issueGroup.width
issue: modelData
now: root.now
pending: root.rs && root.rs.agentPending && root.rs.agentIssueId === modelData.id
launchBusy: root.rs && root.rs.agentPending
launchError: root.rs && root.rs.agentIssueId === modelData.id ? root.rs.agentError : ""
onFocusEntered: Qt.callLater(contentScroll.revealItem, issueRow)
onOpenRequested: if (root.rs)
root.rs.runIssueAction(modelData.id)
onAgentRequested: if (root.rs)
root.rs.runIssueAgent(modelData.id)
}
}
}
}
}
}
}