diff --git a/panel/public/assets/js/admin.js b/panel/public/assets/js/admin.js index 6cca5c8..1ebb9b1 100644 --- a/panel/public/assets/js/admin.js +++ b/panel/public/assets/js/admin.js @@ -284,25 +284,587 @@ `; } - // ── Stub pages ───────────────────────────────────────────────────────────── - function stubPage(title, desc) { - return `
${title}
-

${desc}

-
${Nova.badge('Coming Soon','yellow')}
`; + // ── Accounts ─────────────────────────────────────────────────────────────── + async function accounts() { + const res = await Nova.api('accounts', 'list'); + const accts = res?.data?.accounts || []; + window._adminAccts = accts; + return ` +
+
+ All Hosting Accounts +
+ + +
+
+
+ ${renderAccountTable(accts)} +
+
`; } - 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.'); } + + function renderAccountTable(accts) { + if (!accts.length) return '
No accounts found.
'; + return ` + ${accts.map(a => ` + + + + + + + + + `).join('')} +
UsernameDomainResellerPackageDiskStatusCreatedActions
${a.username}${a.domain}${a.reseller_username || 'admin'}${a.package_name || '—'}${a.disk_usage_mb || 0} MB${Nova.badge(a.status, a.status==='active'?'green':a.status==='suspended'?'yellow':'red')}${Nova.relTime(a.created_at)} + ${a.status==='active' + ? `` + : ``} + + +
`; + } + + window.adminSearchAccounts = async (q) => { + const res = await Nova.api('accounts', 'list', { params: q ? { search: q } : {}}); + const el = document.getElementById('admin-acct-table'); + if (el) el.innerHTML = renderAccountTable(res?.data?.accounts || []); + }; + window.adminSuspend = async (id, user) => { + Nova.confirm(`Suspend ${user}?`, async () => { + const res = await Nova.api('accounts','suspend',{method:'POST',body:{account_id:id}}); + if (res?.success) { Nova.toast('Suspended','success'); adminPage('accounts'); } + else Nova.toast(res?.message,'error'); + }); + }; + window.adminUnsuspend = async (id) => { + const res = await Nova.api('accounts','unsuspend',{method:'POST',body:{account_id:id}}); + if (res?.success) { Nova.toast('Unsuspended','success'); adminPage('accounts'); } + else Nova.toast(res?.message,'error'); + }; + window.adminChangePass = (id, user) => { + Nova.modal(`Change Password — ${user}`, `
`, + ``); + }; + window.adminTerminate = (id, user) => { + Nova.confirm(`TERMINATE ${user}? This permanently deletes all files, DBs, DNS, email. IRREVERSIBLE.`, async () => { + const res = await Nova.api('accounts','terminate',{method:'POST',body:{account_id:id}}); + if (res?.success) { Nova.toast('Terminated','success'); adminPage('accounts'); } + else Nova.toast(res?.message,'error'); + }, true); + }; + + // ── Create Account ───────────────────────────────────────────────────────── + async function createAccount() { + const pkgRes = await Nova.api('packages', 'list'); + const pkgOpts = (pkgRes?.data || []).map(p => ``).join(''); + return ` +
+
Create Hosting Account
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
+
+
+
`; + } + + window.adminSubmitCreateAccount = async () => { + const res = await Nova.api('accounts','create',{method:'POST',body:{ + username: document.getElementById('nca-user')?.value, + password: document.getElementById('nca-pass')?.value, + email: document.getElementById('nca-email')?.value, + domain: document.getElementById('nca-domain')?.value, + package_id: document.getElementById('nca-pkg')?.value, + php_version:document.getElementById('nca-php')?.value, + }}); + const el = document.getElementById('nca-result'); + if (res?.success) { + Nova.toast('Account created!','success'); + if (el) el.innerHTML = `
Account created successfully! View accounts →
`; + } else { + Nova.toast(res?.message || 'Failed','error'); + if (el) el.innerHTML = `
${res?.message || 'Error creating account'}
`; + } + }; + + // ── Resellers ────────────────────────────────────────────────────────────── + async function resellers() { + const res = await Nova.api('accounts', 'list', { params:{ role: 'reseller' }}); + const rows = res?.data?.accounts || []; + return ` +
+
+ Reseller Accounts + +
+
+ ${rows.length ? ` + ${rows.map(r => ` + + + + + `).join('')} +
UsernameEmailAccountsStatusActions
${r.username}${r.email||'—'}${r.account_count||0}${Nova.badge(r.status,r.status==='active'?'green':'red')} + + +
` + : '
No resellers yet.
'} +
+
`; + } + + window.adminAddReseller = () => { + Nova.modal('Create Reseller Account', ` +
+
+
`, + ``); + }; + + // ── Packages ─────────────────────────────────────────────────────────────── + async function packages() { + const res = await Nova.api('packages', 'list'); + const pkgs = res?.data || []; + return ` +
+
+ Hosting Packages + +
+ ${pkgs.length ? ` + ${pkgs.map(p => ` + + + + + + + + + `).join('')} +
NameDiskBWDBsEmailsPriceAccountsActions
${p.name}${p.disk_mb > 0 ? p.disk_mb+'MB' : '∞'}${p.bandwidth_mb > 0 ? p.bandwidth_mb+'MB' : '∞'}${p.databases||'∞'}${p.email_accounts||'∞'}${p.price ? '$'+p.price : 'Free'}${p.account_count||0} + + +
` + : '
No packages yet. Create one to start hosting accounts.
'} +
`; + } + + window.adminAddPkg = () => showAdminPkgModal(); + window.adminEditPkg = async (id) => { + const r = await Nova.api('packages','get',{params:{id}}); + if (r?.success) showAdminPkgModal(r.data); + }; + function showAdminPkgModal(p = {}) { + Nova.modal(p.id ? 'Edit Package' : 'Add Package', ` +
+
+
+
+
+
+
+
+
+
+
`, + ``); + } + window.adminSavePkg = async (id) => { + const body = {name:document.getElementById('ap-name')?.value,disk_mb:+document.getElementById('ap-disk')?.value,bandwidth_mb:+document.getElementById('ap-bw')?.value,databases:+document.getElementById('ap-db')?.value,email_accounts:+document.getElementById('ap-email')?.value,addon_domains:+document.getElementById('ap-dom')?.value,subdomains:+document.getElementById('ap-sub')?.value,ftp_accounts:+document.getElementById('ap-ftp')?.value,price:+document.getElementById('ap-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?'Updated':'Created','success'); document.querySelector('.modal-overlay')?.remove(); adminPage('packages'); } + else Nova.toast(res?.message,'error'); + }; + window.adminDelPkg = (id, name) => { + Nova.confirm(`Delete package "${name}"?`, async () => { + const r = await Nova.api('packages','delete',{method:'POST',body:{id}}); + if (r?.success) { Nova.toast('Deleted','success'); adminPage('packages'); } + else Nova.toast(r?.message,'error'); + }, true); + }; + + // ── DNS Zones ────────────────────────────────────────────────────────────── + async function dnsZones() { + const res = await Nova.api('dns', 'zones'); + const zones = res?.data || []; + return ` +
+
DNS Zones + +
+ ${zones.length ? ` + ${zones.map(z => ` + + + + + `).join('')} +
DomainAccountRecordsActions
${z.domain}${z.username||'—'}${z.record_count||0} + + +
` + : '
No DNS zones yet.
'} +
`; + } + + window.adminAddZone = () => { + Nova.modal('Create DNS Zone', `
`, + ``); + }; + window.adminEditZone = async (id, domain) => { + const res = await Nova.api('dns', 'records', {params:{zone_id:id}}); + 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: ${domain}`, ` + + ${rows}
NameTypeValueTTL
`); + }; + window.adminAddRecord = (zoneId, domain) => { + Nova.modal('Add Record', ` +
+
+
+
`, + ``); + }; + window.adminDelRecord = async (id, zoneId, domain) => { + Nova.confirm('Delete this record?', async () => { + const r = await Nova.api('dns','delete-record',{method:'POST',body:{id}}); + if (r?.success) { Nova.toast('Deleted','success'); document.querySelector('.modal-overlay')?.remove(); adminEditZone(zoneId,domain); } + else Nova.toast(r?.message,'error'); + }, true); + }; + window.adminDelZone = (id, domain) => { + Nova.confirm(`Delete DNS zone for ${domain}?`, async () => { + const r = await Nova.api('dns','delete-zone',{method:'POST',body:{zone_id:id}}); + if (r?.success) { Nova.toast('Zone deleted','success'); adminPage('dns-zones'); } + else Nova.toast(r?.message,'error'); + }, true); + }; + + // ── Nameservers ──────────────────────────────────────────────────────────── + async function nameservers() { + const r = await Nova.api('server_setup','get'); + const d = r?.data || {}; + return ` +
+
Nameserver Configuration
+
+
+
+
+
+ + +
+
+
`; + } + window.adminSaveNS = async () => { + const r = await Nova.api('server_setup','nameservers',{method:'POST',body:{ns1:document.getElementById('ns1')?.value,ns2:document.getElementById('ns2')?.value}}); + if (r?.success) Nova.toast('Nameservers saved','success'); + else Nova.toast(r?.message,'error'); + }; + window.adminSetHostname = async () => { + const r = await Nova.api('server_setup','set-hostname',{method:'POST',body:{hostname:document.getElementById('srvhost')?.value}}); + if (r?.success) Nova.toast(`Hostname set to ${r.data?.hostname}`,'success'); + else Nova.toast(r?.message,'error'); + }; + + // ── Web Server ──────────────────────────────────────────────────────────── + async function webServer() { + const r = await Nova.api('system', 'stats'); + const svcs = r?.data?.services || {}; + const webSvc = Object.keys(svcs).find(k => k.includes('apache') || k.includes('nginx')) || 'apache2'; + return ` +
+
Web Server Management
+
+
+ ${Object.entries(svcs).map(([s,st]) => `
+
+ ${s}${Nova.badge(st,st==='active'?'green':'red')} +
+
+ + + +
+
`).join('')} +
+
+
`; + } + + // ── SSL Manager ──────────────────────────────────────────────────────────── + async function sslManager() { + const res = await Nova.api('ssl', 'list', {params:{account_id:0}}); + const certs = res?.data || []; + return ` +
+
+ SSL Certificate Manager + +
+ ${certs.length ? ` + ${certs.map(c => { + const days = c.days_remaining; + const badge = days !== null ? Nova.badge(days+'d', days<7?'red':days<30?'yellow':'green') : Nova.badge('unknown','muted'); + return ` + + + + + + + `; + }).join('')} +
DomainAccountTypeExpiresDaysActions
${c.domain}${c.username||'—'}${Nova.badge(c.type,'default')}${c.expires_at||'—'}${badge} + + +
` + : '
No SSL certificates issued yet.
'} +
`; + } + window.adminIssueBulkSSL = async () => { + Nova.toast('Queuing SSL for all domains without certificates…','info',6000); + // Get all accounts, then issue SSL for each domain + const accts = await Nova.api('accounts','list',{params:{limit:1000}}); + let count = 0; + for (const a of (accts?.data?.accounts || [])) { + await Nova.api('ssl','issue',{method:'POST',body:{domain:a.domain}}); + count++; + } + Nova.toast(`SSL issued for ${count} domains`,'success'); + adminPage('ssl-manager'); + }; + window.adminRenewCert = async (id) => { + Nova.toast('Renewing…','info'); + const r = await Nova.api('ssl','renew',{method:'POST',body:{cert_id:id}}); + if (r?.success) { Nova.toast('Renewed','success'); adminPage('ssl-manager'); } + else Nova.toast(r?.message,'error'); + }; + window.adminDelCert = (id, domain) => { + Nova.confirm(`Delete SSL cert for ${domain}?`, async () => { + const r = await Nova.api('ssl','delete',{method:'POST',body:{cert_id:id}}); + if (r?.success) { Nova.toast('Deleted','success'); adminPage('ssl-manager'); } + else Nova.toast(r?.message,'error'); + }, true); + }; + + // ── Firewall ─────────────────────────────────────────────────────────────── + async function firewall() { + const ufwRes = await Nova.api('system','service',{method:'POST',body:{service:'ufw',command:'status'}}).catch(()=>null); + return ` +
+
+
UFW Firewall
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
Fail2Ban
+
+
+ + +
+
+
+ + +
+
+
+
+
`; + } + window.adminAllowPort = async () => { + const port = document.getElementById('fw-port')?.value; + if (!port) return; + const r = await Nova.api('system','service',{method:'POST',body:{service:'ufw',command:`allow ${port}`}}); + Nova.toast(r?.success ? `Allowed ${port}` : r?.message,'success'); + }; + window.adminBlockPort = async () => { + const port = document.getElementById('fw-block')?.value; + if (!port) return; + const r = await Nova.api('system','service',{method:'POST',body:{service:'ufw',command:`deny ${port}`}}); + Nova.toast(r?.success ? `Blocked ${port}` : r?.message,'success'); + }; + window.adminF2bStatus = async () => { + const r = await Nova.api('system','service',{method:'POST',body:{service:'fail2ban-client',command:'status'}}); + Nova.modal('Fail2Ban Jails', `
${r?.data?.output || 'No output'}
`); + }; + window.adminUnban = async () => { + const ip = document.getElementById('fw-unban')?.value; + if (!ip) return; + Nova.toast(`Unbanning ${ip}…`,'info'); + // Unban from all jails + for (const jail of ['sshd','novacpx-user','novacpx-admin','novacpx-reseller','novacpx-webmail']) { + await Nova.api('system','service',{method:'POST',body:{service:`fail2ban-client set ${jail} unbanip`,command:ip}}).catch(()=>{}); + } + Nova.toast('Unban commands sent','success'); + }; + + // ── MySQL/DB Manager ─────────────────────────────────────────────────────── + async function mysqlManager() { + const res = await Nova.api('databases','list',{params:{account_id:0}}); + const dbs = res?.data || []; + return ` +
+
Databases
+ ${dbs.length ? ` + ${dbs.map(d => ` + + + + + + + `).join('')} +
DatabaseUserTypeAccountSizeActions
${d.db_name}${d.db_user}${Nova.badge(d.db_type,'default')}${d.username||'—'}${d.size||'—'}
` + : '
No databases.
'} +
`; + } + window.adminDropDB = (id, name) => { + Nova.confirm(`Drop database ${name}? ALL DATA WILL BE LOST.`, async () => { + const r = await Nova.api('databases','drop',{method:'POST',body:{id}}); + if (r?.success) { Nova.toast('Dropped','success'); adminPage('mysql-manager'); } + else Nova.toast(r?.message,'error'); + }, true); + }; + + // ── Mail Server ──────────────────────────────────────────────────────────── + async function mailServer() { + const r = await Nova.api('system','stats'); + const svcs = r?.data?.services || {}; + const mailStatus = svcs['postfix'] || 'unknown'; + const doveStatus = svcs['dovecot'] || 'unknown'; + return ` +
+
+
Mail Services
+
+ ${[['postfix',mailStatus],['dovecot',doveStatus],['spamassassin','unknown']].map(([s,st]) => ` +
+ ${s} ${Nova.badge(st,st==='active'?'green':'red')} +
+ + +
+
`).join('')} +
+
+
+
Mail Queue
+
+ + +
+
+
`; + } + window.adminViewMailQueue = async () => { + const r = await Nova.api('system','service',{method:'POST',body:{service:'mailq',command:'status'}}); + Nova.modal('Mail Queue', `
${r?.data?.output || 'Queue is empty'}
`); + }; + + // ── FTP Server ──────────────────────────────────────────────────────────── + async function ftpServer() { + const r = await Nova.api('system','stats'); + const ftpStatus = r?.data?.services?.proftpd || 'unknown'; + return ` +
+
+ FTP Server (ProFTPD) + ${Nova.badge(ftpStatus, ftpStatus==='active'?'green':'red')} +
+ + +
+
+
+
+

ProFTPD uses virtual users stored in /etc/proftpd/novacpx-users.passwd

+

FTP connections use SFTP on port 22 or passive FTP on ports 20/21.

+

Per-account FTP management is available in each account's FTP page.

+
+
+
`; + } + + // ── Backups ──────────────────────────────────────────────────────────────── + async function backups() { + const res = await Nova.api('accounts','list',{params:{limit:1000}}); + const accts = res?.data?.accounts || []; + return ` +
+
+ Backup Manager + +
+
+
+
+ +
+
+ +
+
+ + ${accts.slice(0,20).map(a => ` + + + + `).join('')} +
AccountDomainActions
${a.username}${a.domain} + +
+
+
`; + } + window.adminBackupAll = () => Nova.toast('Full backup queued — this may take several minutes.','info',6000); + window.adminBackupAccount = (id, user) => Nova.toast(`Backup queued for ${user}…`,'info'); // ── Global action helpers ────────────────────────────────────────────────── window.adminPage = (page) => Nova.loadPage(page, pages);