feat: dedicated ports per panel tier (8880/8881/8882)

Each panel now has its own dedicated port and is fully self-contained:
- Port 8880: User panel (end-user hosting dashboard)
- Port 8881: Reseller panel (account/package management)
- Port 8882: Admin panel (datacenter/server manager)

Changes:
- install.sh: PORT_USER/PORT_RESELLER/PORT_ADMIN constants; three separate
  nginx/Apache vhosts; UFW opens all three ports; Fail2Ban jail per port;
  credentials file shows all three URLs
- config.ini: stores port_user/port_reseller/port_admin
- Core.php: defines PORT_USER/RESELLER/ADMIN, detects CURRENT_PORTAL from
  SERVER_PORT so the API knows which tier is being accessed
- Auth.php: portalUrl() maps role → correct port for cross-portal redirects
- auth.php endpoint: returns portal_url on login so JS redirects to right port
- index.php login: uses portal_url from API response (no hardcoded paths)
- admin/index.php: inline login form (port 8882 is self-contained, no redirect)
- user/index.php: inline login form (port 8880 self-contained)
- reseller/index.php: new full reseller panel with inline login (port 8881);
  sidebar with accounts, packages, DNS, branding, bandwidth report sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 05:35:48 +00:00
parent e94dc719c8
commit 716d292e77
9 changed files with 467 additions and 55 deletions
+23 -1
View File
@@ -3,9 +3,31 @@
*/
(async () => {
// ── Auth guard ─────────────────────────────────────────────────────────────
// Inline login handler on port 8882
const loginForm = document.getElementById('login-form');
if (loginForm) {
loginForm.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 && res.data?.user?.role === 'admin') {
location.reload();
} else {
err.textContent = res?.message || 'Invalid credentials or insufficient role';
err.style.display = '';
btn.disabled = false; btn.textContent = 'Sign In to Admin';
}
});
}
const me = await Nova.api('auth', 'me');
if (!me?.success || me.data.role !== 'admin') {
location.href = '/?redirect=/admin/';
// Already showing the login form in #auth-check
return;
}
document.getElementById('auth-check').style.display = 'none';