/**
* 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 `
`;
}
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 = `
`;
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 => ``).join('');
document.getElementById('r-recent').innerHTML = accts.length
? `| Username | Domain | Package | Status |
${accts.map(a => `
| ${a.username} | ${a.domain} | ${a.package_name||'—'} |
${Nova.badge(a.status, a.status==='active'?'green':'yellow')} |
`).join('')}
`
: 'No accounts yet.
';
}
async function rAccounts(el) {
el.innerHTML = `
`;
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 = `| Username | Domain | Package | Disk | Status | Actions |
${res.data.accounts.map(a => `
| ${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'
? ``
: ``}
|
`).join('')}
`;
}
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 = ``;
} else {
Nova.toast(res?.message || 'Failed to create account','error');
if (btn) btn.innerHTML = `${res?.message || 'Error'}
`;
}
};
async function rPackages(el) {
el.innerHTML = `
`;
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 = `| Name | Disk | BW | DBs | Emails | Domains | Price | Actions |
${res.data.map(p => `
| ${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'} |
|
`).join('')}
`;
}
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 = `
`;
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 = `| Domain | Account | Records | Actions |
${res.data.map(z => `
| ${z.domain} |
${z.username||'—'} |
${z.record_count||0} |
|
`).join('')}
`;
}
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}`,
`
`);
};
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');
});