mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
feat: server monitoring charts, package limits, WHMCS bridge, server options (#19-22)
#19 Server monitoring charts: - server_stats table (migration 007) + collect-stats.php cron script - serverStatus() page rebuilt with Chart.js line charts (CPU/RAM/disk) - Chart.js lazy-loaded from CDN; history shown for last 24h #20 Cron job manager: already complete in prior session #21 Package limits enforcement: - email.php: checks max_email before creating email account - databases.php: checks max_databases before creating database - ftp.php: checks max_ftp before creating FTP account - stats.php: fixed column names (max_email/max_ftp/max_databases vs old aliases) #22b WHMCS billing bridge: - whmcs.php endpoint: create/suspend/unsuspend/terminate/changepackage/info - Auth via X-WHMCS-Key header; enabled/key stored in settings table #22a/c/d/e Server options admin page: - Web/mail/FTP/DNS server selection with settings persistence - Server switch triggers /opt/novacpx/bin/switch-*.sh scripts (if present) - NS health checker: live dig lookup of all zones vs configured NS1/NS2 - system.php: server-options + save-option actions - dns.php: ns-health action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* NovaCPX stats collector — runs every 5 minutes via cron
|
||||
* Cron: *\/5 * * * * root /usr/bin/php /opt/novacpx/bin/collect-stats.php
|
||||
*/
|
||||
define('NOVACPX_ROOT', '/srv/novacpx/public');
|
||||
define('NOVACPX_LIB', NOVACPX_ROOT . '/lib');
|
||||
require NOVACPX_ROOT . '/lib/Core.php';
|
||||
require NOVACPX_ROOT . '/lib/DB.php';
|
||||
|
||||
// CPU usage (idle from /proc/stat)
|
||||
$stat1 = file_get_contents('/proc/stat');
|
||||
usleep(200000); // 200ms sample
|
||||
$stat2 = file_get_contents('/proc/stat');
|
||||
|
||||
$line1 = explode(' ', trim(explode("\n", $stat1)[0]));
|
||||
$line2 = explode(' ', trim(explode("\n", $stat2)[0]));
|
||||
array_shift($line1); array_shift($line2);
|
||||
$line1 = array_filter($line1, 'strlen'); $line2 = array_filter($line2, 'strlen');
|
||||
$line1 = array_values($line1); $line2 = array_values($line2);
|
||||
$total1 = array_sum($line1); $total2 = array_sum($line2);
|
||||
$idle1 = (int)($line1[3] ?? 0); $idle2 = (int)($line2[3] ?? 0);
|
||||
$cpuPct = $total1 === $total2 ? 0 : round((1 - ($idle2 - $idle1) / ($total2 - $total1)) * 100, 2);
|
||||
|
||||
// RAM
|
||||
$meminfo = [];
|
||||
foreach (file('/proc/meminfo') as $line) {
|
||||
if (preg_match('/^(\w+):\s+(\d+)/', $line, $m)) $meminfo[$m[1]] = (int)$m[2];
|
||||
}
|
||||
$ramPct = isset($meminfo['MemTotal'], $meminfo['MemAvailable'])
|
||||
? round((1 - $meminfo['MemAvailable'] / $meminfo['MemTotal']) * 100, 2)
|
||||
: 0;
|
||||
|
||||
// Disk
|
||||
$dfLine = trim(shell_exec("df / | tail -1") ?: '');
|
||||
$dfParts = preg_split('/\s+/', $dfLine);
|
||||
$diskPct = isset($dfParts[4]) ? (float) rtrim($dfParts[4], '%') : 0;
|
||||
|
||||
// Load
|
||||
$load = sys_getloadavg();
|
||||
|
||||
// Network I/O (eth0 or first interface)
|
||||
$netIn = 0; $netOut = 0;
|
||||
$netData = @file_get_contents('/proc/net/dev');
|
||||
if ($netData) {
|
||||
foreach (explode("\n", $netData) as $line) {
|
||||
if (preg_match('/^\s*(eth0|ens\w+|enp\w+|eno\w+):\s*(\d+)(?:\s+\d+){7}\s+(\d+)/', $line, $m)) {
|
||||
$netIn = (int)($m[2] / 1024);
|
||||
$netOut = (int)($m[3] / 1024);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert
|
||||
$db = DB::getInstance();
|
||||
$db->execute(
|
||||
"INSERT INTO server_stats (cpu_usage, ram_usage, disk_usage, load_avg, net_in_kb, net_out_kb)
|
||||
VALUES (?,?,?,?,?,?)",
|
||||
[$cpuPct, $ramPct, $diskPct, $load[0], $netIn, $netOut]
|
||||
);
|
||||
|
||||
// Prune rows older than 30 days
|
||||
$db->execute("DELETE FROM server_stats WHERE recorded_at < DATE_SUB(NOW(), INTERVAL 30 DAY)");
|
||||
Reference in New Issue
Block a user