mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
Add full API endpoint suite, lib managers, webmail (Roundcube :8883), and NovaCPX icon/branding assets
- 14 API endpoints: accounts, packages, domains, dns, email, databases, ftp, ssl, cron, php, files, stats, webmail, server_setup - 8 lib managers: AccountManager, VhostManager, DNSManager, EmailManager, DatabaseManager, PHPManager, FTPManager, SSLManager - Roundcube webmail on dedicated port 8883 (sequenced after 8880/8881/8882) - Custom NovaCPX SVG icon sprite (30+ unique icons), logo, mark, favicon - PORT_WEBMAIL=8883 wired into Core.php, install.sh, UFW, Fail2Ban, credentials file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats endpoint — resource usage history, charts, per-account usage
|
||||
*/
|
||||
$db = DB::getInstance();
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
$user = Auth::getInstance()->user();
|
||||
|
||||
match ($action) {
|
||||
'server' => (function() use ($db) {
|
||||
// Last 24 hours of 5-min samples
|
||||
$rows = $db->fetchAll(
|
||||
"SELECT cpu_usage, ram_usage, disk_usage, load_avg, recorded_at
|
||||
FROM server_stats ORDER BY recorded_at DESC LIMIT 288"
|
||||
);
|
||||
$rows = array_reverse($rows);
|
||||
|
||||
// Current live snapshot
|
||||
$cpu = (float) trim(shell_exec("top -bn1 | grep 'Cpu(s)' | awk '{print $2+$4}'") ?: 0);
|
||||
$ram = [];
|
||||
foreach (file('/proc/meminfo') as $line) {
|
||||
[$k, $v] = preg_split('/\s+/', trim($line), 2);
|
||||
$ram[$k] = (int) $v;
|
||||
}
|
||||
$ramPct = $ram['MemTotal_kB'] ?? 0
|
||||
? round(100 - ($ram['MemAvailable:'] / $ram['MemTotal:']) * 100, 1)
|
||||
: 0;
|
||||
|
||||
$disk = [];
|
||||
$dfLine = trim(shell_exec("df / | tail -1") ?: '');
|
||||
$dfParts = preg_split('/\s+/', $dfLine);
|
||||
$diskPct = isset($dfParts[4]) ? (int) $dfParts[4] : 0;
|
||||
$diskUsed = isset($dfParts[2]) ? round($dfParts[2]/1024/1024, 1) : 0;
|
||||
$diskTotal = isset($dfParts[1]) ? round($dfParts[1]/1024/1024, 1) : 0;
|
||||
|
||||
$load = sys_getloadavg();
|
||||
Response::success([
|
||||
'current' => [
|
||||
'cpu' => $cpu,
|
||||
'ram' => $ramPct,
|
||||
'disk_pct' => $diskPct,
|
||||
'disk_used' => $diskUsed,
|
||||
'disk_total' => $diskTotal,
|
||||
'load' => $load[0],
|
||||
],
|
||||
'history' => $rows,
|
||||
]);
|
||||
})(),
|
||||
|
||||
'account' => (function() use ($db, $body) {
|
||||
$accountId = (int)($body['account_id'] ?? $_GET['account_id'] ?? 0);
|
||||
if (!$accountId) Response::error("account_id required");
|
||||
$acct = $db->fetchOne("SELECT * FROM accounts WHERE id = ?", [$accountId]);
|
||||
if (!$acct) Response::error("Account not found", 404);
|
||||
|
||||
// Disk
|
||||
$diskKB = (int)trim(shell_exec("du -sk " . escapeshellarg($acct['home_dir']) . " 2>/dev/null | awk '{print $1}'") ?: 0);
|
||||
$diskMB = round($diskKB / 1024, 1);
|
||||
|
||||
// Inodes
|
||||
$inodes = (int)trim(shell_exec("find " . escapeshellarg($acct['home_dir']) . " 2>/dev/null | wc -l") ?: 0);
|
||||
|
||||
// DB count & size
|
||||
$dbs = $db->fetchAll("SELECT id FROM databases WHERE account_id = ?", [$accountId]);
|
||||
$dbCount = count($dbs);
|
||||
|
||||
// Email count
|
||||
$emailCount = (int)($db->fetchOne("SELECT COUNT(*) c FROM email_accounts WHERE account_id = ?", [$accountId])['c'] ?? 0);
|
||||
// FTP count
|
||||
$ftpCount = (int)($db->fetchOne("SELECT COUNT(*) c FROM ftp_accounts WHERE account_id = ?", [$accountId])['c'] ?? 0);
|
||||
// Domain count
|
||||
$domCount = (int)($db->fetchOne("SELECT COUNT(*) c FROM domains WHERE account_id = ?", [$accountId])['c'] ?? 0);
|
||||
|
||||
// Package limits
|
||||
$pkg = $db->fetchOne("SELECT * FROM packages WHERE id = ?", [$acct['package_id'] ?? 0]);
|
||||
|
||||
Response::success([
|
||||
'disk_mb' => $diskMB,
|
||||
'disk_limit' => $pkg['disk_mb'] ?? 0,
|
||||
'inodes' => $inodes,
|
||||
'databases' => $dbCount,
|
||||
'db_limit' => $pkg['databases'] ?? 0,
|
||||
'emails' => $emailCount,
|
||||
'email_limit' => $pkg['email_accounts'] ?? 0,
|
||||
'ftp' => $ftpCount,
|
||||
'ftp_limit' => $pkg['ftp_accounts'] ?? 0,
|
||||
'domains' => $domCount,
|
||||
'subdomain_limit' => $pkg['subdomains'] ?? 0,
|
||||
]);
|
||||
})(),
|
||||
|
||||
'bandwidth' => (function() use ($db, $body) {
|
||||
$accountId = (int)($body['account_id'] ?? $_GET['account_id'] ?? 0);
|
||||
// Read nginx/apache access log and sum bytes for account's domains
|
||||
$acct = $db->fetchOne("SELECT username FROM accounts WHERE id = ?", [$accountId]);
|
||||
if (!$acct) Response::error("Account not found");
|
||||
$logFile = "/var/log/novacpx/{$acct['username']}-access.log";
|
||||
$daily = [];
|
||||
if (file_exists($logFile)) {
|
||||
$lines = explode("\n", trim(shell_exec("tail -50000 " . escapeshellarg($logFile)) ?: ''));
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/\[(\d{2}\/\w+\/\d{4})/', $line, $m) &&
|
||||
preg_match('/" \d+ (\d+)/', $line, $b)) {
|
||||
$day = $m[1];
|
||||
$daily[$day] = ($daily[$day] ?? 0) + (int)$b[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
Response::success(array_map(fn($d,$b) => ['date'=>$d,'bytes'=>$b], array_keys($daily), $daily));
|
||||
})(),
|
||||
|
||||
default => Response::error("Unknown stats action: $action", 404),
|
||||
};
|
||||
Reference in New Issue
Block a user