diff --git a/web/maintenance/systeminfo.html b/web/maintenance/systeminfo.html
index 28d03b2474..37a30c215a 100644
--- a/web/maintenance/systeminfo.html
+++ b/web/maintenance/systeminfo.html
@@ -193,7 +193,7 @@
Systeminformationen
let chartsHtml = '';
for (const key in history) {
if (!Object.hasOwn(history, key)) continue;
- if (!chartSections.includes(key)) continue;
+ if (!chartSections.some(section => key.startsWith(section))) continue;
const value = history[key];
// Prüfe, ob es sich um mehrere Reihen handelt (Objekt mit value/unit) oder Array
if (Array.isArray(value)) {
@@ -248,7 +248,7 @@
${key}
// Diagramme zeichnen
for (const key in history) {
if (!Object.hasOwn(history, key)) continue;
- if (!chartSections.includes(key)) continue;
+ if (!chartSections.some(section => key.startsWith(section))) continue;
const value = history[key];
if (Array.isArray(value)) {
const canvas = document.getElementById(`chart_${key}`);
diff --git a/web/maintenance/systeminfo_api.php b/web/maintenance/systeminfo_api.php
index 1df9132bcc..1f1d8d2f6e 100644
--- a/web/maintenance/systeminfo_api.php
+++ b/web/maintenance/systeminfo_api.php
@@ -9,35 +9,231 @@
'mosquitto_local.service'
];
+$numTopProcesses = 10;
+
+function getHardwareInfo()
+{
+ // Board-Name
+ $board = trim(@shell_exec("cat /sys/class/dmi/id/board_name 2>/dev/null"));
+ if ($board === '') {
+ $board = trim(@shell_exec("cat /proc/device-tree/model 2>/dev/null"));
+ }
+ if ($board === '') {
+ $board = 'unbekannt';
+ }
+
+ // CPU-Kerne
+ $cpuCores = (int)@shell_exec('nproc') ?: 1;
+
+ // CPU-Temperatur (verschiedene Pfade möglich)
+ $temp = null;
+ $paths = [
+ '/sys/class/thermal/thermal_zone0/temp',
+ '/sys/devices/virtual/thermal/thermal_zone0/temp'
+ ];
+ foreach ($paths as $path) {
+ if (is_readable($path)) {
+ $raw = trim(file_get_contents($path));
+ if (is_numeric($raw)) {
+ // Wert meist in Milligrad
+ $temp = round(((int)$raw) / 1000, 1);
+ break;
+ }
+ }
+ }
+ if ($temp === null) {
+ // Fallback: vcgencmd (z.B. Raspberry Pi)
+ $vcgencmd = trim(@shell_exec('which vcgencmd'));
+ if ($vcgencmd) {
+ $out = trim(@shell_exec('vcgencmd measure_temp 2>/dev/null'));
+ if (preg_match('/temp=([\d\.]+)/', $out, $m)) {
+ $temp = (float)$m[1];
+ }
+ }
+ }
+ if ($temp === null) {
+ $temp = 'unbekannt';
+ }
+
+ // Uptime
+ $uptime = 'unbekannt';
+ if (is_readable('/proc/uptime')) {
+ $uptimeSeconds = (float)file_get_contents('/proc/uptime');
+ $days = floor($uptimeSeconds / 86400);
+ $hours = floor(($uptimeSeconds % 86400) / 3600);
+ $minutes = floor(($uptimeSeconds % 3600) / 60);
+ $seconds = floor($uptimeSeconds % 60);
+ $uptime = sprintf('%d Tage, %02d:%02d:%02d', $days, $hours, $minutes, $seconds);
+ }
+
+ // Systemzeit
+ $systemTime = date('d.m.Y, H:i:s');
+
+ return [
+ 'board' => [
+ 'value' => $board,
+ 'unit' => ''
+ ],
+ 'cpu_cores' => [
+ 'value' => $cpuCores,
+ 'unit' => ''
+ ],
+ 'cpu_temp' => [
+ 'value' => $temp,
+ 'unit' => is_numeric($temp) ? '°C' : ''
+ ],
+ 'uptime' => [
+ 'value' => $uptime,
+ 'unit' => ''
+ ],
+ 'system_time' => [
+ 'value' => $systemTime,
+ 'unit' => ''
+ ]
+ ];
+}
+
+function getNetworkInfo()
+{
+ $mac = 'unbekannt';
+ $ip = 'unbekannt';
+ $subnet = 'unbekannt';
+ $gateway = 'unbekannt';
+ $iface = null;
+
+ // Ermittle Gateway und zugehörige Schnittstelle
+ $route = @shell_exec("ip route | awk '/default/ {print \$3, \$5; exit}'");
+ if ($route) {
+ list($gateway, $iface) = explode(' ', trim($route));
+ }
+
+ if ($iface) {
+ // MAC-Adresse mit ip-Befehl
+ $macInfo = @shell_exec("ip link show $iface | awk '/link\\// {print \$2; exit}'");
+ if ($macInfo) {
+ $mac = trim($macInfo);
+ }
+ // IP und Subnetz
+ $ipInfo = @shell_exec("ip -o -f inet addr show $iface | awk '{print \$4}'");
+ if ($ipInfo) {
+ $ipCidr = trim($ipInfo);
+ if (strpos($ipCidr, '/') !== false) {
+ list($ip, $cidr) = explode('/', $ipCidr);
+ // Subnetz berechnen
+ $subnet = long2ip(-1 << (32 - (int)$cidr));
+ }
+ }
+ }
+
+ return [
+ 'interface' => [
+ 'value' => $iface ?? 'unbekannt',
+ 'unit' => ''
+ ],
+ 'mac' => [
+ 'value' => $mac,
+ 'unit' => ''
+ ],
+ 'ip' => [
+ 'value' => $ip,
+ 'unit' => ''
+ ],
+ 'subnet' => [
+ 'value' => $subnet,
+ 'unit' => ''
+ ],
+ 'gateway' => [
+ 'value' => $gateway,
+ 'unit' => ''
+ ]
+ ];
+}
+
+function getSoftwareInfo()
+{
+ $version = 'unbekannt';
+ // $branch = 'unbekannt';
+ $commit = 'unbekannt';
+
+ // Git-Verzeichnis bestimmen (eine Ebene höher als "web")
+ $repoDir = dirname(__DIR__, 2);
+
+ // Lese Versionsdatei (eine Ebene höher)
+ $versionFile = dirname(__DIR__, 1) . '/version';
+ if (is_readable($versionFile)) {
+ $version = trim(file_get_contents($versionFile));
+ }
+
+ // // Ermittle aktuellen Git-Branch
+ // $branchCmd = 'git -C ' . escapeshellarg($repoDir) . ' rev-parse --abbrev-ref HEAD 2>/dev/null';
+ // $branchOut = trim(@shell_exec($branchCmd));
+ // if ($branchOut !== '') {
+ // $branch = $branchOut;
+ // }
+
+ // Ermittle aktuellen Git-Commit
+ $commitFile = dirname(__DIR__, 1) . '/lastcommit';
+ if (is_readable($commitFile)) {
+ $commit = trim(file_get_contents($commitFile));
+ }
+
+ return [
+ 'version' => [
+ 'value' => $version,
+ 'unit' => ''
+ ],
+ // 'git_branch' => [
+ // 'value' => $branch,
+ // 'unit' => ''
+ // ],
+ 'git_commit' => [
+ 'value' => $commit,
+ 'unit' => ''
+ ]
+ ];
+}
+
function getCpuLoad()
{
$load = sys_getloadavg();
- $cpuCores = (int)@shell_exec('nproc') ?: 1; // Fallback auf 1 Kern, falls nproc nicht verfügbar
+ // Rückgabe der 1, 5 und 15 Minuten Last
+
+ // Lese die CPU-Zeile aus /proc/stat
+ $stat1 = explode(" ", preg_replace('!\s+!', ' ', trim(shell_exec("head -n1 /proc/stat"))));
+ usleep(100000); // 100ms warten
+ $stat2 = explode(" ", preg_replace('!\s+!', ' ', trim(shell_exec("head -n1 /proc/stat"))));
+
+ // user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice
+ $fields = [1, 2, 3, 4, 5, 6, 7, 8];
+ $cpu1 = $cpu2 = 0;
+ foreach ($fields as $i) {
+ $cpu1 += isset($stat1[$i]) ? (int)$stat1[$i] : 0;
+ $cpu2 += isset($stat2[$i]) ? (int)$stat2[$i] : 0;
+ }
+ $idle1 = isset($stat1[4]) ? (int)$stat1[4] : 0;
+ $idle2 = isset($stat2[4]) ? (int)$stat2[4] : 0;
+
+ $totalDiff = $cpu2 - $cpu1;
+ $idleDiff = $idle2 - $idle1;
+
+ $cpuPercent = $totalDiff > 0 ? round((1 - ($idleDiff / $totalDiff)) * 100, 1) : 0;
return [
- // '1min' => [
- // 'value' => $load[0],
- // 'unit' => ''
- // ],
- '1min_percent' => [
- 'value' => round(($load[0] / $cpuCores) * 100, 1),
+ 'used_percent' => [
+ 'value' => $cpuPercent,
'unit' => '%'
],
- // '5min' => [
- // 'value' => $load[1],
- // 'unit' => ''
- // ],
- '5min_percent' => [
- 'value' => round(($load[1] / $cpuCores) * 100, 1),
- 'unit' => '%'
+ 'load_1min' => [
+ 'value' => $load[0],
+ 'unit' => ''
],
- // '15min' => [
- // 'value' => $load[2],
- // 'unit' => ''
- // ],
- '15min_percent' => [
- 'value' => round(($load[2] / $cpuCores) * 100, 1),
- 'unit' => '%'
+ 'load_5min' => [
+ 'value' => $load[1],
+ 'unit' => ''
+ ],
+ 'load_15min' => [
+ 'value' => $load[2],
+ 'unit' => ''
]
];
}
@@ -77,30 +273,30 @@ function getMemoryUsage()
return false;
}
-function getStorageUsage()
+function getPartitionUsage($path, $label)
{
- $diskTotal = @disk_total_space("/");
- $diskFree = @disk_free_space("/");
+ $diskTotal = @disk_total_space($path);
+ $diskFree = @disk_free_space($path);
if ($diskTotal === false || $diskFree === false) {
return false;
}
$diskUsed = $diskTotal - $diskFree;
return [
- 'used_percent' => [
+ $label . '_used_percent' => [
'value' => round($diskUsed / $diskTotal * 100, 1),
'unit' => '%'
],
- 'total' => [
- 'value' => round($diskTotal / 1024 / 1024 / 1024, 2),
- 'unit' => 'GB'
+ $label . '_total' => [
+ 'value' => round($diskTotal / 1024 / 1024, 2),
+ 'unit' => 'MB'
],
- 'used' => [
- 'value' => round($diskUsed / 1024 / 1024 / 1024, 2),
- 'unit' => 'GB'
+ $label . '_used' => [
+ 'value' => round($diskUsed / 1024 / 1024, 2),
+ 'unit' => 'MB'
],
- 'free' => [
- 'value' => round($diskFree / 1024 / 1024 / 1024, 2),
- 'unit' => 'GB'
+ $label . '_free' => [
+ 'value' => round($diskFree / 1024 / 1024, 2),
+ 'unit' => 'MB'
]
];
}
@@ -116,6 +312,28 @@ function getServiceStatus($services)
return $result;
}
+function getTopCpuProcesses($limit)
+{
+ // ps gibt: PID, Benutzer, CPU-Auslastung, Speicher, Befehl
+ $cmd = "ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | head -n " . ($limit + 1);
+ $output = [];
+ exec($cmd, $output);
+
+ $result = [];
+ // Erste Zeile ist die Überschrift
+ for ($i = 1; $i < count($output); $i++) {
+ // Spalten trennen (mehrere Leerzeichen)
+ $cols = preg_split('/\s+/', trim($output[$i]), 5);
+ if (count($cols) === 5) {
+ $result[$cols[4] . ' (' . $cols[0] . ')'] = [
+ 'value' => (float)$cols[2],
+ 'unit' => '%'
+ ];
+ }
+ }
+ return $result;
+}
+
// API-Handler
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
@@ -158,10 +376,16 @@ function getServiceStatus($services)
if (php_sapi_name() !== 'cli' && basename($_SERVER['SCRIPT_FILENAME']) === 'systeminfo_api.php') {
header('Content-Type: application/json');
echo json_encode([
+ 'hardwareInfo' => getHardwareInfo(),
+ 'networkInfo' => getNetworkInfo(),
+ 'softwareInfo' => getSoftwareInfo(),
'cpuLoad' => getCpuLoad(),
'memory' => getMemoryUsage(),
- 'storage' => getStorageUsage(),
- 'services' => getServiceStatus($serviceList)
+ 'top10CpuProcesses' => getTopCpuProcesses($numTopProcesses),
+ 'services' => getServiceStatus($serviceList),
+ 'storage (root)' => getPartitionUsage('/', 'root'),
+ 'storage (boot)' => getPartitionUsage('/boot', 'boot'),
+ 'storage (ramdisk)' => getPartitionUsage('/var/www/html/openWB/ramdisk', 'ramdisk'),
]);
exit;
}