Add complete user and reseller panel JS — all pages fully implemented

User panel (user.js): dashboard with usage rings, domains+SSL, email accounts+forwarders, databases, FTP, SSL manager, PHP switcher, cron jobs, file manager (edit/upload/chmod), stats
Reseller panel (reseller.js): dashboard, accounts list+search+suspend/terminate, create account form, packages CRUD, DNS zones editor
Both panels: dynamic sidebar nav using nova-icons.svg sprite, inline auth guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 06:08:32 +00:00
parent e3b166803a
commit 870ec062f0
4 changed files with 1140 additions and 115 deletions
+3 -112
View File
@@ -12,7 +12,7 @@
</head>
<body>
<div class="panel-layout" id="app" style="display:none">
<div class="panel-layout" id="main-layout" style="display:none">
<aside class="sidebar" id="sidebar">
<div class="sidebar-brand">
<svg class="logo-icon" viewBox="0 0 40 40" fill="none">
@@ -26,65 +26,7 @@
</svg>
<span class="logo-text">Nova<strong>CPX</strong> <small style="font-size:.65rem;color:var(--text-muted)">Reseller</small></span>
</div>
<nav>
<div class="sidebar-section">
<div class="sidebar-section-label">Overview</div>
<a href="#" class="sidebar-link active" data-page="dashboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg> Dashboard
</a>
<a href="#" class="sidebar-link" data-page="resource-usage">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg> Resource Usage
</a>
</div>
<div class="sidebar-section">
<div class="sidebar-section-label">Accounts</div>
<a href="#" class="sidebar-link" data-page="accounts">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg> My Accounts
</a>
<a href="#" class="sidebar-link" data-page="create-account">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg> Create Account
</a>
<a href="#" class="sidebar-link" data-page="suspended">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg> Suspended
</a>
</div>
<div class="sidebar-section">
<div class="sidebar-section-label">Plans</div>
<a href="#" class="sidebar-link" data-page="packages">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg> My Packages
</a>
<a href="#" class="sidebar-link" data-page="create-package">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> Create Package
</a>
</div>
<div class="sidebar-section">
<div class="sidebar-section-label">DNS & Domains</div>
<a href="#" class="sidebar-link" data-page="dns">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> DNS Zones
</a>
<a href="#" class="sidebar-link" data-page="nameservers">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg> Nameservers
</a>
</div>
<div class="sidebar-section">
<div class="sidebar-section-label">Branding</div>
<a href="#" class="sidebar-link" data-page="branding">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 3"/></svg> White Label
</a>
<a href="#" class="sidebar-link" data-page="emails">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> Email Templates
</a>
</div>
<div class="sidebar-section">
<div class="sidebar-section-label">Reports</div>
<a href="#" class="sidebar-link" data-page="bandwidth">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg> Bandwidth Report
</a>
<a href="#" class="sidebar-link" data-page="logs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/></svg> Account Logs
</a>
</div>
</nav>
<nav id="sidebar-nav"></nav>
<div class="sidebar-user">
<div class="sidebar-user-info">
<div class="avatar" id="user-avatar">R</div>
@@ -134,58 +76,7 @@
</div>
<script src="/assets/js/nova.js"></script>
<script>
(async () => {
// Inline login on port 8881
document.getElementById('login-form').addEventListener('submit', async e => {
e.preventDefault();
const btn = document.getElementById('l-btn');
const err = document.getElementById('login-err');
btn.disabled = true; btn.textContent = 'Signing in…'; err.style.display = 'none';
const res = await Nova.api('auth', 'login', {
method: 'POST',
body: { username: document.getElementById('l-user').value, password: document.getElementById('l-pass').value }
});
if (res?.success && ['reseller','admin'].includes(res.data?.user?.role)) {
location.reload();
} else {
err.textContent = res?.message || 'Invalid credentials or insufficient role';
err.style.display = '';
btn.disabled = false; btn.textContent = 'Sign In to Reseller Panel';
}
});
<script src="/assets/js/reseller.js"></script>
const me = await Nova.api('auth', 'me');
if (!me?.success || !['reseller','admin'].includes(me.data.role)) { return; }
document.getElementById('auth-check').style.display = 'none';
document.getElementById('app').style.display = '';
document.getElementById('user-name').textContent = me.data.username;
document.getElementById('user-avatar').textContent = me.data.username[0].toUpperCase();
document.getElementById('logout-btn').addEventListener('click', async e => {
e.preventDefault();
await Nova.api('auth', 'logout', { method: 'POST' });
location.reload();
});
// Simple dashboard
document.getElementById('page-content').innerHTML = `
<div class="stats-grid">
<div class="stat-card"><div class="stat-label">Total Accounts</div><div class="stat-value stat-blue">0</div><div class="stat-sub">Active hosting accounts</div></div>
<div class="stat-card"><div class="stat-label">Disk Used</div><div class="stat-value">0 GB</div><div class="stat-sub">of allocated quota</div></div>
<div class="stat-card"><div class="stat-label">Bandwidth</div><div class="stat-value">0 GB</div><div class="stat-sub">this month</div></div>
<div class="stat-card"><div class="stat-label">Packages</div><div class="stat-value stat-green">0</div><div class="stat-sub">Active plans</div></div>
</div>
<div class="card">
<div class="card-header"><span class="card-title">Quick Actions</span></div>
<div class="card-body flex gap-2">
<button class="btn btn-primary">Create Account</button>
<button class="btn btn-ghost">Create Package</button>
<button class="btn btn-ghost">View DNS Zones</button>
</div>
</div>`;
})();
</script>
</body>
</html>