feat: graphical RAID display, remove server action buttons
- Remove Restart Web, Restart PHP-FPM, Shutdown, Reboot buttons - Remove runServerAction() library method and action() controller - Remove /system-ops/action route (no longer needed) - Parse /proc/mdstat into structured array (name, level, health, drives) - Show each RAID array as a card with colored drive icons per-slot - Green drive = healthy, red = failed, blue = spare - Show rebuild progress bar when array is rebuilding - Internet status shown as colored badge with wifi icon - Services shown as colored badges (green=running, grey=other)
This commit is contained in:
parent
eb81f1999f
commit
1073ab8aca
|
|
@ -1218,7 +1218,6 @@ $routes->group('setting', static function($routes) {
|
|||
$routes->get('admin/system-ops/panel', 'SystemOps::panel', ['filter' => $settingsAdminFilter]);
|
||||
$routes->get('admin/system-ops/diagnose', 'SystemOps::diagnose', ['filter' => $settingsAdminFilter]);
|
||||
$routes->post('admin/system-ops/updateDirect', 'SystemOps::updateDirect', ['filter' => $settingsAdminFilter]);
|
||||
$routes->post('admin/system-ops/action', 'SystemOps::action', ['filter' => $settingsAdminFilter]);
|
||||
$routes->post('admin/hospital-profile/save', 'Setting\\HospitalProfile::save');
|
||||
$routes->post('admin/hospital-profile/delete-logo', 'Setting\\HospitalProfile::deleteLogo');
|
||||
$routes->post('admin/hospital-profile/reset', 'Setting\\HospitalProfile::reset');
|
||||
|
|
|
|||
|
|
@ -37,15 +37,6 @@ class SystemOps extends BaseController
|
|||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
public function action()
|
||||
{
|
||||
$action = (string) $this->request->getPost('action');
|
||||
$ops = new SystemOperations();
|
||||
$result = $ops->runServerAction($action);
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
public function diagnose()
|
||||
{
|
||||
$ops = new SystemOperations();
|
||||
|
|
|
|||
|
|
@ -400,42 +400,6 @@ class SystemOperations
|
|||
return $info;
|
||||
}
|
||||
|
||||
public function runServerAction(string $action): array
|
||||
{
|
||||
$commands = [
|
||||
'restart_web' => ['systemctl restart nginx', 'systemctl restart apache2', 'service nginx restart', 'service apache2 restart'],
|
||||
'restart_php' => ['systemctl restart php-fpm', 'systemctl restart php8.3-fpm', 'service php-fpm restart', 'service php8.3-fpm restart'],
|
||||
'reboot' => ['systemctl reboot'],
|
||||
'shutdown' => ['systemctl poweroff'],
|
||||
];
|
||||
|
||||
$options = $commands[$action] ?? [];
|
||||
if ($options === []) {
|
||||
return ['ok' => false, 'message' => 'Unknown action'];
|
||||
}
|
||||
|
||||
$command = $this->findWorkingCommand($options);
|
||||
if ($command === null) {
|
||||
return ['ok' => false, 'message' => 'System control commands are not available on this server.'];
|
||||
}
|
||||
|
||||
$result = $this->runCommand($command);
|
||||
$status = $result['exit_code'] === 0 ? 'success' : 'failed';
|
||||
$this->appendHistory([
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'type' => $action,
|
||||
'status' => $status,
|
||||
'message' => ucfirst(str_replace('_', ' ', $action)) . ' completed',
|
||||
'detail' => $result['output'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => $result['exit_code'] === 0,
|
||||
'message' => $result['exit_code'] === 0 ? ucfirst(str_replace('_', ' ', $action)) . ' executed.' : 'Action failed.',
|
||||
'output' => $result['output'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getHistory(): array
|
||||
{
|
||||
if (! file_exists($this->historyPath)) {
|
||||
|
|
@ -954,16 +918,97 @@ class SystemOperations
|
|||
return $result;
|
||||
}
|
||||
|
||||
private function readRaidStatus(): string
|
||||
private function readRaidStatus(): array
|
||||
{
|
||||
if (file_exists('/proc/mdstat')) {
|
||||
$content = file_get_contents('/proc/mdstat');
|
||||
if (is_string($content) && trim($content) !== '') {
|
||||
return trim($content);
|
||||
}
|
||||
if (!file_exists('/proc/mdstat')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return 'Not detected';
|
||||
$content = @file_get_contents('/proc/mdstat');
|
||||
if (!is_string($content) || trim($content) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$arrays = [];
|
||||
$lines = explode("\n", $content);
|
||||
$i = 0;
|
||||
|
||||
while ($i < count($lines)) {
|
||||
$line = trim($lines[$i]);
|
||||
|
||||
// Match array definition: "md0 : active raid1 sda1[0] sdb1[1]"
|
||||
if (preg_match('/^(md\d+)\s*:\s*(\w+)\s+(\w+)\s+(.+)$/', $line, $m)) {
|
||||
$name = $m[1];
|
||||
$state = $m[2]; // active / inactive
|
||||
$level = $m[3]; // raid1, raid5, etc.
|
||||
$devStr = $m[4];
|
||||
|
||||
// Parse devices: sda1[0], sdb1[1], sdc1[2](F) etc.
|
||||
preg_match_all('/(\w+)\[(\d+)\](\(F\)|\(S\))?/', $devStr, $dm);
|
||||
$devices = [];
|
||||
foreach ($dm[1] as $k => $dev) {
|
||||
$flag = $dm[3][$k] ?? '';
|
||||
$devices[] = [
|
||||
'name' => $dev,
|
||||
'index' => (int) $dm[2][$k],
|
||||
'failed' => $flag === '(F)',
|
||||
'spare' => $flag === '(S)',
|
||||
];
|
||||
}
|
||||
|
||||
// Next line has block count and health: " 1953382400 blocks super 1.2 [2/2] [UU]"
|
||||
$health = 'Unknown';
|
||||
$total = 0;
|
||||
$active = 0;
|
||||
$healthStr = '';
|
||||
if (isset($lines[$i + 1])) {
|
||||
$next = trim($lines[$i + 1]);
|
||||
// [total/active]
|
||||
if (preg_match('/\[(\d+)\/(\d+)\]/', $next, $nm)) {
|
||||
$total = (int) $nm[1];
|
||||
$active = (int) $nm[2];
|
||||
}
|
||||
// [UU] or [U_UU] style
|
||||
if (preg_match('/\[([U_]+)\]/', $next, $hm)) {
|
||||
$healthStr = $hm[1];
|
||||
}
|
||||
// Rebuild progress line (line after next)
|
||||
$rebuildProgress = null;
|
||||
if (isset($lines[$i + 2]) && preg_match('/(\d+\.\d+)%/', $lines[$i + 2], $pm)) {
|
||||
$rebuildProgress = (float) $pm[1];
|
||||
}
|
||||
}
|
||||
|
||||
$failedCount = substr_count($healthStr, '_');
|
||||
if ($state !== 'active') {
|
||||
$health = 'Inactive';
|
||||
} elseif ($failedCount === 0 && $active === $total && $total > 0) {
|
||||
$health = 'Healthy';
|
||||
} elseif ($failedCount > 0) {
|
||||
$health = 'Degraded';
|
||||
} elseif ($total === 0) {
|
||||
$health = 'Unknown';
|
||||
} else {
|
||||
$health = 'Rebuilding';
|
||||
}
|
||||
|
||||
$arrays[] = [
|
||||
'name' => $name,
|
||||
'state' => $state,
|
||||
'level' => strtoupper($level),
|
||||
'health' => $health,
|
||||
'health_str' => $healthStr,
|
||||
'total_drives' => $total,
|
||||
'active_drives' => $active,
|
||||
'failed_drives' => $failedCount,
|
||||
'devices' => $devices,
|
||||
'rebuild_progress' => $rebuildProgress ?? null,
|
||||
];
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $arrays;
|
||||
}
|
||||
|
||||
private function findWorkingCommand(array $options): ?string
|
||||
|
|
|
|||
|
|
@ -88,12 +88,8 @@
|
|||
setHealthState('healthy');
|
||||
setInterval(function(){ refreshPanel(); }, 30000);
|
||||
|
||||
$(document).off('click', '#btnUpdateDirectGithub, #btnRestartWeb, #btnRestartPhp, #btnReboot, #btnShutdown')
|
||||
.on('click', '#btnUpdateDirectGithub', function(){ if (!confirm('Deploy latest HMS code from GitHub?\n\nThis will download the latest code and update all files.')) return; postAction('<?= base_url('setting/admin/system-ops/updateDirect') ?>', {}, this); })
|
||||
.on('click', '#btnRestartWeb', function(){ if (!confirm('Restart the web server now?')) return; postAction('<?= base_url('setting/admin/system-ops/action') ?>', {action: 'restart_web'}, this); })
|
||||
.on('click', '#btnRestartPhp', function(){ if (!confirm('Restart PHP-FPM now?')) return; postAction('<?= base_url('setting/admin/system-ops/action') ?>', {action: 'restart_php'}, this); })
|
||||
.on('click', '#btnReboot', function(){ if (!confirm('Reboot the server now?')) return; postAction('<?= base_url('setting/admin/system-ops/action') ?>', {action: 'reboot'}, this); })
|
||||
.on('click', '#btnShutdown', function(){ if (!confirm('Shut down the server now?')) return; postAction('<?= base_url('setting/admin/system-ops/action') ?>', {action: 'shutdown'}, this); });
|
||||
$(document).off('click', '#btnUpdateDirectGithub')
|
||||
.on('click', '#btnUpdateDirectGithub', function(){ if (!confirm('Deploy latest HMS code from GitHub?\n\nThis will download the latest code and update all files.')) return; postAction('<?= base_url('setting/admin/system-ops/updateDirect') ?>', {}, this); });
|
||||
}
|
||||
</script>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -45,11 +45,6 @@
|
|||
</div>
|
||||
<div class="card-body d-grid gap-2">
|
||||
<button class="btn btn-success" id="btnUpdateDirectGithub" title="Download and deploy latest from GitHub">Deploy from GitHub</button>
|
||||
<hr class="my-2">
|
||||
<button class="btn btn-outline-warning" id="btnRestartWeb">Restart Web Server</button>
|
||||
<button class="btn btn-outline-info" id="btnRestartPhp">Restart PHP-FPM</button>
|
||||
<button class="btn btn-outline-danger" id="btnShutdown">Shutdown Server</button>
|
||||
<button class="btn btn-outline-secondary" id="btnReboot">Reboot Server</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -106,16 +101,102 @@
|
|||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="small text-muted">Internet</div>
|
||||
<div class="fw-semibold"><?= esc($status['internet'] ?? 'Unavailable') ?></div>
|
||||
<?php $inet = $status['internet'] ?? 'Unavailable'; ?>
|
||||
<span class="badge <?= $inet === 'Online' ? 'bg-success' : 'bg-danger' ?> fs-6">
|
||||
<i class="bi <?= $inet === 'Online' ? 'bi-wifi' : 'bi-wifi-off' ?>"></i>
|
||||
<?= esc($inet) ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="small text-muted">Services</div>
|
||||
<div class="small"><?= esc(implode(', ', array_keys($status['services'] ?? []))) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-muted">RAID</div>
|
||||
<div class="small"><?= esc($status['raid'] ?? 'Unavailable') ?></div>
|
||||
<div class="small text-muted mb-1">Services</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<?php foreach ($status['services'] ?? [] as $svc => $state): ?>
|
||||
<span class="badge <?= $state === 'running' ? 'bg-success' : 'bg-secondary' ?>"><?= esc($svc) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$raidArrays = $status['raid'] ?? [];
|
||||
?>
|
||||
<div class="small text-muted mb-2">RAID Arrays</div>
|
||||
<?php if (empty($raidArrays)): ?>
|
||||
<div class="d-flex align-items-center gap-2 text-muted">
|
||||
<i class="bi bi-hdd-stack fs-5"></i>
|
||||
<span class="small">No RAID detected</span>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($raidArrays as $arr): ?>
|
||||
<?php
|
||||
$health = $arr['health'] ?? 'Unknown';
|
||||
$healthColor = match($health) {
|
||||
'Healthy' => 'success',
|
||||
'Degraded' => 'danger',
|
||||
'Rebuilding'=> 'warning',
|
||||
default => 'secondary',
|
||||
};
|
||||
$healthIcon = match($health) {
|
||||
'Healthy' => 'bi-shield-check',
|
||||
'Degraded' => 'bi-shield-exclamation',
|
||||
'Rebuilding'=> 'bi-arrow-repeat',
|
||||
default => 'bi-question-circle',
|
||||
};
|
||||
$healthStr = $arr['health_str'] ?? '';
|
||||
?>
|
||||
<div class="border rounded p-2 mb-2" style="background: #f8f9fa;">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<span class="fw-bold"><?= esc($arr['name']) ?></span>
|
||||
<span class="badge bg-secondary ms-1"><?= esc($arr['level']) ?></span>
|
||||
<span class="text-muted small ms-1"><?= esc($arr['state']) ?></span>
|
||||
</div>
|
||||
<span class="badge bg-<?= $healthColor ?>">
|
||||
<i class="bi <?= $healthIcon ?>"></i> <?= esc($health) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Drive visual indicators -->
|
||||
<div class="d-flex gap-1 flex-wrap mb-1">
|
||||
<?php
|
||||
// Build per-slot health from health_str [UU_] etc.
|
||||
$slots = str_split($healthStr);
|
||||
foreach ($arr['devices'] ?? [] as $di => $dev):
|
||||
$slotOk = ($slots[$dev['index']] ?? '_') === 'U';
|
||||
if ($dev['spare']) {
|
||||
$driveColor = 'bg-info'; $driveIcon = 'bi-hdd'; $driveTitle = 'Spare';
|
||||
} elseif ($dev['failed']) {
|
||||
$driveColor = 'bg-danger'; $driveIcon = 'bi-hdd'; $driveTitle = 'Failed';
|
||||
} elseif ($slotOk) {
|
||||
$driveColor = 'bg-success'; $driveIcon = 'bi-hdd-fill'; $driveTitle = 'Healthy';
|
||||
} else {
|
||||
$driveColor = 'bg-danger'; $driveIcon = 'bi-hdd'; $driveTitle = 'Failed';
|
||||
}
|
||||
?>
|
||||
<div class="d-flex flex-column align-items-center" title="<?= esc($dev['name']) ?> — <?= $driveTitle ?>">
|
||||
<span class="badge <?= $driveColor ?> p-2">
|
||||
<i class="bi <?= $driveIcon ?> fs-6"></i>
|
||||
</span>
|
||||
<span class="text-muted" style="font-size:10px;"><?= esc($dev['name']) ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Drive count summary -->
|
||||
<div class="small text-muted">
|
||||
<?= esc((string)$arr['active_drives']) ?>/<?= esc((string)$arr['total_drives']) ?> drives active
|
||||
<?php if (($arr['failed_drives'] ?? 0) > 0): ?>
|
||||
— <span class="text-danger fw-semibold"><?= esc((string)$arr['failed_drives']) ?> failed</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($arr['rebuild_progress'] !== null): ?>
|
||||
— Rebuilding <?= esc((string)$arr['rebuild_progress']) ?>%
|
||||
<div class="progress mt-1" style="height:6px;">
|
||||
<div class="progress-bar bg-warning" style="width:<?= esc((string)$arr['rebuild_progress']) ?>%"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user