// ── 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 `
| 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||'—')}
| Plugin | Version | Status |
|---|
None
'}| 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 `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`:''} |
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')} |
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')} | — |