From 3df92697e0b6a259dd0ff17ca0711ccf1b335358 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 15 Jul 2025 16:08:57 -0400 Subject: [PATCH] processmonitor: add system info tab --- Services/SystemMonitorService.qml | 322 +++++++++++++++++++ Widgets/ProcessListWidget.qml | 494 ++++++++++++++++++++++++++++-- 2 files changed, 793 insertions(+), 23 deletions(-) diff --git a/Services/SystemMonitorService.qml b/Services/SystemMonitorService.qml index af7d194a..22d80080 100644 --- a/Services/SystemMonitorService.qml +++ b/Services/SystemMonitorService.qml @@ -24,9 +24,25 @@ Singleton { property real cpuTemperature: 0.0 + property string kernelVersion: "" + property string distribution: "" + property string hostname: "" + property string uptime: "" + property string scheduler: "" + property string architecture: "" + property string loadAverage: "" + property int processCount: 0 + property int threadCount: 0 + property string bootTime: "" + property string motherboard: "" + property string biosVersion: "" + property var diskMounts: [] + property string diskUsage: "" + property int cpuUpdateInterval: 3000 property int memoryUpdateInterval: 5000 property int temperatureUpdateInterval: 10000 + property int systemInfoUpdateInterval: 30000 property bool enabledForTopBar: true property bool enabledForDetailedView: false @@ -35,6 +51,7 @@ Singleton { console.log("SystemMonitorService: Starting initialization...") getCpuInfo() updateSystemStats() + updateSystemInfo() console.log("SystemMonitorService: Initialization complete") } @@ -162,6 +179,282 @@ Singleton { } } + Process { + id: kernelInfoProcess + command: ["bash", "-c", "uname -r"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.kernelVersion = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Kernel info check failed with exit code:", exitCode) + } + } + } + + Process { + id: distributionProcess + command: ["bash", "-c", "grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'=' -f2 | tr -d '\"' || echo 'Unknown'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.distribution = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Distribution check failed with exit code:", exitCode) + } + } + } + + Process { + id: hostnameProcess + command: ["bash", "-c", "hostname"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.hostname = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Hostname check failed with exit code:", exitCode) + } + } + } + + Process { + id: uptimeProcess + command: ["bash", "-c", "uptime -p | sed 's/up //'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.uptime = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Uptime check failed with exit code:", exitCode) + } + } + } + + Process { + id: schedulerProcess + command: ["bash", "-c", "cat /sys/block/sda/queue/scheduler 2>/dev/null | grep -o '\\[.*\\]' | tr -d '[]' || echo 'Unknown'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.scheduler = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Scheduler check failed with exit code:", exitCode) + } + } + } + + Process { + id: architectureProcess + command: ["bash", "-c", "uname -m"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.architecture = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Architecture check failed with exit code:", exitCode) + } + } + } + + Process { + id: loadAverageProcess + command: ["bash", "-c", "cat /proc/loadavg | cut -d' ' -f1,2,3"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.loadAverage = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Load average check failed with exit code:", exitCode) + } + } + } + + Process { + id: processCountProcess + command: ["bash", "-c", "ps aux | wc -l"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.processCount = parseInt(text.trim()) - 1 + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Process count check failed with exit code:", exitCode) + } + } + } + + Process { + id: threadCountProcess + command: ["bash", "-c", "cat /proc/stat | grep processes | awk '{print $2}'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.threadCount = parseInt(text.trim()) + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Thread count check failed with exit code:", exitCode) + } + } + } + + Process { + id: bootTimeProcess + command: ["bash", "-c", "who -b | awk '{print $3, $4}' || stat -c %w /proc/1 2>/dev/null | cut -d' ' -f1,2 || echo 'Unknown'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.bootTime = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Boot time check failed with exit code:", exitCode) + } + } + } + + Process { + id: motherboardProcess + command: ["bash", "-c", "if [ -r /sys/devices/virtual/dmi/id/board_vendor ] && [ -r /sys/devices/virtual/dmi/id/board_name ]; then echo \"$(cat /sys/devices/virtual/dmi/id/board_vendor 2>/dev/null) $(cat /sys/devices/virtual/dmi/id/board_name 2>/dev/null)\"; else echo 'Unknown'; fi"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.motherboard = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Motherboard check failed with exit code:", exitCode) + } + } + } + + Process { + id: biosProcess + command: ["bash", "-c", "if [ -r /sys/devices/virtual/dmi/id/bios_version ] && [ -r /sys/devices/virtual/dmi/id/bios_date ]; then echo \"$(cat /sys/devices/virtual/dmi/id/bios_version 2>/dev/null) $(cat /sys/devices/virtual/dmi/id/bios_date 2>/dev/null)\"; else echo 'Unknown'; fi"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.biosVersion = text.trim() + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("BIOS check failed with exit code:", exitCode) + } + } + } + + Process { + id: diskMountsProcess + command: ["bash", "-c", "df -h --output=source,target,fstype,size,used,avail,pcent | tail -n +2 | grep -v tmpfs | grep -v devtmpfs | head -10"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + let mounts = [] + const lines = text.trim().split('\n') + for (const line of lines) { + const parts = line.split(/\s+/) + if (parts.length >= 7) { + mounts.push({ + device: parts[0], + mount: parts[1], + fstype: parts[2], + size: parts[3], + used: parts[4], + avail: parts[5], + percent: parts[6] + }) + } + } + root.diskMounts = mounts + } + } + } + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Disk mounts check failed with exit code:", exitCode) + } + } + } + Timer { id: cpuTimer interval: root.cpuUpdateInterval @@ -202,6 +495,19 @@ Singleton { } } + Timer { + id: systemInfoTimer + interval: root.systemInfoUpdateInterval + running: root.enabledForDetailedView + repeat: true + + onTriggered: { + if (root.enabledForDetailedView) { + updateSystemInfo() + } + } + } + function getCpuInfo() { cpuInfoProcess.running = true } @@ -217,6 +523,22 @@ Singleton { } } + function updateSystemInfo() { + kernelInfoProcess.running = true + distributionProcess.running = true + hostnameProcess.running = true + uptimeProcess.running = true + schedulerProcess.running = true + architectureProcess.running = true + loadAverageProcess.running = true + processCountProcess.running = true + threadCountProcess.running = true + bootTimeProcess.running = true + motherboardProcess.running = true + biosProcess.running = true + diskMountsProcess.running = true + } + function enableTopBarMonitoring(enabled) { root.enabledForTopBar = enabled } diff --git a/Widgets/ProcessListWidget.qml b/Widgets/ProcessListWidget.qml index a6ced9f8..c5f5d9ec 100644 --- a/Widgets/ProcessListWidget.qml +++ b/Widgets/ProcessListWidget.qml @@ -1115,36 +1115,481 @@ PanelWindow { Component { id: systemTabComponent - Rectangle { - color: "transparent" + + ScrollView { + anchors.fill: parent + clip: true + ScrollBar.vertical.policy: ScrollBar.AsNeeded + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff Column { - anchors.centerIn: parent - spacing: Theme.spacingL + width: parent.width + spacing: Theme.spacingM - Text { - text: "settings" - font.family: Theme.iconFont - font.pixelSize: 48 - color: Theme.primary - opacity: 0.6 - anchors.horizontalCenter: parent.horizontalCenter + Row { + width: parent.width + height: 140 + spacing: Theme.spacingM + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: 140 + radius: Theme.cornerRadiusLarge + color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) + border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) + border.width: 1 + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + anchors.bottomMargin: Theme.spacingM + 4 + spacing: Theme.spacingXS + + Row { + width: parent.width + spacing: Theme.spacingS + + Text { + text: "computer" + font.family: Theme.iconFont + font.pixelSize: Theme.iconSize + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: "System" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Text { + text: "Host: " + SystemMonitorService.hostname + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "OS: " + SystemMonitorService.distribution + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "Arch: " + SystemMonitorService.architecture + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "Kernel: " + SystemMonitorService.kernelVersion + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + } + } + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: 140 + radius: Theme.cornerRadiusLarge + color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08) + border.color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.16) + border.width: 1 + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + anchors.bottomMargin: Theme.spacingM + 4 + spacing: Theme.spacingXS + + Row { + width: parent.width + spacing: Theme.spacingS + + Text { + text: "developer_board" + font.family: Theme.iconFont + font.pixelSize: Theme.iconSize + color: Theme.secondary + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: "Hardware" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Text { + text: "CPU: " + SystemMonitorService.cpuModel + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "Motherboard: " + SystemMonitorService.motherboard + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "BIOS: " + SystemMonitorService.biosVersion + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "Memory: " + SystemMonitorService.formatMemory(SystemMonitorService.totalMemory) + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + } + } } - Text { - text: "System Information" - font.pixelSize: Theme.fontSizeLarge + 2 - font.weight: Font.Bold - color: Theme.surfaceText - anchors.horizontalCenter: parent.horizontalCenter + Row { + width: parent.width + height: 120 + spacing: Theme.spacingM + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: 120 + radius: Theme.cornerRadiusLarge + color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.08) + border.color: Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.16) + border.width: 1 + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + anchors.bottomMargin: Theme.spacingM + 4 + spacing: Theme.spacingXS + + Row { + width: parent.width + spacing: Theme.spacingS + + Text { + text: "timer" + font.family: Theme.iconFont + font.pixelSize: Theme.iconSize + color: Theme.warning + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: "Uptime" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Text { + text: SystemMonitorService.uptime + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "Boot: " + SystemMonitorService.bootTime + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: "Load: " + SystemMonitorService.loadAverage + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + } + } + + Rectangle { + width: (parent.width - Theme.spacingM) / 2 + height: 120 + radius: Theme.cornerRadiusLarge + color: Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.08) + border.color: Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.16) + border.width: 1 + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + anchors.bottomMargin: Theme.spacingM + 4 + spacing: Theme.spacingXS + + Row { + width: parent.width + spacing: Theme.spacingS + + Text { + text: "list_alt" + font.family: Theme.iconFont + font.pixelSize: Theme.iconSize + color: Theme.info + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: "Processes" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Text { + text: SystemMonitorService.processCount + " Running" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + Text { + text: SystemMonitorService.threadCount + " Total Created" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + } + } } - Text { - text: "Kernel information, schedulers,\nand system details will be shown here" - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceVariantText - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter + Rectangle { + width: parent.width + height: Math.max(200, diskMountRepeater.count * 28 + 60) + radius: Theme.cornerRadiusLarge + color: Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.08) + border.color: Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.16) + border.width: 1 + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + spacing: Theme.spacingS + + Row { + width: parent.width + spacing: Theme.spacingS + + Text { + text: "storage" + font.family: Theme.iconFont + font.pixelSize: Theme.iconSize + color: Theme.success + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: "Storage & Disks" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Text { + text: "I/O Scheduler: " + SystemMonitorService.scheduler + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + } + + ScrollView { + width: parent.width + height: parent.height - 60 + clip: true + ScrollBar.vertical.policy: ScrollBar.AsNeeded + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + Column { + width: parent.width + spacing: 2 + + Row { + width: parent.width + height: 24 + spacing: Theme.spacingS + + Text { + text: "Device" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width * 0.25 + elide: Text.ElideRight + } + + Text { + text: "Mount" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width * 0.2 + elide: Text.ElideRight + } + + Text { + text: "Size" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width * 0.15 + elide: Text.ElideRight + } + + Text { + text: "Used" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width * 0.15 + elide: Text.ElideRight + } + + Text { + text: "Available" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width * 0.15 + elide: Text.ElideRight + } + + Text { + text: "Use%" + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Bold + color: Theme.surfaceText + width: parent.width * 0.1 + elide: Text.ElideRight + } + } + + Repeater { + id: diskMountRepeater + model: SystemMonitorService.diskMounts + + Rectangle { + width: parent.width + height: 24 + radius: Theme.cornerRadiusSmall + color: diskMouseArea.containsMouse ? + Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04) : + "transparent" + + MouseArea { + id: diskMouseArea + anchors.fill: parent + hoverEnabled: true + } + + Row { + anchors.fill: parent + spacing: Theme.spacingS + + Text { + text: modelData.device + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width * 0.25 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: modelData.mount + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width * 0.2 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: modelData.size + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width * 0.15 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: modelData.used + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width * 0.15 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: modelData.avail + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + width: parent.width * 0.15 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: modelData.percent + font.pixelSize: Theme.fontSizeSmall + color: { + const percent = parseInt(modelData.percent) + if (percent > 90) return Theme.error + if (percent > 75) return Theme.warning + return Theme.surfaceText + } + width: parent.width * 0.1 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + } + } + } } } } @@ -1484,10 +1929,13 @@ PanelWindow { processListWidget.isVisible = true ProcessMonitorService.updateSystemInfo() ProcessMonitorService.updateProcessList() + SystemMonitorService.enableDetailedMonitoring(true) + SystemMonitorService.updateSystemInfo() } function hide() { processListWidget.isVisible = false + SystemMonitorService.enableDetailedMonitoring(false) } function toggle() {