GHOSTSONAFB / public /index.php
ghostai1's picture
Upload index.php
ceead6b verified
<?php
// index.php - GhostAI Server Dashboard (Dark Bootstrap, High-Contrast, ADA-friendly)
// ---------- Helpers ----------
function run_cmd($cmd, $timeout = 3) {
// Basic guard: strip dangerous chars (still assume trusted admin usage)
$cmd = trim($cmd);
if ($cmd === '') return ['code' => 1, 'out' => 'N/A'];
// Timeout wrapper (Linux 'timeout' if available)
$timeout_bin = trim(shell_exec('command -v timeout 2>/dev/null')) ?: '';
$safe = $timeout_bin ? escapeshellcmd($timeout_bin) . " " . intval($timeout) . "s " . $cmd : $cmd;
$out = [];
$code = 0;
@exec($safe . ' 2>&1', $out, $code);
return ['code' => $code, 'out' => implode("\n", $out)];
}
function human_bytes($bytes) {
$u = ['B','KB','MB','GB','TB','PB'];
$i = 0;
while ($bytes >= 1024 && $i < count($u)-1) { $bytes /= 1024; $i++; }
return sprintf('%.1f %s', $bytes, $u[$i]);
}
function status_badge($status) {
$s = strtolower(trim($status));
if (in_array($s, ['online','active','up','running','listening'])) return ['success','Online'];
if (in_array($s, ['stopped','inactive','exited'])) return ['secondary','Stopped'];
if (in_array($s, ['errored','failed','dead'])) return ['danger','Error'];
if (in_array($s, ['unknown','n/a','not found'])) return ['dark','Unknown'];
return ['warning', ucfirst($status ?: 'Unknown')];
}
function parse_pm2() {
// Prefer JSON output
$res = run_cmd('pm2 jlist');
if ($res['code'] === 0 && strlen($res['out']) > 2) {
$json = json_decode($res['out'], true);
if (is_array($json)) return $json;
}
// Fallback: pm2 status tabular (parse minimally)
$res2 = run_cmd('pm2 list --no-color');
$apps = [];
if ($res2['code'] === 0) {
$lines = explode("\n", $res2['out']);
foreach ($lines as $ln) {
if (preg_match('/^\s*\|\s*(\S+)\s*\|\s*(\d+)\s*\|\s*([A-Z]+)\s*\|\s*(\d+)\s*\|\s*([\dms:\.]+)\s*\|\s*(\S*)/i', $ln, $m)) {
$apps[] = [
'name' => $m[1],
'pm_id' => $m[2],
'status' => strtolower($m[3]),
'cpu' => null,
'mem' => null,
'uptime' => $m[5],
'mode' => $m[6] ?? ''
];
}
}
}
return $apps;
}
function get_cpu_info() {
$model = trim(run_cmd("awk -F': ' '/model name/ {print \$2; exit}' /proc/cpuinfo")['out']);
$cores_p = intval(trim(run_cmd("getconf _NPROCESSORS_ONLN")['out'])) ?: 0;
$loadavg = trim(run_cmd("cut -d' ' -f1-3 /proc/loadavg")['out']);
return [$model ?: 'N/A', $cores_p, $loadavg ?: 'N/A'];
}
function get_mem_info() {
// /proc/meminfo (bytes -> kB)
$mem_total_kb = intval(trim(run_cmd("awk '/MemTotal/ {print \$2}' /proc/meminfo")['out']));
$mem_avail_kb = intval(trim(run_cmd("awk '/MemAvailable/ {print \$2}' /proc/meminfo")['out']));
$mem_used_kb = max(0, $mem_total_kb - $mem_avail_kb);
return [
'total' => human_bytes($mem_total_kb * 1024),
'used' => human_bytes($mem_used_kb * 1024),
'free' => human_bytes($mem_avail_kb * 1024),
'pct' => $mem_total_kb ? round(($mem_used_kb / $mem_total_kb) * 100) : 0
];
}
function get_disk_info() {
$res = run_cmd("df -h --output=target,size,used,avail,pcent -x tmpfs -x devtmpfs");
$rows = [];
if ($res['code'] === 0) {
$lines = explode("\n", trim($res['out']));
foreach (array_slice($lines, 1) as $l) {
$l = preg_replace('/\s+/', ' ', trim($l));
if (!$l) continue;
[$mount, $size, $used, $avail, $pct] = array_pad(explode(' ', $l), 5, '');
$rows[] = compact('mount','size','used','avail','pct');
}
}
return $rows;
}
function get_gpu_info() {
$nvsmi = trim(run_cmd('command -v nvidia-smi')['out']);
if (!$nvsmi) return [];
$q = '--query-gpu=name,driver_version,pstate,temperature.gpu,memory.total,memory.used,pcie.link.gen.current,pcie.link.width.current --format=csv,noheader';
$res = run_cmd("nvidia-smi $q", 4);
$out = [];
if ($res['code'] === 0) {
foreach (explode("\n", trim($res['out'])) as $line) {
if (!$line) continue;
$parts = array_map('trim', explode(',', $line));
$out[] = [
'name' => $parts[0] ?? 'NVIDIA GPU',
'driver' => $parts[1] ?? 'N/A',
'pstate' => $parts[2] ?? 'N/A',
'tempC' => $parts[3] ?? 'N/A',
'mem_total' => $parts[4] ?? 'N/A',
'mem_used' => $parts[5] ?? 'N/A',
'pcie_gen' => $parts[6] ?? 'N/A',
'pcie_w' => $parts[7] ?? 'N/A',
];
}
}
return $out;
}
function get_network_info() {
$hostname = trim(run_cmd('hostname')['out']);
$ips = trim(run_cmd("hostname -I")['out']);
$gateway = trim(run_cmd("ip route | awk '/default/ {print \$3; exit}'")['out']);
return [$hostname ?: 'N/A', $ips ?: 'N/A', $gateway ?: 'N/A'];
}
function get_os_info() {
$pretty = trim(run_cmd("awk -F'=' '/^PRETTY_NAME/{gsub(/\"/ ,\"\",\$2); print \$2}' /etc/os-release")['out']);
$kernel = trim(run_cmd('uname -r')['out']);
$arch = trim(run_cmd('uname -m')['out']);
$uptime = trim(run_cmd('uptime -p')['out']);
$boot = trim(run_cmd('who -b | awk \'{print $3, $4}\'')['out']);
$phpv = PHP_VERSION;
$nginx = trim(run_cmd('nginx -v 2>&1')['out']);
$nginx = $nginx ?: 'nginx not found';
return [$pretty ?: 'Linux', $kernel ?: 'N/A', $arch ?: 'N/A', $uptime ?: 'N/A', $boot ?: 'N/A', $phpv, $nginx];
}
function get_services_status($services = ['nginx','php-fpm','pm2']) {
$data = [];
foreach ($services as $svc) {
$cmd = "systemctl is-active $svc";
$r = run_cmd($cmd);
$status = $r['code'] === 0 ? trim($r['out']) : 'unknown';
$data[] = ['name' => $svc, 'status' => $status];
}
return $data;
}
function get_top_procs($limit = 8) {
$r = run_cmd("ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n " . intval($limit + 1));
$rows = [];
if ($r['code'] === 0) {
$lines = explode("\n", trim($r['out']));
foreach (array_slice($lines, 1) as $ln) {
$ln = preg_replace('/\s+/', ' ', trim($ln));
if (!$ln) continue;
[$pid,$comm,$cpu,$mem] = array_pad(explode(' ', $ln), 4, '');
$rows[] = compact('pid','comm','cpu','mem');
}
}
return $rows;
}
function get_log_tail($path, $lines = 80) {
if (!is_readable($path)) return "Log not found or unreadable: $path";
$r = run_cmd("tail -n " . intval($lines) . " " . escapeshellarg($path));
return $r['code'] === 0 ? $r['out'] : "Unable to read log.";
}
// ---------- Page Settings ----------
$auto_refresh = isset($_GET['autorefresh']) ? (int)$_GET['autorefresh'] : 0;
$refresh_secs = ($auto_refresh > 0 && $auto_refresh <= 600) ? $auto_refresh : 0;
$custom_log = isset($_GET['log']) ? $_GET['log'] : '';
$log_path = $custom_log ?: '/var/log/nginx/access.log';
// ---------- Data ----------
[$os_name,$kernel,$arch,$uptime,$booted,$phpv,$nginxv] = get_os_info();
[$hostname,$ips,$gw] = get_network_info();
[$cpu_model,$cores,$load] = get_cpu_info();
$mem = get_mem_info();
$disks = get_disk_info();
$gpus = get_gpu_info();
$pm2 = parse_pm2();
$services = get_services_status(['nginx','php-fpm','pm2']);
$procs = get_top_procs(8);
$log_tail = get_log_tail($log_path, 100);
?>
<!doctype html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php if ($refresh_secs): ?>
<meta http-equiv="refresh" content="<?php echo htmlspecialchars($refresh_secs); ?>">
<?php endif; ?>
<title>🖥️ GhostAI Server Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
color-scheme: dark;
}
body {
background: #0E1014 !important;
color: #EAECEF !important;
font-size: 1.05rem;
}
* { color: #EAECEF !important; }
.card { background: #111520; border: 1px solid #243049; border-radius: 16px; }
.badge { font-size: 0.9rem; }
.table > :not(caption) > * > * { background: transparent !important; color: #EAECEF !important; }
.nav-link { color: #EAECEF !important; }
.form-select, .form-control { background: #151a24; color: #EAECEF; border: 1px solid #2A3142; }
.skip-link {
position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden;
}
.skip-link:focus { position: static; width: auto; height: auto; margin: 8px; padding: 8px; background: #1f2937; border-radius: 8px; }
.kpi { font-weight: 700; font-size: 1.25rem; }
.progress { background-color: #1b2231; height: 12px; border-radius: 999px; }
.emoji { font-size: 1.4rem; margin-right: .35rem; }
.footer { color: #9aa4b2 !important; }
</style>
</head>
<body>
<a href="#main" class="skip-link" aria-label="Skip to content">Skip to content</a>
<nav class="navbar navbar-expand-lg border-bottom" aria-label="Primary">
<div class="container-fluid">
<span class="navbar-brand fw-bold">🖥️ GhostAI Dashboard</span>
<div class="d-flex align-items-center gap-2">
<form method="get" class="d-flex" role="search" aria-label="Auto refresh interval">
<label class="me-2" for="autorefresh">⏱️ Auto-refresh</label>
<select class="form-select form-select-sm me-2" id="autorefresh" name="autorefresh" aria-label="Auto refresh interval select">
<?php foreach ([0,5,10,15,30,60,120,300,600] as $sec): ?>
<option value="<?= $sec ?>" <?= $sec===$auto_refresh?'selected':''; ?>><?= $sec ?>s</option>
<?php endforeach; ?>
</select>
<button class="btn btn-sm btn-primary" type="submit" aria-label="Apply refresh interval">Apply</button>
</form>
</div>
</div>
</nav>
<main id="main" class="container py-4" aria-live="polite">
<div class="row g-3">
<div class="col-12 col-xl-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><span class="emoji">💻</span>System Overview</h5>
<div class="row g-3">
<div class="col-12">
<div class="kpi">Host: <span class="text-info-emphasis"><?= htmlspecialchars($hostname) ?></span></div>
<div><?= htmlspecialchars($os_name) ?> • Kernel <?= htmlspecialchars($kernel) ?> • <?= htmlspecialchars($arch) ?></div>
<div>Uptime: <?= htmlspecialchars($uptime) ?> • Boot: <?= htmlspecialchars($booted) ?></div>
<div>PHP: <?= htmlspecialchars($phpv) ?> • <?= htmlspecialchars($nginxv) ?></div>
<div>IP(s): <?= htmlspecialchars($ips) ?> • GW: <?= htmlspecialchars($gw) ?></div>
</div>
<div class="col-md-6">
<div class="card p-3">
<div class="fw-semibold"><span class="emoji">🧠</span>CPU</div>
<div class="small text-secondary">Model</div>
<div class="kpi"><?= htmlspecialchars($cpu_model) ?></div>
<div class="small">Cores: <span class="fw-semibold"><?= intval($cores) ?></span></div>
<div class="small">Load: <span class="fw-semibold"><?= htmlspecialchars($load) ?></span></div>
</div>
</div>
<div class="col-md-6">
<div class="card p-3">
<div class="fw-semibold"><span class="emoji">📈</span>Memory</div>
<div class="small text-secondary">Usage</div>
<div class="kpi"><?= htmlspecialchars($mem['used']) ?> / <?= htmlspecialchars($mem['total']) ?></div>
<div class="progress mt-2" role="progressbar" aria-valuenow="<?= intval($mem['pct']) ?>" aria-valuemin="0" aria-valuemax="100" aria-label="Memory usage">
<div class="progress-bar bg-warning" style="width: <?= intval($mem['pct']) ?>%"></div>
</div>
<div class="small mt-1">Free: <?= htmlspecialchars($mem['free']) ?> (<?= intval($mem['pct']) ?>%)</div>
</div>
</div>
</div>
<?php if (!empty($gpus)): ?>
<div class="mt-3">
<div class="fw-semibold mb-2"><span class="emoji">🎮</span>GPU</div>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead><tr><th>Name</th><th>Driver</th><th>Temp</th><th>VRAM</th><th>PCIe</th><th>P-State</th></tr></thead>
<tbody>
<?php foreach ($gpus as $g): ?>
<tr>
<td><?= htmlspecialchars($g['name']) ?></td>
<td><?= htmlspecialchars($g['driver']) ?></td>
<td><?= htmlspecialchars($g['tempC']) ?></td>
<td><?= htmlspecialchars($g['mem_used']) ?> / <?= htmlspecialchars($g['mem_total']) ?></td>
<td>Gen <?= htmlspecialchars($g['pcie_gen']) ?> ×<?= htmlspecialchars($g['pcie_w']) ?></td>
<td><?= htmlspecialchars($g['pstate']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php else: ?>
<div class="mt-3 small text-secondary">No NVIDIA GPU info (nvidia-smi not found or no GPU).</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-12 col-xl-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><span class="emoji">🗄️</span>Disks</h5>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead><tr><th>Mount</th><th>Size</th><th>Used</th><th>Avail</th><th>Use%</th></tr></thead>
<tbody>
<?php foreach ($disks as $d): ?>
<tr>
<td><?= htmlspecialchars($d['mount']) ?></td>
<td><?= htmlspecialchars($d['size']) ?></td>
<td><?= htmlspecialchars($d['used']) ?></td>
<td><?= htmlspecialchars($d['avail']) ?></td>
<td><span class="badge bg-<?= (intval(rtrim($d['pct'],'%')) >= 85 ? 'danger' : (intval(rtrim($d['pct'],'%')) >= 70 ? 'warning' : 'success')) ?>"><?= htmlspecialchars($d['pct']) ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="mt-4">
<h5 class="card-title"><span class="emoji">🧩</span>Services</h5>
<div class="row g-2">
<?php foreach ($services as $s): $b = status_badge($s['status']); ?>
<div class="col-auto">
<span class="badge bg-<?= $b[0] ?>" aria-label="<?= htmlspecialchars($s['name']) ?> status"><?= htmlspecialchars($s['name']) ?>: <?= htmlspecialchars($b[1]) ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="mt-4">
<h5 class="card-title"><span class="emoji">📊</span>Top Processes (CPU)</h5>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead><tr><th>PID</th><th>Command</th><th>CPU%</th><th>MEM%</th></tr></thead>
<tbody>
<?php foreach ($procs as $p): ?>
<tr>
<td><?= htmlspecialchars($p['pid']) ?></td>
<td><?= htmlspecialchars($p['comm']) ?></td>
<td><?= htmlspecialchars($p['cpu']) ?></td>
<td><?= htmlspecialchars($p['mem']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title"><span class="emoji">🎛️</span>PM2 Apps</h5>
<?php if (!empty($pm2) && is_array($pm2) && isset($pm2[0]['name']) || (isset($pm2[0]['name']) || (is_array($pm2) && count($pm2)>0))): ?>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Name</th><th>ID</th><th>Status</th><th>Mode</th><th>CPU</th><th>Mem</th><th>Uptime</th>
</tr>
</thead>
<tbody>
<?php
// Two shapes: JSON from jlist, or parsed table rows
if (!empty($pm2) && isset($pm2[0]['pm2_env'])) {
foreach ($pm2 as $app) {
$name = $app['name'] ?? ($app['pm2_env']['name'] ?? 'app');
$pm_id = $app['pm_id'] ?? ($app['pm2_env']['pm_id'] ?? '');
$st = strtolower($app['pm2_env']['status'] ?? 'unknown');
$mode = $app['pm2_env']['exec_mode'] ?? '';
$cpu = $app['monit']['cpu'] ?? null;
$mem_b = $app['monit']['memory'] ?? null;
$memh = $mem_b ? human_bytes($mem_b) : 'N/A';
$upt = $app['pm2_env']['pm_uptime'] ?? 0;
$upt_h = $upt ? (floor((time() - intval($upt/1000)) / 3600) . 'h') : 'N/A';
$b = status_badge($st);
echo "<tr>
<td>".htmlspecialchars($name)."</td>
<td>".htmlspecialchars($pm_id)."</td>
<td><span class='badge bg-{$b[0]}'>".htmlspecialchars($b[1])."</span></td>
<td>".htmlspecialchars($mode)."</td>
<td>".($cpu !== null ? htmlspecialchars($cpu).'%' : 'N/A')."</td>
<td>".htmlspecialchars($memh)."</td>
<td>".htmlspecialchars($upt_h)."</td>
</tr>";
}
} else {
foreach ($pm2 as $app) {
$b = status_badge($app['status'] ?? 'unknown');
echo "<tr>
<td>".htmlspecialchars($app['name'] ?? 'app')."</td>
<td>".htmlspecialchars($app['pm_id'] ?? '')."</td>
<td><span class='badge bg-{$b[0]}'>".htmlspecialchars($b[1])."</span></td>
<td>".htmlspecialchars($app['mode'] ?? '')."</td>
<td>".htmlspecialchars($app['cpu'] ?? 'N/A')."</td>
<td>".htmlspecialchars($app['mem'] ?? 'N/A')."</td>
<td>".htmlspecialchars($app['uptime'] ?? 'N/A')."</td>
</tr>";
}
}
?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="small text-secondary">PM2 not detected or no apps configured.</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title"><span class="emoji">🪵</span>Logs</h5>
<form method="get" class="row g-2 mb-3" aria-label="Log selection">
<div class="col-md-6">
<label for="log" class="form-label">Log file path</label>
<input type="text" class="form-control" id="log" name="log" value="<?= htmlspecialchars($log_path) ?>" placeholder="/var/log/nginx/access.log" aria-describedby="logHelp">
<div id="logHelp" class="form-text">Enter a readable log file path to tail.</div>
</div>
<div class="col-md-3">
<label for="autorefresh2" class="form-label">Auto-refresh</label>
<select class="form-select" id="autorefresh2" name="autorefresh">
<?php foreach ([0,5,10,15,30,60,120,300,600] as $sec): ?>
<option value="<?= $sec ?>" <?= $sec===$auto_refresh?'selected':''; ?>><?= $sec ?>s</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button class="btn btn-success w-100" type="submit">Update</button>
</div>
</form>
<pre class="p-3" style="background:#0b0f17; border-radius:12px; max-height:380px; overflow:auto; white-space:pre-wrap;"><?= htmlspecialchars($log_tail) ?></pre>
</div>
</div>
</div>
</div>
</main>
<footer class="container py-4 footer">
<div class="d-flex flex-wrap justify-content-between">
<div>© <?= date('Y') ?> GhostAIHigh-contrast, keyboard-navigable UI</div>
<div>
<span class="me-2">Contrast: AAA</span>
<span class="me-2">ARIA Labels Enabled</span>
<span>Keyboard Shortcuts: <kbd>Tab</kbd> to navigate</span>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
</body>
</html>