| Account | Domain | Actions |
|---|---|---|
| ${a.username} | -${a.domain} | -- - | -
Loading…
`; } + async function cloudflare() { return `Loading…
`; } + async function twofa() { return `Loading…
`; } + async function nginxProxy() { return `Loading...
`; } + async function sessions() { return `Loading...
`; } // ── Global action helpers ────────────────────────────────────────────────── window.adminPage = (page) => Nova.loadPage(page, pages); @@ -1406,3 +1414,825 @@ ${ips.length ? ` if (badge && total > 0) { badge.textContent = total; badge.style.display = ''; } } })(); +// ── ADDITIONS: appended by features #14-17 ──────────────────────────────── + +// ── WordPress Manager (#14) ──────────────────────────────────────────────── +async function wordpress() { + const [acctRes, wpRes] = await Promise.all([ + Nova.api('accounts','list',{params:{limit:500}}), + Nova.api('wordpress','list'), + ]); + const accts = acctRes?.data?.accounts || []; + const installs = wpRes?.data?.installs || []; + window._adminAcctsWP = accts; + + return ` +WordPress Manager
+ +| Domain | Path | Account | Version | Status | Actions |
|---|---|---|---|---|---|
| ${Nova.escHtml(w.domain)} | +${Nova.escHtml(w.path||'/')} |
+ ${Nova.escHtml(w.username||'—')} | +${w.wp_version ? `${Nova.escHtml(w.wp_version)}` : '—'} |
+ ${Nova.badge(w.status||'active', w.status==='active'?'green':w.status==='updating'?'yellow':'red')} | ++ + + + + ${!w.staging_of ? `` : `staging`} + + | +
wp-cli will be downloaded automatically if not installed. This may take 1-2 minutes.
`, + ` + `); +}; + +window.wpSubmitInstall = async () => { + const btn = document.getElementById('wp-install-btn'); + if (btn) { btn.disabled = true; btn.textContent = 'Installing…'; } + Nova.toast('Installing WordPress — this may take 1-2 minutes…', 'info', 90000); + const res = await Nova.api('wordpress','install',{method:'POST',body:{ + account_id: +document.getElementById('wp-acct')?.value, + domain: document.getElementById('wp-domain')?.value, + path: document.getElementById('wp-path')?.value || '/', + site_title: document.getElementById('wp-title')?.value, + admin_user: document.getElementById('wp-admin')?.value, + admin_pass: document.getElementById('wp-adminpass')?.value, + admin_email:document.getElementById('wp-email')?.value, + }}); + document.querySelector('.modal-overlay')?.remove(); + if (res?.success) { Nova.toast('WordPress installed!','success'); adminPage('wordpress'); } + else Nova.toast(res?.message || 'Install failed','error'); +}; + +window.wpUpdate = async (id, type) => { + const action = type === 'core' ? 'update-core' : type === 'plugins' ? 'update-plugins' : 'update-themes'; + Nova.toast(`Updating ${type}…`,'info',15000); + const r = await Nova.api('wordpress', action, {method:'POST',body:{install_id:id}}); + Nova.toast(r?.message || (r?.success ? 'Updated' : 'Failed'), r?.success ? 'success' : 'error'); + if (r?.success) adminPage('wordpress'); +}; + +window.wpInfo = async (id, domain) => { + Nova.toast('Loading info…','info',5000); + const r = await Nova.api('wordpress','info',{params:{install_id:id}}); + if (!r?.success) { Nova.toast(r?.message,'error'); return; } + const d = r.data || {}; + const plugins = (d.plugins||[]).map(p => `Core Version
${Nova.escHtml(d.version||'—')}
Site URL
${Nova.escHtml(d.siteurl||'—')}
Plugins (${(d.plugins||[]).length})
+ ${plugins ? `| Plugin | Version | Status |
|---|
None
'} +Themes (${(d.themes||[]).length})
+ ${themes ? `| Theme | Version | Status |
|---|
None
'}`); +}; + +window.wpCloneStaging = (id, domain) => { + Nova.confirm(`Clone ${domain} to a staging environment? This copies all files and the database.`, async () => { + Nova.toast('Cloning to staging…','info',30000); + const r = await Nova.api('wordpress','clone-staging',{method:'POST',body:{install_id:id}}); + Nova.toast(r?.message || (r?.success ? 'Staging created' : 'Failed'), r?.success ? 'success' : 'error'); + if (r?.success) adminPage('wordpress'); + }); +}; + +window.wpDelete = (id, domain) => { + Nova.confirm(`DELETE WordPress install on ${domain}? This removes all files AND drops the database. IRREVERSIBLE.`, async () => { + const r = await Nova.api('wordpress','delete',{method:'POST',body:{install_id:id}}); + Nova.toast(r?.message || (r?.success ? 'Deleted' : 'Failed'), r?.success ? 'success' : 'error'); + if (r?.success) adminPage('wordpress'); + }, true); +}; + +// ── Backup Manager — full implementation (#15) ───────────────────────────── +async function backupsFull() { + const [acctRes, bkRes] = await Promise.all([ + Nova.api('accounts','list',{params:{limit:500}}), + Nova.api('backup','list'), + ]); + const accts = acctRes?.data?.accounts || []; + const backupList = bkRes?.data?.backups || []; + const diskUsed = bkRes?.data?.disk_used || 0; + window._adminAcctsBK = accts; + + return ` +Backup Manager
+Set per-account backup schedules. Cron runs backups automatically based on the configured frequency.
+| Account | Type | Size | Status | Storage | Created | Actions |
|---|---|---|---|---|---|---|
| ${Nova.escHtml(b.username||b.account_id||'—')} | +${Nova.badge(b.type,'default')} | +${Nova.bytes(b.size||0)} | +${Nova.badge(b.status, b.status==='complete'?'green':b.status==='failed'?'red':'yellow')} | +${b.remote_path ? Nova.badge('remote','blue') : Nova.badge('local','muted')} | +${Nova.relTime(b.created_at)} | ++ ${b.status==='complete'?`Download`:''} + + + | +
Cloudflare Integration
+Manage Cloudflare API credentials and DNS sync per account.
+Select an account to configure or view its Cloudflare API key.
+Key on file: ${Nova.escHtml(c.cf_api_key)}
${Nova.escHtml(r?.message||'Failed to load zones')}
`; return; } + if (!zones.length) { body.innerHTML='No zones found for these credentials.
'; return; } + body.innerHTML = ` +| Zone | Status | Plan | Actions |
|---|---|---|---|
${Nova.escHtml(z.name)}${Nova.escHtml(z.id)} |
+ ${Nova.badge(z.status,z.status==='active'?'green':'yellow')} | +${Nova.escHtml(z.plan?.name||'—')} | ++ + + + + | +
No records.
' : ` +| Name | Type | Value | Proxy |
|---|---|---|---|
| ${Nova.escHtml(rec.name)} | +${Nova.badge(rec.type,'default')} | + ++ + | +
Two-Factor Authentication
+View 2FA status for all users. Force-disable for account recovery.
+| Username | Role | 2FA Status | Actions | |
|---|---|---|---|---|
| ${Nova.escHtml(u.username)} | +${Nova.escHtml(u.email||'—')} | +${Nova.badge(u.role||'user','default')} | ++ — + | ++ + + | +
Nginx Proxy Manager
+Nginx Not Installed
+Install Nginx on this VM to use it as a reverse proxy in front of Apache, or use a separate proxy VM (see Setup Guide).
+Service Controls
+Proxy Hosts
+ ${hosts.length} total +| Domain | +Upstream | +SSL | +Status | +Actions | +
|---|---|---|---|---|
| ${Nova.escHtml(h.domain)} | +${Nova.escHtml(h.upstream)} | +${h.ssl_enabled ? Nova.badge('SSL','green') : Nova.badge('HTTP','muted')} | +${h.enabled ? Nova.badge('Active','green') : Nova.badge('Disabled','red')} | ++ + + + | +
Option A — Local (Nginx on this VM)
+Install Nginx alongside Apache on this VM. Nginx listens on ports 80/443 and forwards to Apache. Best for SSL termination and caching.
+-
+
- Click Install Nginx Locally on the main Nginx Proxy page +
- Move Apache to port 8080: edit
/etc/apache2/ports.conf→ changeListen 80toListen 8080
+ - Update upstream in all proxy hosts to
http://127.0.0.1:8080
+ - Click Sync Accounts to auto-populate proxy hosts from your hosted accounts +
- Click Reload Config to apply changes +
Option B — Remote Proxy VM (Recommended for production)
+Run a dedicated Nginx proxy VM in front of this NovaCPX VM. Traffic flows: Internet → FortiGate → Nginx Proxy VM → NovaCPX VM (Apache).
+-
+
- Create a new VM on Proxmox (Ubuntu 22.04, 1 vCPU, 1GB RAM) +
- Run the setup script below on the new VM as root +
- Point FortiGate VIPs to the proxy VM IP (ports 80/443) +
- Set the proxy upstream to this NovaCPX VM IP (
http://10.48.200.110:80)
+ - Add proxy hosts for each domain from your NovaCPX admin panel +
Automated Setup Script
+Run this on the target VM (local or remote) as root:
+Or download and review before running:
++ cat proxy-setup.sh # review
+ bash proxy-setup.sh +
Integration with VirtualHost Manager
+When proxy mode is active, NovaCPX automatically:
+-
+
- Creates a proxy host entry for every new account +
- Removes the proxy host when an account is terminated +
- Re-generates Nginx config on every account change +
- Uses account SSL certs automatically if SSL is enabled on the proxy host +
Session Manager
+Active Sessions
${rows.length} total| User | Role | IP | Browser | Created | Expires | Actions | +
|---|---|---|---|---|---|---|
| ${Nova.escHtml(s.username)} ${Nova.escHtml(s.email)} |
+ ${Nova.badge(s.role, s.role==='admin'?'red':s.role==='reseller'?'yellow':'blue')} | +${Nova.escHtml(s.ip_address)} | + +${fmt(s.created_at)} | +${fmt(s.expires_at)} | ++ + + |
Page Not Found
+The page you're looking for doesn't exist or has been moved.
+ + + Go Back + +Internal Server Error
+Something went wrong on our end. The issue has been logged. Please try again in a moment.
+ + + Go Back + +