- Notifier.php: CyberMail API sender with 4 trigger types (account
created, suspended, disk quota warning, SSL expiry)
- Reads cybermail_api_key / notify_from_* / notify_admin_email from
settings table
- accounts.php: fires Notifier on create (welcome + admin alert) and
suspend (user + admin alert)
- system.php: notify-settings GET, save-notify-settings POST,
test-notify POST (with API key masking)
- bin/notify-checks.php: daily cron for disk ≥85% and SSL ≤14 days
(flag-based dedup in settings table)
- admin panel: Notifications page with form + trigger reference table;
sidebar link added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#26 Mobile responsive:
- Hamburger button (SVG) in topbar for all three panels (admin/user/reseller)
- Sidebar overlay div for click-outside-to-close on mobile
- nova.js: DOMContentLoaded toggle handler with overlay and auto-close on nav click
- nova.css: sidebar-overlay, page-header, panel/panel-header, table, btn-success/warning/danger/secondary/xs,
badge-muted; mobile media query shows toggle, fixes stats-grid/modal/panel-header layout
#27 Custom error pages:
- /errors/404.php and /errors/500.php with NovaCPX dark theme matching panel design
- Apache ErrorDocument 400/401/403/404/500/503 for ports 8880/8881/8882 with Alias /errors
#28 API rate limiting:
- api_rate_limits table (migration 004) with per-IP per-bucket counters
- api/index.php: 10 req/min for auth endpoint, 120 req/min for all others
- Returns X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers
- Returns 429 Too Many Requests when exceeded; rate limit failure is non-fatal
#29 Session Manager:
- sessions.php endpoint: list/revoke/revoke-user/revoke-all
- Admin panel Sessions page: table of active sessions with user, role, IP, browser, timestamps
- Revoke single session, revoke all for user, revoke all sessions (self-evicts)
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>