/**
* NovaCPX Admin Panel — page controllers
*/
(async () => {
// ── Auth guard ─────────────────────────────────────────────────────────────
// Inline login handler on port 8882
const loginForm = document.getElementById('login-form');
if (loginForm) {
loginForm.addEventListener('submit', async e => {
e.preventDefault();
const btn = document.getElementById('l-btn');
const err = document.getElementById('login-err');
btn.disabled = true; btn.textContent = 'Signing in…'; err.style.display = 'none';
const res = await Nova.api('auth', 'login', {
method: 'POST',
body: { username: document.getElementById('l-user').value, password: document.getElementById('l-pass').value }
});
if (res?.success && res.data?.user?.role === 'admin') {
location.reload();
} else {
err.textContent = res?.message || 'Invalid credentials or insufficient role';
err.style.display = '';
btn.disabled = false; btn.textContent = 'Sign In to Admin';
}
});
}
const me = await Nova.api('auth', 'me');
if (!me?.success || me.data.role !== 'admin') {
// Already showing the login form in #auth-check
return;
}
document.getElementById('auth-check').style.display = 'none';
document.getElementById('app').style.display = '';
document.getElementById('user-name').textContent = me.data.username;
document.getElementById('user-avatar').textContent = me.data.username[0].toUpperCase();
// ── Logout ─────────────────────────────────────────────────────────────────
document.getElementById('logout-btn').addEventListener('click', async e => {
e.preventDefault();
await Nova.api('auth', 'logout', { method: 'POST' });
location.href = '/';
});
// ── Page definitions ───────────────────────────────────────────────────────
const pages = {
dashboard,
'server-status': serverStatus,
accounts,
resellers,
packages,
'create-account': createAccount,
'dns-zones': dnsZones,
nameservers,
'web-server': webServer,
'php-manager': phpManager,
'mysql-manager': mysqlManager,
'mail-server': mailServer,
'ftp-server': ftpServer,
'ssl-manager': sslManager,
firewall,
'audit-log': auditLog,
updates,
backups,
settings,
};
Nova.initNav(pages);
await Nova.loadPage('dashboard', pages);
checkUpdates();
// ── Dashboard ──────────────────────────────────────────────────────────────
async function dashboard() {
const [stats, version] = await Promise.all([
Nova.api('system', 'stats'),
Nova.api('system', 'version'),
]);
const s = stats?.data || {};
const v = version?.data || {};
document.getElementById('server-ip').textContent = '';
return `
CPU Usage
${s.cpu?.pct ?? 0}%
Load: ${(s.cpu?.load || [0,0,0]).join(' / ')}
${Nova.progressBar(s.cpu?.pct || 0)}
Memory
${s.ram?.pct ?? 0}%
${Nova.bytes((s.ram?.used_kb||0)*1024)} / ${Nova.bytes((s.ram?.total_kb||0)*1024)}
${Nova.progressBar(s.ram?.pct || 0)}
Disk
${s.disk?.pct ?? 0}%
${Nova.bytes(s.disk?.total - s.disk?.free || 0)} used
${Nova.progressBar(s.disk?.pct || 0)}
Uptime
${s.uptime || '—'}
PHP ${v.php_version || '—'}
${Object.entries(s.services || {}).map(([svc, status]) => `
${Nova.serviceDot(status)} ${svc}
${Nova.badge(status, status === 'active' ? 'green' : 'red')}
Restart
Stop
`).join('')}
Installed ${v.installed_version || '—'}
Branch ${v.git_branch || 'main'}
Commit ${v.git_commit || '—'}${v.git_dirty ? ' dirty ' : ''}
PHP ${v.php_version || '—'}
OS ${v.os || '—'}
Check for Updates
`;
}
// ── Server Status ──────────────────────────────────────────────────────────
async function serverStatus() {
const res = await Nova.api('system', 'stats');
const s = res?.data || {};
return `
CPU
${s.cpu?.pct}% ${Nova.progressBar(s.cpu?.pct||0)}
RAM
${s.ram?.pct}% ${Nova.progressBar(s.ram?.pct||0)}
Disk
${s.disk?.pct}% ${Nova.progressBar(s.disk?.pct||0)}
Load Average
${(s.cpu?.load||[]).join(' / ')}
`;
}
// ── Updates ────────────────────────────────────────────────────────────────
async function updates() {
const [ver, check] = await Promise.all([
Nova.api('system', 'version'),
Nova.api('system', 'check-update'),
]);
const v = ver?.data || {};
const upd = check?.data || {};
const count = upd.updates_available || 0;
return `
Installed Version
${v.installed_version}
Git Commit
${v.git_commit || '—'}
Branch
${v.git_branch || 'main'}
Dirty Working Tree
${v.git_dirty ? Nova.badge('Yes','yellow') : Nova.badge('No','green')}
${count > 0 ? `
${upd.commits?.map(c => `
${c}
`).join('') || 'None'}
Apply Update
` : `
NovaCPX is up to date.
`}
`;
}
// ── Audit Log ──────────────────────────────────────────────────────────────
async function auditLog() {
const res = await Nova.api('system', 'audit-log', { params: { per_page: 50 } });
const rows = res?.data || [];
return `
Time User Action Resource IP
${rows.map(r => `
${Nova.relTime(r.created_at)}
${r.username || '—'}
${r.action}
${r.resource || '—'}
${r.ip_address || '—'}
`).join('')}
`;
}
// ── PHP Manager ────────────────────────────────────────────────────────────
async function phpManager() {
return `
Manage installed PHP versions and global extensions.
${['7.4','8.1','8.2','8.3'].map(v => `
PHP ${v}
${Nova.badge('Active','green')}
Restart FPM
`).join('')}
Global PHP Extensions
Extensions installed across all PHP versions: mbstring, curl, gd, xml, zip, opcache, redis, imagick, pdo, pdo_mysql, pdo_pgsql
`;
}
// ── Settings ───────────────────────────────────────────────────────────────
async function settings() {
return `
`;
}
// ── Stub pages ─────────────────────────────────────────────────────────────
function stubPage(title, desc) {
return `
${desc}
${Nova.badge('Coming Soon','yellow')}
`;
}
function accounts() { return stubPage('All Accounts', 'View and manage all hosting accounts on this server.'); }
function resellers() { return stubPage('Resellers', 'Create and manage reseller accounts with custom packages and resource limits.'); }
function packages() { return stubPage('Packages', 'Define hosting packages with disk, bandwidth, email, FTP, and database limits.'); }
function createAccount() { return stubPage('Create Account', 'Create a new hosting account and assign it a package.'); }
function dnsZones() { return stubPage('DNS Zones', 'View, add, and edit all DNS zones on this nameserver.'); }
function nameservers() { return stubPage('Nameservers', 'Configure primary and secondary nameservers for all hosted domains.'); }
function webServer() { return stubPage('Web Server', 'Manage Apache2 / nginx virtual hosts, modules, and configuration.'); }
function mysqlManager() { return stubPage('MySQL / PostgreSQL', 'Create databases, users, and manage remote access.'); }
function mailServer() { return stubPage('Mail Server', 'Manage Postfix/Dovecot configuration, spam filters, and mail queues.'); }
function ftpServer() { return stubPage('FTP Server', 'Configure ProFTPD, manage FTP accounts and access rules.'); }
function sslManager() { return stubPage('SSL Manager', 'Issue, install, and auto-renew Let\'s Encrypt SSL certificates for all domains.'); }
function firewall() { return stubPage('Firewall / Fail2Ban', 'Manage UFW rules and review Fail2Ban bans.'); }
function backups() { return stubPage('Backups', 'Configure automated backups, restore accounts, and manage backup storage.'); }
// ── Global action helpers ──────────────────────────────────────────────────
window.adminPage = (page) => Nova.loadPage(page, pages);
window.applyUpdate = async () => {
Nova.confirm('Apply all pending updates? The panel may restart.', async () => {
Nova.toast('Applying update…', 'info', 8000);
const res = await Nova.api('system', 'apply-update', { method: 'POST' });
if (res?.data?.updated) {
Nova.toast(`Updated to ${res.data.to_commit}`, 'success');
Nova.loadPage('updates', pages);
} else {
Nova.toast(res?.data?.pull_output || 'Already up to date', 'info');
}
});
};
window.adminServiceAction = async (svc, cmd) => {
const res = await Nova.api('system', 'service', { method: 'POST', body: { service: svc, command: cmd } });
Nova.toast(`${svc}: ${cmd} → ${res?.success ? 'OK' : res?.message}`, res?.success ? 'success' : 'error');
};
window.phpAction = async (ver, cmd) => {
const svc = `php${ver}-fpm`;
await window.adminServiceAction(svc, 'restart');
};
// ── Check for updates badge ────────────────────────────────────────────────
async function checkUpdates() {
const res = await Nova.api('system', 'check-update');
const n = res?.data?.updates_available || 0;
const badge = document.getElementById('update-badge');
if (badge && n > 0) { badge.textContent = n; badge.style.display = ''; }
}
})();