mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
dbc5a01de9
#4: Postfix virtual mailbox config (virtual_mailbox_domains/maps, vmail user, maildir at /var/mail/vhosts/%d/%n). Dovecot SQL backend pointed at novacpx.email_accounts with SHA512-CRYPT passdb and per-domain Maildir userdb. #5: BIND9 confirmed working — dig @localhost resolves testdomain1.com correctly. #6: Certbot 2.9.0 confirmed installed; domains.document_root wired; infrastructure ready for live domain issuance (testdomain1.com not publicly resolvable so dry-run expected to fail). #7: Fixed all broken user-panel API queries — missing tables (databases, ftp_accounts, ssl_certs, cron_jobs, php_configs, notifications) created; `databases` reserved-word backtick-quoted across DatabaseManager+endpoints; domains.php is_primary→type=main, doc_root→document_root column fixes; DNSManager::createZone call signature fixed; stats/account auto-resolves account_id for user role. #8: assert_account_access() helper added to api/index.php; reseller ownership check wired into email, ftp, databases, domains, dns, ssl endpoints. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
5.1 KiB
PHP
121 lines
5.1 KiB
PHP
<?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 = isset($ram['MemTotal:'], $ram['MemAvailable:']) && $ram['MemTotal:'] > 0
|
|
? round((1 - $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) {
|
|
$user = Auth::getInstance()->user();
|
|
if ($user['role'] === 'user') {
|
|
$acctRow = $db->fetchOne("SELECT id FROM accounts WHERE user_id = ?", [$user['uid']]);
|
|
$accountId = $acctRow ? (int)$acctRow['id'] : 0;
|
|
} else {
|
|
$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['max_databases'] ?? 0,
|
|
'emails' => $emailCount,
|
|
'email_limit' => $pkg['max_email'] ?? 0,
|
|
'ftp' => $ftpCount,
|
|
'ftp_limit' => $pkg['max_ftp'] ?? 0,
|
|
'domains' => $domCount,
|
|
'subdomain_limit' => $pkg['max_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),
|
|
};
|