diff --git a/panel/lib/AccountManager.php b/panel/lib/AccountManager.php index 28acbde..da3fc87 100644 --- a/panel/lib/AccountManager.php +++ b/panel/lib/AccountManager.php @@ -33,8 +33,18 @@ class AccountManager { self::shell("sudo chmod 750 {$homeDir}"); self::shell("sudo chmod 775 {$docRoot}"); - // Default index page (write as root via sudo tee) - $html = "

Welcome to {$domain}

Hosted by NovaCPX

"; + // Default index page — use custom template from settings if set, else built-in + $customTpl = null; + try { + $db2 = DB::getInstance(); + $tplRow = $db2->fetchOne("SELECT value FROM settings WHERE key='default_index_template'"); + $customTpl = $tplRow ? trim($tplRow['value']) : null; + } catch (Throwable $e) {} + + $html = $customTpl + ? str_replace(['{domain}', '{username}'], [$domain, $username], $customTpl) + : "\n\n\n\nWelcome to {$domain}\n\n\n
\n
{$domain}
\n

Your website is ready. Upload your files to get started.

\nHosted by NovaCPX\n
"; + self::shell("sudo tee " . escapeshellarg("{$docRoot}/index.html") . " > /dev/null << 'HTMLEOF'\n{$html}\nHTMLEOF"); // Wrap all DB writes in a transaction so partial failures leave no orphans diff --git a/panel/public/assets/js/admin.js b/panel/public/assets/js/admin.js index da4fad7..26a84a2 100644 --- a/panel/public/assets/js/admin.js +++ b/panel/public/assets/js/admin.js @@ -4534,7 +4534,21 @@ async function serverOptions() {
-`; + + + +
+
Default Index Page Template
+
+

+ HTML shown when a new hosting account is created. Use {domain} and {username} as placeholders. Leave blank to use the built-in styled template. +

+ + + +
+
`; } window.soSave = (key, inputId, label) => { @@ -4605,6 +4619,12 @@ window.soSaveNS = async () => { Nova.toast('Nameservers saved', 'success'); }; +window.soSaveIndexTemplate = async () => { + const tpl = document.getElementById('so-index-tpl')?.value || ''; + const res = await Nova.api('system','save-option',{method:'POST',body:{key:'default_index_template',value:tpl}}); + Nova.toast(res?.success ? 'Default template saved' : 'Save failed', res?.success?'success':'error'); +}; + window.soCheckNS = async () => { const tc = document.getElementById('so-ns-results'); if (!tc) return; diff --git a/panel/public/assets/js/user.js b/panel/public/assets/js/user.js index ae0d9c2..fe84fd8 100644 --- a/panel/public/assets/js/user.js +++ b/panel/public/assets/js/user.js @@ -970,6 +970,8 @@ const navGroups = [ svg: '' }, ]}, { label: 'Account', items: [ + { id: 'account-settings', label: 'Settings', + svg: '' }, { id: 'change-password', label: 'Change Password', svg: '' }, ]}, @@ -1010,6 +1012,7 @@ window.userNav = (page) => { const allItems = navGroups.flatMap(g => g.items); // Extra pages not in nav groups const extraPages = { + 'account-settings': accountSettings, 'subdomains': userSubdomains, 'parked-domains': userParked, }; @@ -1393,3 +1396,122 @@ window.submitParked = async () => { if (res?.success) { Nova.toast(res.message,'success'); document.querySelector('.modal-overlay')?.remove(); loadParkedList(); } else Nova.toast(res?.message||'Failed','error'); }; + +// ══ #38 ACCOUNT SETTINGS PAGE ═════════════════════════════════════════════ +async function accountSettings(el) { + Nova.loadPage('account-settings', 'Account Settings', ` +
+
Loading…
+
`); + + const [usageRes, pkgRes, acctRes] = await Promise.all([ + Nova.api('accounts', 'usage'), + Nova.api('packages', 'my'), + Nova.api('accounts', 'me'), + ]); + + const u = usageRes?.data || {}; + const p = pkgRes?.data || {}; + const a = acctRes?.data || {}; + const el2 = document.getElementById('acct-settings-grid'); + if (!el2) return; + + el2.innerHTML = ` + +
+

Account Information

+
+ ${[ + ['Username', a.username||'—'], + ['Primary Domain', a.domain||'—'], + ['Package', p.name||'Default'], + ['Status', a.status ? Nova.badge(a.status, a.status==='active'?'green':'yellow') : '—'], + ['PHP Version', a.php_version||'8.3'], + ['Created', (a.created_at||'').split('T')[0]||'—'], + ].map(([k,v])=>`
+
${k}
+
${v}
+
`).join('')} +
+
+ + +
+

Resource Usage

+ ${[ + ['Disk', u.disk_used_mb||0, u.disk_limit_mb, 'MB'], + ['Email', u.email_count||0, u.email_limit, 'accounts'], + ['Databases', u.db_count||0, u.db_limit, 'databases'], + ['FTP', u.ftp_count||0, u.ftp_limit, 'accounts'], + ['Domains', u.domain_count||0, u.domain_limit, 'domains'], + ].map(([name,used,limit,unit])=>{ + const pct = limit>0 ? Math.min(100,Math.round(used/limit*100)) : 0; + const col = pct>90?'var(--red)':pct>70?'var(--yellow)':'var(--primary)'; + return `
+
+ ${name} + ${used}${limit>0?` / ${limit} ${unit}`:` ${unit}`} +
+ ${limit>0?`
+
+
`:''} +
`; + }).join('')} +
+ + +
+

PHP Configuration

+
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ + +
+

Security & Access

+
+ + + +
+
+ `; +} +window.accountSettings = accountSettings; + +window.saveAccountPHP = async () => { + const body = { + php_version: document.getElementById('as-php-ver')?.value, + memory_limit: document.getElementById('as-mem')?.value, + upload_max_filesize: document.getElementById('as-upload')?.value, + post_max_size: document.getElementById('as-upload')?.value, + max_execution_time: parseInt(document.getElementById('as-exec')?.value), + }; + Nova.loading('Saving…'); + const res = await Nova.api('php', 'update-config', { method: 'POST', body }); + Nova.loadingDone(); + if (res?.success) Nova.toast('PHP settings saved', 'success'); + else Nova.toast(res?.message || 'Failed', 'error'); +};