mirror of
https://github.com/michalcz10/USB-RAID-Array.git
synced 2026-11-11 07:59:04 +00:00
Redesign SFTP workspace and fix server monitoring
Replace Bootstrap dependencies with custom blue-accented light and dark themes and native dialogs. Share the sidebar, top bar and responsive layout across file browsing, account settings, user management, server statistics and file previews. Add folder search, type filters, upload progress and asset cache versioning. Fix SSH configuration usage, filesystem discovery, missing statistics and RAID health parsing. Adapt PDF sizing and fullscreen controls to the shared workspace. Validation: PHP workspace and server statistics regression tests, JavaScript syntax checks, responsive browser checks and live XAMPP navigation.
This commit is contained in:
+103
-377
@@ -12,47 +12,20 @@ if (empty($_SESSION['admin']) || $_SESSION['admin'] !== true) {
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
use phpseclib3\Net\SSH2;
|
||||
|
||||
// $ssh_host = 'localhost';
|
||||
// $ssh_username = 'uname';
|
||||
// $ssh_password = 'pswd';
|
||||
require_once __DIR__ . '/../../config/config.php';
|
||||
require_once __DIR__ . '/../../includes/workspace.php';
|
||||
|
||||
// The configuration helper authenticates using SSH_HOST, SSH_USER and SSH_PASS.
|
||||
$ssh = getSSHConnection();
|
||||
if (!$ssh->login($ssh_username, $ssh_password)) {
|
||||
die('SSH login failed');
|
||||
}
|
||||
|
||||
function getDiskData($ssh, $device) {
|
||||
return $ssh->exec("df -h $device");
|
||||
}
|
||||
require_once __DIR__ . '/lib/serverstats.php';
|
||||
|
||||
function getSystemData($ssh) {
|
||||
// Get memory info using awk to clean up the output
|
||||
$meminfo = $ssh->exec("free -m | awk 'NR==2{printf \"%s,%s,%s,\", $2,$3,$7} NR==3{printf \"%s,%s,%s\", $2,$3,$4}'");
|
||||
|
||||
// Use vmstat to get CPU idle percentage, more reliable and commonly available
|
||||
$cpuIdle = $ssh->exec("vmstat 1 2 | tail -1 | awk '{print $15}'");
|
||||
|
||||
return [
|
||||
'storage' => [
|
||||
'system' => parseDiskData($ssh->exec('df -h /dev/ubuntu-vg/ubuntu-lv')),
|
||||
'data' => parseDiskData($ssh->exec('df -h /dev/sdb1'))
|
||||
],
|
||||
'raid' => parseRaidStatus($ssh->exec('cat /proc/mdstat')),
|
||||
'resources' => parseSystemStats($cpuIdle, $meminfo),
|
||||
'lastUpdate' => date('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
$systemData = getSystemData($ssh);
|
||||
$systemData = getSystemData($ssh, $defaultPath);
|
||||
|
||||
$monitors = [
|
||||
[
|
||||
'id' => 'SystemDisk',
|
||||
'title' => 'System Disk (sda)',
|
||||
'title' => 'System filesystem',
|
||||
'icon' => 'hdd',
|
||||
'color' => 'primary',
|
||||
'type' => 'storage',
|
||||
@@ -60,7 +33,7 @@ $monitors = [
|
||||
],
|
||||
[
|
||||
'id' => 'DataDisk',
|
||||
'title' => 'Data Disk (sdb)',
|
||||
'title' => 'Storage filesystem',
|
||||
'icon' => 'hdd',
|
||||
'color' => 'primary',
|
||||
'type' => 'storage',
|
||||
@@ -91,349 +64,101 @@ $monitors = [
|
||||
]
|
||||
];
|
||||
|
||||
function parseDiskData($output) {
|
||||
$lines = explode("\n", trim($output));
|
||||
if (count($lines) < 2) return null;
|
||||
|
||||
$values = preg_split('/\s+/', trim($lines[1]));
|
||||
return [
|
||||
'size' => $values[1] ?? 'N/A',
|
||||
'used' => $values[2] ?? 'N/A',
|
||||
'available' => $values[3] ?? 'N/A',
|
||||
'usage' => $values[4] ?? '0%'
|
||||
];
|
||||
}
|
||||
|
||||
function parseRaidStatus($output) {
|
||||
if (empty($output)) {
|
||||
return [
|
||||
'active' => false,
|
||||
'status' => 'unknown',
|
||||
'type' => 'N/A'
|
||||
];
|
||||
}
|
||||
|
||||
$status = [
|
||||
'active' => false,
|
||||
'status' => 'unknown',
|
||||
'type' => 'N/A'
|
||||
];
|
||||
|
||||
$lines = explode("\n", trim($output));
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/active\s+(\w+)/', $line, $matches)) {
|
||||
$status['active'] = true;
|
||||
$status['type'] = $matches[1] ?? 'N/A';
|
||||
}
|
||||
if (strpos($line, '[UU]') !== false) {
|
||||
$status['status'] = 'healthy';
|
||||
} elseif (strpos($line, '_') !== false) {
|
||||
$status['status'] = 'degraded';
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
function parseSystemStats($cpuOutput, $memOutput) {
|
||||
// Parse CPU stats - convert idle percentage to usage percentage
|
||||
$idlePercent = floatval(trim(str_replace(',', '.', $cpuOutput)));
|
||||
$cpuUsage = 100 - $idlePercent; // Convert idle to usage percentage
|
||||
if (is_nan($cpuUsage) || $cpuUsage < 0 || $cpuUsage > 100) {
|
||||
$cpuUsage = 0; // Default to 0% if parsing fails
|
||||
}
|
||||
$cpu = ['usage' => number_format($cpuUsage, 1)];
|
||||
|
||||
// Parse memory stats
|
||||
$parts = explode(',', $memOutput);
|
||||
if (count($parts) >= 6) {
|
||||
$memTotal = intval($parts[0]);
|
||||
$memUsed = intval($parts[1]);
|
||||
$memAvail = intval($parts[2]);
|
||||
$swapTotal = intval($parts[3]);
|
||||
$swapUsed = intval($parts[4]);
|
||||
$swapFree = intval($parts[5]);
|
||||
|
||||
$memory = [
|
||||
'total' => number_format($memTotal / 1024, 2) . 'G',
|
||||
'used' => number_format($memUsed / 1024, 2) . 'G',
|
||||
'available' => number_format($memAvail / 1024, 2) . 'G',
|
||||
'usage' => ($memTotal > 0 ? round(($memUsed / $memTotal) * 100, 1) : 0) . '%',
|
||||
'swap_total' => number_format($swapTotal / 1024, 2) . 'G',
|
||||
'swap_used' => number_format($swapUsed / 1024, 2) . 'G',
|
||||
'swap_free' => number_format($swapFree / 1024, 2) . 'G',
|
||||
'swap_usage' => ($swapTotal > 0 ? round(($swapUsed / $swapTotal) * 100, 1) : 0) . '%'
|
||||
];
|
||||
} else {
|
||||
$memory = [
|
||||
'total' => '0G',
|
||||
'used' => '0G',
|
||||
'available' => '0G',
|
||||
'usage' => '0%',
|
||||
'swap_total' => '0G',
|
||||
'swap_used' => '0G',
|
||||
'swap_free' => '0G',
|
||||
'swap_usage' => '0%'
|
||||
];
|
||||
}
|
||||
|
||||
return ['cpu' => $cpu, 'memory' => $memory];
|
||||
}
|
||||
|
||||
function convertToBytes($size) {
|
||||
if (preg_match('/^([\d.]+)([KMGT]?)i?B?$/', trim($size), $matches)) {
|
||||
$value = floatval($matches[1]);
|
||||
$unit = strtoupper($matches[2]);
|
||||
|
||||
switch ($unit) {
|
||||
case 'P': $value *= 1024;
|
||||
case 'T': $value *= 1024;
|
||||
case 'G': $value *= 1024;
|
||||
case 'M': $value *= 1024;
|
||||
case 'K': $value *= 1024;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function formatBytes($bytes, $forceUnit = '') {
|
||||
$units = ['B', 'K', 'M', 'G', 'T', 'P'];
|
||||
$bytes = max($bytes, 0);
|
||||
|
||||
if ($forceUnit) {
|
||||
$unitIndex = array_search($forceUnit, $units);
|
||||
if ($unitIndex !== false) {
|
||||
$bytes /= pow(1024, $unitIndex);
|
||||
return round($bytes, 2) . $forceUnit;
|
||||
}
|
||||
}
|
||||
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, 2) . $units[$pow];
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="<?= isset($_COOKIE['theme']) ? $_COOKIE['theme'] : 'light' ?>">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Server Status</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="icon" href="../../img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="../../css/bootstrap.css">
|
||||
<link rel="stylesheet" href="../../css/modern.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="css/serverstat.css">
|
||||
<script src="../../js/bootstrap.bundle.js"></script>
|
||||
<link rel="stylesheet" href="<?= workspaceAsset('../../', 'css/modern.css') ?>">
|
||||
<link rel="stylesheet" href="<?= workspaceAsset('../../', 'css/workspace.css') ?>">
|
||||
<link rel="stylesheet" href="<?= workspaceAsset('../../', 'content/ftp/css/serverstat.css') ?>">
|
||||
<script src="<?= workspaceAsset('../../', 'js/theme.js') ?>"></script>
|
||||
<script src="<?= workspaceAsset('../../', 'js/ui.js') ?>" defer></script>
|
||||
</head>
|
||||
|
||||
<body class="container-fluid text-center">
|
||||
<div class="d-flex justify-content-end p-3">
|
||||
<button id="themeToggle" class="btn btn-sm theme-toggle">
|
||||
<i class="bi"></i>
|
||||
<span id="themeText"></span>
|
||||
</button>
|
||||
</div>
|
||||
<?php renderWorkspaceStart([
|
||||
'root' => '../../',
|
||||
'active' => 'server',
|
||||
'section' => 'Server status',
|
||||
'eyebrow' => 'Server overview',
|
||||
'title' => 'Server status',
|
||||
'description' => 'Keep an eye on your storage, RAID health and system resources.',
|
||||
'connected' => true,
|
||||
'class' => 'server-page',
|
||||
]); ?>
|
||||
|
||||
<div class="custom-container dark-box">
|
||||
<header class="row border-bottom m-5">
|
||||
<h1>Server Status</h1>
|
||||
<div class="mb-3 p-3">
|
||||
<a href="../logout.php" class="btn btn-danger">Logout</a>
|
||||
<a href="../changepassword.php" class="btn btn-warning">Change Password</a>
|
||||
<a href="../adminpanel.php" class="btn btn-primary">Admin Panel</a>
|
||||
<a href="index.php" class="btn btn-primary">SFTP</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card shadow-sm bg-body-tertiary">
|
||||
<div class="card-header monitor-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h1 class="h4 mb-0">
|
||||
<i class="bi bi-server text-primary me-2"></i>
|
||||
Server Disk Status
|
||||
</h1>
|
||||
<span class="badge bg-light text-dark">
|
||||
<i class="bi bi-clock me-1"></i>
|
||||
<?= htmlspecialchars($systemData['lastUpdate']) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row g-4">
|
||||
<?php foreach ($monitors as $monitor): ?>
|
||||
<div class="col-md-6">
|
||||
<div class="monitor-card card h-100 border-<?= $monitor['color'] ?>">
|
||||
<div class="card-header bg-<?= $monitor['color'] ?> bg-opacity-10">
|
||||
<h3 class="h5 mb-0">
|
||||
<i class="bi bi-<?= $monitor['icon'] ?> text-<?= $monitor['color'] ?> me-2"></i>
|
||||
<?= $monitor['title'] ?>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if ($monitor['type'] === 'storage'):
|
||||
$diskData = $systemData['storage'][$monitor['source']];
|
||||
$usagePercent = intval(rtrim($diskData['usage'], '%'));
|
||||
?>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span>Storage Usage</span>
|
||||
<span><?= $diskData['usage'] ?></span>
|
||||
</div>
|
||||
<div class="progress" style="height: 10px">
|
||||
<div class="progress-bar bg-<?= $usagePercent > 90 ? 'danger' : ($usagePercent > 75 ? 'warning' : 'success') ?>"
|
||||
role="progressbar"
|
||||
style="width: <?= $usagePercent ?>%"
|
||||
aria-valuenow="<?= $usagePercent ?>"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center g-2">
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Total</div>
|
||||
<div class="fw-bold"><?= $diskData['size'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Used</div>
|
||||
<div class="fw-bold"><?= $diskData['used'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Free</div>
|
||||
<div class="fw-bold"><?= $diskData['available'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($monitor['type'] === 'raid'): ?>
|
||||
<div class="text-center">
|
||||
<div class="display-4 mb-2">
|
||||
<i class="bi bi-<?= $systemData['raid']['active'] ? 'check-circle-fill text-success' : 'x-circle-fill text-danger' ?>"></i>
|
||||
</div>
|
||||
<h4 class="h6"><?= $systemData['raid']['type'] ?> Array</h4>
|
||||
<span class="badge bg-<?= $systemData['raid']['status'] === 'healthy' ? 'success' : 'warning' ?>">
|
||||
<?= ucfirst($systemData['raid']['status']) ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php elseif ($monitor['type'] === 'resources' && $monitor['source'] === 'cpu'): ?>
|
||||
<div class="cpu-gauge">
|
||||
<canvas id="cpuGauge" width="150" height="150" data-value="<?= $systemData['resources']['cpu']['usage'] ?>"></canvas>
|
||||
</div>
|
||||
<?php elseif ($monitor['type'] === 'resources' && $monitor['source'] === 'memory'):
|
||||
$memData = $systemData['resources']['memory'];
|
||||
$usagePercent = floatval(rtrim($memData['usage'], '%'));
|
||||
$swapPercent = floatval(rtrim($memData['swap_usage'], '%'));
|
||||
?>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span>Memory Usage</span>
|
||||
<span><?= $memData['usage'] ?></span>
|
||||
</div>
|
||||
<div class="progress" style="height: 10px">
|
||||
<div class="progress-bar bg-<?= $usagePercent > 90 ? 'danger' : ($usagePercent > 75 ? 'warning' : 'success') ?>"
|
||||
role="progressbar"
|
||||
style="width: <?= $usagePercent ?>%"
|
||||
aria-valuenow="<?= $usagePercent ?>"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center g-2 mb-3">
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Total</div>
|
||||
<div class="fw-bold"><?= $memData['total'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Used</div>
|
||||
<div class="fw-bold"><?= $memData['used'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Available</div>
|
||||
<div class="fw-bold"><?= $memData['available'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span>Swap Usage</span>
|
||||
<span><?= $memData['swap_usage'] ?></span>
|
||||
</div>
|
||||
<div class="progress" style="height: 10px">
|
||||
<div class="progress-bar bg-<?= $swapPercent > 90 ? 'danger' : ($swapPercent > 75 ? 'warning' : 'success') ?>"
|
||||
role="progressbar"
|
||||
style="width: <?= $swapPercent ?>%"
|
||||
aria-valuenow="<?= $swapPercent ?>"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center g-2">
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Total</div>
|
||||
<div class="fw-bold"><?= $memData['swap_total'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Used</div>
|
||||
<div class="fw-bold"><?= $memData['swap_used'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded">
|
||||
<div class="small text-muted">Free</div>
|
||||
<div class="fw-bold"><?= $memData['swap_free'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-toolbar"><p>Live statistics · refreshes every 5 seconds</p><span class="badge bg-light text-dark"><i class="ui-icon icon-clock" aria-hidden="true"></i> <?= htmlspecialchars($systemData['lastUpdate']) ?></span></div>
|
||||
<div class="stats-grid">
|
||||
<?php foreach ($monitors as $monitor): ?>
|
||||
<section class="stats-cell" aria-label="<?= htmlspecialchars($monitor['title']) ?>">
|
||||
<div class="monitor-card card h-100">
|
||||
<div class="card-header"><h3 class="mb-0"><i class="ui-icon icon-<?= $monitor['icon'] ?> text-primary me-2" aria-hidden="true"></i><?= htmlspecialchars($monitor['title']) ?></h3></div>
|
||||
<div class="card-body">
|
||||
<?php if ($monitor['type'] === 'storage'):
|
||||
$diskData = $systemData['storage'][$monitor['source']];
|
||||
?>
|
||||
<?php if (!$diskData['available_data']): ?>
|
||||
<p class="text-muted" role="status">Storage statistics are unavailable for this path.</p>
|
||||
<?php else:
|
||||
$usagePercent = min(100, (int) rtrim($diskData['usage'], '%'));
|
||||
?>
|
||||
<p class="small text-muted"><?= htmlspecialchars($diskData['filesystem']) ?> · <?= htmlspecialchars($monitor['source'] === 'system' ? '/' : $defaultPath) ?></p>
|
||||
<div class="d-flex justify-content-between"><span>Storage usage</span><strong><?= htmlspecialchars($diskData['usage']) ?></strong></div>
|
||||
<div class="progress mb-4"><div class="progress-bar bg-<?= $usagePercent > 90 ? 'danger' : ($usagePercent > 75 ? 'warning' : 'success') ?>" role="progressbar" style="width:<?= $usagePercent ?>%" aria-label="Storage usage" aria-valuenow="<?= $usagePercent ?>" aria-valuemin="0" aria-valuemax="100"></div></div>
|
||||
<div class="row text-center">
|
||||
<?php foreach (['size' => 'Total', 'used' => 'Used', 'available' => 'Free'] as $key => $label): ?>
|
||||
<div class="col-4"><div class="p-2 border rounded"><div class="small text-muted"><?= $label ?></div><div class="fw-bold"><?= htmlspecialchars($diskData[$key]) ?></div></div></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($monitor['type'] === 'raid'): ?>
|
||||
<div class="raid-summary">
|
||||
<i class="ui-icon icon-<?= $systemData['raid']['status'] === 'healthy' ? 'check-circle-fill text-success' : 'x-circle-fill text-warning' ?>" aria-hidden="true"></i>
|
||||
<h4><?= $systemData['raid']['arrays'] ? htmlspecialchars($systemData['raid']['type']) . ' Array' : 'Linux MD RAID' ?></h4>
|
||||
<span class="badge"><?= htmlspecialchars(ucfirst($systemData['raid']['status'])) ?></span>
|
||||
<?php foreach ($systemData['raid']['arrays'] as $array): ?>
|
||||
<p class="small text-muted mt-2"><?= htmlspecialchars($array['name'] . ' · ' . $array['type'] . ' · ' . $array['status']) ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($monitor['source'] === 'cpu'): ?>
|
||||
<?php if (!$systemData['resources']['cpu']['available_data']): ?>
|
||||
<p class="text-muted" role="status">CPU statistics are unavailable.</p>
|
||||
<?php else: ?>
|
||||
<div class="cpu-gauge"><canvas id="cpuGauge" width="170" height="170" data-value="<?= $systemData['resources']['cpu']['usage'] ?>" aria-label="CPU usage <?= $systemData['resources']['cpu']['usage'] ?> percent"></canvas></div>
|
||||
<?php endif; ?>
|
||||
<?php else:
|
||||
$memData = $systemData['resources']['memory'];
|
||||
?>
|
||||
<?php if (!$memData['available_data']): ?>
|
||||
<p class="text-muted" role="status">Memory statistics are unavailable.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach (['usage' => 'Memory usage', 'swap_usage' => 'Swap usage'] as $usageKey => $label):
|
||||
$usagePercent = (float) rtrim($memData[$usageKey], '%');
|
||||
$fields = $usageKey === 'usage' ? ['total' => 'Total', 'used' => 'Used', 'available' => 'Available'] : ['swap_total' => 'Total', 'swap_used' => 'Used', 'swap_free' => 'Free'];
|
||||
?>
|
||||
<div class="resource-section">
|
||||
<div class="d-flex justify-content-between"><span><?= $label ?></span><strong><?= htmlspecialchars($memData[$usageKey]) ?></strong></div>
|
||||
<div class="progress mb-3"><div class="progress-bar bg-<?= $usagePercent > 90 ? 'danger' : ($usagePercent > 75 ? 'warning' : 'success') ?>" role="progressbar" style="width:<?= $usagePercent ?>%" aria-label="<?= $label ?>" aria-valuenow="<?= $usagePercent ?>" aria-valuemin="0" aria-valuemax="100"></div></div>
|
||||
<div class="row text-center">
|
||||
<?php foreach ($fields as $key => $fieldLabel): ?>
|
||||
<div class="col-4"><div class="p-2 border rounded"><div class="small text-muted"><?= $fieldLabel ?></div><div class="fw-bold"><?= htmlspecialchars($memData[$key]) ?></div></div></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php renderWorkspaceEnd(); ?>
|
||||
|
||||
</div>
|
||||
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3">
|
||||
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||
<div class="d-flex gap-3">
|
||||
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||
</a>
|
||||
<a href="https://app.freelo.io/public/shared-link-view/?a=81efbcb4df761b3f29cdc80855b41e6d&b=4519c717f0729cc8e953af661e9dc981" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||
<img src="../../img/freelo-logo-rgb.png" alt="Freelo Logo" class="img-fluid hover-effect light-logo" style="height: 24px;">
|
||||
<img src="../../img/freelo-logo-rgb-on-dark.png" alt="Freelo Logo" class="img-fluid hover-effect dark-logo" style="height: 24px;">
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script>
|
||||
let refreshInterval;
|
||||
let isRefreshing = false;
|
||||
@@ -441,13 +166,13 @@ let isRefreshing = false;
|
||||
function updateData(html) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
|
||||
// Update monitor cards
|
||||
document.querySelectorAll('.monitor-card').forEach((card, index) => {
|
||||
const newCard = doc.querySelectorAll('.monitor-card')[index];
|
||||
if (newCard && card.querySelector('.card-body')) {
|
||||
card.querySelector('.card-body').innerHTML = newCard.querySelector('.card-body').innerHTML;
|
||||
|
||||
|
||||
// Redraw CPU gauge if this is the CPU card
|
||||
if (card.querySelector('.cpu-gauge')) {
|
||||
const cpuValue = parseFloat(newCard.querySelector('canvas').getAttribute('data-value') || '0');
|
||||
@@ -455,7 +180,7 @@ function updateData(html) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Update last update time
|
||||
const newLastUpdate = doc.querySelector('.badge.bg-light.text-dark')?.innerHTML;
|
||||
const currentLastUpdate = document.querySelector('.badge.bg-light.text-dark');
|
||||
@@ -463,11 +188,6 @@ function updateData(html) {
|
||||
currentLastUpdate.innerHTML = newLastUpdate;
|
||||
}
|
||||
|
||||
// Update CPU gauge if needed
|
||||
const cpuValue = document.querySelector('.cpu-gauge .h4')?.textContent;
|
||||
if (cpuValue) {
|
||||
drawCpuGauge(parseFloat(cpuValue));
|
||||
}
|
||||
}
|
||||
|
||||
function startRefresh() {
|
||||
@@ -476,7 +196,10 @@ function startRefresh() {
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
fetch(window.location.href)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
if (!response.ok || response.redirected) throw new Error('Status refresh failed');
|
||||
return response.text();
|
||||
})
|
||||
.then(html => {
|
||||
updateData(html);
|
||||
isRefreshing = false;
|
||||
@@ -506,17 +229,16 @@ document.addEventListener('visibilitychange', () => {
|
||||
});
|
||||
|
||||
// Initial setup
|
||||
startRefresh();
|
||||
drawCpuGauge(parseFloat(document.querySelector('.cpu-gauge .h4')?.textContent || '0'));
|
||||
if (!document.hidden) startRefresh();
|
||||
|
||||
function drawCpuGauge(value) {
|
||||
const canvas = document.getElementById('cpuGauge');
|
||||
if (!canvas) return;
|
||||
|
||||
|
||||
// Ensure value is a valid number between 0 and 100
|
||||
value = parseFloat(value) || 0;
|
||||
value = Math.max(0, Math.min(100, value));
|
||||
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const centerX = canvas.width / 2;
|
||||
const centerY = canvas.height / 2;
|
||||
@@ -527,14 +249,14 @@ function drawCpuGauge(value) {
|
||||
// Draw background circle
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = getComputedStyle(document.body).getPropertyValue('--bs-border-color');
|
||||
ctx.strokeStyle = getComputedStyle(document.body).getPropertyValue('--line');
|
||||
ctx.lineWidth = 10;
|
||||
ctx.stroke();
|
||||
|
||||
// Draw value arc
|
||||
const startAngle = -Math.PI / 2;
|
||||
const endAngle = startAngle + (Math.PI * 2 * value / 100);
|
||||
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
|
||||
ctx.strokeStyle = value > 90 ? '#dc3545' : value > 75 ? '#ffc107' : '#198754';
|
||||
@@ -543,7 +265,7 @@ function drawCpuGauge(value) {
|
||||
|
||||
// Draw percentage text inside the circle
|
||||
ctx.font = 'bold 20px Arial';
|
||||
ctx.fillStyle = getComputedStyle(document.body).getPropertyValue('--bs-body-color');
|
||||
ctx.fillStyle = getComputedStyle(document.body).getPropertyValue('--text');
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(`${value.toFixed(1)}%`, centerX, centerY - 10);
|
||||
@@ -558,6 +280,10 @@ window.addEventListener('load', function() {
|
||||
const cpuValue = parseFloat(document.querySelector('.cpu-gauge canvas')?.getAttribute('data-value') || '0');
|
||||
drawCpuGauge(cpuValue);
|
||||
});
|
||||
new MutationObserver(() => {
|
||||
const cpuValue = document.getElementById('cpuGauge')?.dataset.value;
|
||||
if (cpuValue !== undefined) drawCpuGauge(cpuValue);
|
||||
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user