/** * NovaCPX Reseller Panel JS */ let _rUser = null; async function initReseller() { const res = await Nova.api('auth', 'me'); if (!res?.success || !['admin','reseller'].includes(res.data?.role)) { document.getElementById('auth-check').innerHTML = renderLogin(); document.getElementById('main-layout').style.display = 'none'; return false; } _rUser = res.data; document.getElementById('user-name').textContent = _rUser.username || 'Reseller'; return true; } function renderLogin() { return `
Reseller Portal · Port 8881
`; } async function doLogin() { const res = await Nova.api('auth', 'login', { method: 'POST', body: { username: document.getElementById('li-user')?.value, password: document.getElementById('li-pass')?.value }}); if (res?.success) { if (res.data?.portal_url && !res.data.portal_url.includes(':8881')) location.href = res.data.portal_url; else location.reload(); } else { const err = document.getElementById('li-err'); if (err) { err.textContent = res?.message || 'Login failed'; err.style.display = 'block'; } } } window.doLogin = doLogin; /* ── Pages ─────────────────────────────────────────────────────────────── */ async function rDashboard(el) { el.innerHTML = `
Loading…
Recent Accounts
Loading…
`; const res = await Nova.api('accounts', 'list', { params:{ limit:5 }}); const accts = res?.data?.accounts || []; document.getElementById('r-stats').innerHTML = [ { label: 'Total Accounts', val: res?.data?.total || 0, icon: 'ni-accounts' }, { label: 'Active', val: accts.filter(a=>a.status==='active').length, icon: 'ni-stats' }, { label: 'Suspended', val: accts.filter(a=>a.status==='suspended').length, icon: 'ni-suspend' }, ].map(s => `
${s.val}
${s.label}
`).join(''); document.getElementById('r-recent').innerHTML = accts.length ? ` ${accts.map(a => ``).join('')}
UsernameDomainPackageStatus
${a.username}${a.domain}${a.package_name||'—'} ${Nova.badge(a.status, a.status==='active'?'green':'yellow')}
` : '
No accounts yet.
'; } async function rAccounts(el) { el.innerHTML = `
Loading…
`; loadRAccounts(); } async function loadRAccounts(search = '') { const el = document.getElementById('r-accounts-list'); if (!el) return; const res = await Nova.api('accounts', 'list', { params: search ? { search } : {}}); if (!res?.success || !res.data.accounts.length) { el.innerHTML = '
No accounts found.
'; return; } el.innerHTML = ` ${res.data.accounts.map(a => ``).join('')}
UsernameDomainPackageDiskStatusActions
${a.username} ${a.domain} ${a.package_name || '—'} ${a.disk_usage_mb || 0} MB ${Nova.badge(a.status, a.status==='active'?'green':a.status==='suspended'?'yellow':'red')} ${a.status === 'active' ? `` : ``}
`; } window.loadRAccounts = loadRAccounts; window.rSearchAccounts = (v) => loadRAccounts(v); window.rSuspend = async (id, user) => { Nova.confirm(`Suspend account ${user}? Their website will show a suspension page.`, async () => { const res = await Nova.api('accounts', 'suspend', { method:'POST', body:{ account_id: id }}); if (res?.success) { Nova.toast('Account suspended','success'); loadRAccounts(); } else Nova.toast(res?.message,'error'); }); }; window.rUnsuspend = async (id, user) => { const res = await Nova.api('accounts', 'unsuspend', { method:'POST', body:{ account_id: id }}); if (res?.success) { Nova.toast('Account unsuspended','success'); loadRAccounts(); } else Nova.toast(res?.message,'error'); }; window.rTerminate = (id, user) => { Nova.confirm(`TERMINATE ${user}? This permanently deletes all files, databases, DNS, and email. THIS CANNOT BE UNDONE.`, async () => { const res = await Nova.api('accounts', 'terminate', { method:'POST', body:{ account_id: id }}); if (res?.success) { Nova.toast('Account terminated','success'); loadRAccounts(); } else Nova.toast(res?.message,'error'); }, true); }; window.rChangePass = (id, user) => { Nova.modal(`Change Password — ${user}`, `
`, ``); }; async function rCreateAccount(el) { el.innerHTML = `
`; Nova.api('packages', 'list').then(res => { const sel = document.getElementById('ca-pkg'); if (sel && res?.success) { sel.innerHTML = res.data.map(p => ``).join(''); } }); } window.submitCreateAccount = async () => { const btn = document.querySelector('#ca-result'); if (btn) btn.textContent = ''; const res = await Nova.api('accounts', 'create', { method:'POST', body:{ username: document.getElementById('ca-user')?.value, password: document.getElementById('ca-pass')?.value, email: document.getElementById('ca-email')?.value, domain: document.getElementById('ca-domain')?.value, package_id: document.getElementById('ca-pkg')?.value, }}); if (res?.success) { Nova.toast('Account created successfully!','success'); if (btn) btn.innerHTML = `
Account created! View accounts →
`; } else { Nova.toast(res?.message || 'Failed to create account','error'); if (btn) btn.innerHTML = `
${res?.message || 'Error'}
`; } }; async function rPackages(el) { el.innerHTML = `
Loading…
`; const res = await Nova.api('packages', 'list'); const plist = document.getElementById('pkg-list'); if (!res?.success || !res.data.length) { plist.innerHTML = '
No packages yet.
'; return; } plist.innerHTML = ` ${res.data.map(p => ``).join('')}
NameDiskBWDBsEmailsDomainsPriceActions
${p.name} ${p.disk_mb > 0 ? p.disk_mb+'MB' : '∞'} ${p.bandwidth_mb > 0 ? p.bandwidth_mb+'MB' : '∞'} ${p.databases || '∞'} ${p.email_accounts || '∞'} ${p.addon_domains || '∞'} ${p.price ? '$'+p.price : 'Free'}
`; } window.rAddPackage = () => showPackageModal(); window.rEditPackage = async (id) => { const res = await Nova.api('packages', 'get', { params:{ id }}); if (res?.success) showPackageModal(res.data); }; function showPackageModal(pkg = null) { const p = pkg || {}; Nova.modal(pkg ? 'Edit Package' : 'Add Package', `
`, ``); } window.submitPackage = async (id) => { const body = { name:document.getElementById('pk-name')?.value, disk_mb:parseInt(document.getElementById('pk-disk')?.value), bandwidth_mb:parseInt(document.getElementById('pk-bw')?.value), databases:parseInt(document.getElementById('pk-db')?.value), email_accounts:parseInt(document.getElementById('pk-email')?.value), addon_domains:parseInt(document.getElementById('pk-adom')?.value), subdomains:parseInt(document.getElementById('pk-sub')?.value), ftp_accounts:parseInt(document.getElementById('pk-ftp')?.value), price:parseFloat(document.getElementById('pk-price')?.value) }; const res = id ? await Nova.api('packages','update',{method:'POST',body:{...body,id}}) : await Nova.api('packages','create',{method:'POST',body}); if (res?.success) { Nova.toast(id ? 'Package updated' : 'Package created','success'); document.querySelector('.modal-overlay')?.remove(); rPackages(document.getElementById('page-content')); } else Nova.toast(res?.message,'error'); }; window.rDeletePackage = (id, name) => { Nova.confirm(`Delete package "${name}"? Cannot delete if accounts are using it.`, async () => { const res = await Nova.api('packages','delete',{method:'POST',body:{id}}); if (res?.success) { Nova.toast('Deleted','success'); rPackages(document.getElementById('page-content')); } else Nova.toast(res?.message,'error'); }, true); }; async function rDNS(el) { el.innerHTML = `
Loading…
`; const res = await Nova.api('dns', 'zones'); const list = document.getElementById('r-dns-list'); if (!res?.success || !res.data.length) { list.innerHTML = '
No DNS zones.
'; return; } list.innerHTML = ` ${res.data.map(z => ``).join('')}
DomainAccountRecordsActions
${z.domain} ${z.username||'—'} ${z.record_count||0}
`; } window.rViewZone = async (zoneId, domain) => { const res = await Nova.api('dns', 'records', { params:{ zone_id: zoneId }}); if (!res?.success) { Nova.toast('Failed to load records','error'); return; } const rows = res.data.map(r => ` ${r.name}${Nova.badge(r.type,'default')}${r.value}${r.ttl} `).join(''); Nova.modal(`DNS Records — ${domain}`, ` ${rows}
NameTypeValueTTL
`); }; window.rAddRecord = (zoneId, domain) => { Nova.modal('Add DNS Record', `
`, ``); }; window.rDeleteRecord = async (id, zoneId, domain) => { Nova.confirm('Delete this DNS record?', async () => { const res = await Nova.api('dns', 'delete-record', { method:'POST', body:{id, zone_id: zoneId }}); if (res?.success) { Nova.toast('Deleted','success'); document.querySelector('.modal-overlay')?.remove(); rViewZone(zoneId, domain); } else Nova.toast(res?.message,'error'); }, true); }; /* ── Nav ────────────────────────────────────────────────────────────────── */ const rNavItems = [ { id:'dashboard', label:'Dashboard', icon:'ni-dashboard' }, { id:'accounts', label:'Accounts', icon:'ni-accounts' }, { id:'createAccount', label:'New Account', icon:'ni-add' }, { id:'packages', label:'Packages', icon:'ni-packages' }, { id:'dns', label:'DNS Zones', icon:'ni-dns' }, ]; const rPages = { dashboard: rDashboard, accounts: rAccounts, createAccount: rCreateAccount, packages: rPackages, dns: rDNS }; let _rActivePage = 'dashboard'; function renderRNav() { const nav = document.getElementById('sidebar-nav'); if (!nav) return; nav.innerHTML = rNavItems.map(n => ` ${n.label} `).join(''); } window.resellerNav = (page) => { _rActivePage = page; renderRNav(); const content = document.getElementById('page-content'); if (!content) return; content.innerHTML = '
Loading…
'; if (rPages[page]) rPages[page](content); }; document.addEventListener('DOMContentLoaded', async () => { const ok = await initReseller(); if (!ok) return; renderRNav(); window.resellerNav('dashboard'); });