import QtQuick import qs.Common import qs.Services import qs.Widgets Item { id: widget readonly property var activeModules: ["system", "cpu", "memory", "network", "disk", "diskmounts"] readonly property var rootMount: { const mounts = DgopService.diskMounts || []; return mounts.find(mount => mount.mount === "/") || mounts[0] || null; } readonly property real diskPercent: { if (!rootMount || !rootMount.percent) return 0; return parseFloat(String(rootMount.percent).replace("%", "")) || 0; } function formatRate(bytesPerSecond) { const value = bytesPerSecond || 0; if (value < 1024) return value.toFixed(0) + " B/s"; if (value < 1024 * 1024) return (value / 1024).toFixed(0) + " K/s"; if (value < 1024 * 1024 * 1024) return (value / (1024 * 1024)).toFixed(1) + " M/s"; return (value / (1024 * 1024 * 1024)).toFixed(1) + " G/s"; } function formatGiB(megabytes) { return ((megabytes || 0) / 1024).toFixed(1) + " GiB"; } // Stats only get polled while this widget exists. Component.onCompleted: DgopService.addRef(widget.activeModules) Component.onDestruction: DgopService.removeRef(widget.activeModules) component StatTile: Rectangle { id: tile property string icon: "" property string label: "" property string value: "" property string detail: "" property real fraction: 0 property color accent: Theme.primary radius: Theme.cornerRadius color: Theme.withAlpha(Theme.surfaceLight, Theme.popupTransparency) border.color: Theme.outlineLight border.width: Theme.layerOutlineWidth DankIcon { id: tileIcon anchors.left: parent.left anchors.leftMargin: Theme.spacingM anchors.top: parent.top anchors.topMargin: Theme.spacingM name: tile.icon size: Theme.iconSize - 6 color: tile.accent } StyledText { anchors.left: tileIcon.right anchors.leftMargin: Theme.spacingXS anchors.verticalCenter: tileIcon.verticalCenter text: tile.label font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText } StyledText { id: tileValue anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.verticalCenter: tileIcon.verticalCenter text: tile.value font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText } StyledText { id: tileDetail anchors.left: parent.left anchors.leftMargin: Theme.spacingM anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.bottom: tileTrack.top anchors.bottomMargin: Theme.spacingXS text: tile.detail font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText elide: Text.ElideRight } Rectangle { id: tileTrack anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.leftMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM anchors.bottomMargin: Theme.spacingM height: 5 radius: height / 2 color: Theme.withAlpha(Theme.outline, 0.4) Rectangle { width: parent.width * Math.max(0, Math.min(1, tile.fraction)) height: parent.height radius: parent.radius color: tile.accent Behavior on width { NumberAnimation { duration: Theme.shortDuration easing.type: Easing.OutCubic } } } } } Grid { anchors.fill: parent columns: 2 rowSpacing: Theme.spacingS columnSpacing: Theme.spacingS readonly property real tileWidth: (width - columnSpacing) / 2 readonly property real tileHeight: (height - rowSpacing) / 2 StatTile { width: parent.tileWidth height: parent.tileHeight icon: "memory" label: "CPU" value: Math.round(DgopService.cpuUsage || 0) + "%" detail: DgopService.cpuTemperature > 0 ? DgopService.cpuTemperature.toFixed(0) + "°C" : (DgopService.cpuCores + " cores") fraction: (DgopService.cpuUsage || 0) / 100 accent: DgopService.cpuUsage > 85 ? Theme.error : (DgopService.cpuUsage > 60 ? Theme.warning : Theme.primary) } StatTile { width: parent.tileWidth height: parent.tileHeight icon: "developer_board" label: "Memory" value: Math.round(DgopService.memoryUsage || 0) + "%" detail: widget.formatGiB(DgopService.usedMemoryMB) + " / " + widget.formatGiB(DgopService.totalMemoryMB) fraction: (DgopService.memoryUsage || 0) / 100 accent: DgopService.memoryUsage > 85 ? Theme.error : (DgopService.memoryUsage > 60 ? Theme.warning : Theme.primary) } StatTile { width: parent.tileWidth height: parent.tileHeight icon: "swap_vert" label: "Network" value: "↓ " + widget.formatRate(DgopService.networkRxRate) detail: "↑ " + widget.formatRate(DgopService.networkTxRate) // Scaled against 100 MiB/s so the bar stays readable on fast links. fraction: Math.min(1, ((DgopService.networkRxRate || 0) + (DgopService.networkTxRate || 0)) / (100 * 1024 * 1024)) accent: Theme.primary } StatTile { width: parent.tileWidth height: parent.tileHeight icon: "storage" label: widget.rootMount ? widget.rootMount.mount : "Disk" value: Math.round(widget.diskPercent) + "%" detail: "R " + widget.formatRate(DgopService.diskReadRate) + " W " + widget.formatRate(DgopService.diskWriteRate) fraction: widget.diskPercent / 100 accent: widget.diskPercent > 90 ? Theme.error : (widget.diskPercent > 75 ? Theme.warning : Theme.primary) } } StyledText { anchors.centerIn: parent width: parent.width - Theme.spacingL * 2 text: "dgop is not available - install it to see system stats." font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap visible: !DgopService.dgopAvailable } }