Reaching 100% in the upload progress bar only means the browser finished SENDING the bytes to nginx - it does not mean the server has finished receiving/saving the file. For multi-GB files this gap was invisible, making a genuinely-still-working upload look frozen at 100 percent, leading to a premature reload (confirmed via nginx access log: status 499, client closed request).
nginx-panel.conf: added fastcgi_request_buffering off on the admin panels /api/ location so nginx streams the body straight to PHP-FPM instead of fully buffering it to a temp file first and reading it back (a full extra disk I/O pass for large files); also added client_body_timeout/fastcgi_send_timeout/send_timeout at 1800s alongside the existing fastcgi_read_timeout, since those are separate timeout phases that previously still defaulted to 60s.
admin.js: added an xhr.upload load-complete handler that switches the dialog to a clear "Finishing up on the server..." state once the send phase hits 100%, so large uploads no longer look stuck during the save phase.
nginx-panel.conf: added client_max_body_size 20g at the http level (this nginx instance only serves the NovaCPX panel, isolated from the system nginx), and scoped generous PHP upload limits (upload_max_filesize/post_max_size 20G, execution/input time 1800s) to just the admin panels /api/ location via fastcgi_param PHP_VALUE - this avoids touching the shared php8.3-fpm www pool config used by other sites.
admin.js: rewrote fmSubmitUpload from fetch() (no progress support, and would throw uncaught on a non-JSON error body like an nginx 413 page) to XMLHttpRequest with a real progress bar (percent + bytes transferred) that stays visible for the whole transfer instead of the dialog closing immediately on click.
upload only checked [path], which is parsed from JSON php://input and is always empty for multipart/form-data uploads. It silently fell back to a hardcoded /public_html, which does not exist at filesystem root - breaking uploads for the admin File Manager (baseDir=/) entirely, though it went unnoticed for regular hosting accounts since /public_html happens to be their real default. Now falls back through and as well.
- New File Manager page under Admin > System, built on the existing /api/files endpoint (admin sessions with no linked hosting account get baseDir=/, i.e. full filesystem).
- Fixed two baseDir=/ edge-case bugs in files.php: safe_path() double-slash prefix check, and list actions path stripping via str_replace corrupting every slash.
- Added download and chown actions to files.php.
- Added a server-enforced dangerous-path guard: write/delete/rename/chmod/chown/mkdir/upload/compress/extract targeting /etc,/boot,/root,/sys,/proc,/dev,/run,/lib*,/bin,/sbin,/var/lib,/snap are rejected with 409 unless confirm_dangerous=true is sent - matched by a client-side warning modal requiring a checkbox and typed CONFIRM.
- install.sh: added zip package (required by the existing compress action, was missing).
1. admin.js: dashboard setTimeout was after return (dead code) — restructured
to assign template to const html, run setTimeout, then return html
2. DockerManager.php createStack: replaced SELECT LAST_INSERT_ID() with
db->insert() which already returns lastInsertId correctly for SQLite
3. DockerManager.php setQuota: replaced ON DUPLICATE KEY UPDATE / VALUES()
MySQL syntax with SQLite-compatible ON CONFLICT(user_id) DO UPDATE SET
excluded.col syntax
4. post-restore.sh: PHP helper file now written ONCE at start of step 4
before any call to it (was written AFTER first call, causing silent failure)
5. post-restore.sh: git pull exit code now captured before pipeline (the
while-read loop always exited 0, masking pull failures)
6. uninstall.sh: tar backup now aborts on failure (previously 2>/dev/null
swallowed errors and rm -rf destroyed source unconditionally); also
rm -f → rm -rf for .service.d drop-in directory
- Added history chart to dashboard: fetches stats/server API, renders
24-hour CPU/RAM/Disk chart with Chart.js lazy-loaded
- setTimeout properly INSIDE function before closing brace
- Removed ONLY serverStatus() function body (2521 chars), kept initStatsChart
- pages object redirects server-status → dashboard
- Removed server-status from admin sidebar nav
- All 26 functions intact, backticks balanced, accounts/packages/DNS all kept
- Cleans orphaned DB user records before recreating webacct
- Handles both orphaned-Linux-user and orphaned-DB-user cases
- Pulls latest NovaCPX code + runs migrations
- Bumps PHP-FPM pm.max_children to 20 if still at default 5
- Creates /tmp/_nova_create_webacct.php helper separately (avoids here-doc PHP issues)
- Deploys both index.html and notes.php from web-dashboard repo
- Disables Apache2 if running
- --no-git flag to skip code pull
- Comprehensive logging with ✓/⚠ indicators
- Adminer installed at /adminer.php (MySQL + PostgreSQL)
- db-tools API now detects adminer.php file and returns its URL
- Tool cards: phpMyAdmin, Adminer (MySQL/PG), pgAdmin4
- Open buttons use API-provided URL (adminer.php for Adminer)
- Separate MySQL and PostgreSQL database sections in DB Manager
- PostgreSQL section has direct link to Adminer PG mode
- #42 Docker: already complete (full docker page with all tabs)
Previous merge accidentally deleted 38KB of page functions (accounts, packages,
DNS, etc.) by using wrong boundary. This time only removes the serverStatus()
function body. Dashboard now includes history chart + setTimeout to render it.
All other pages intact.
- Row badge updates to 'stopping…'/'starting…' instantly on click
- Buttons disabled while action runs so no double-clicks
- List stays visible while refreshing after action (no blank flash)
- container-remove changed to POST so body passes through proxies correctly
- removeImage now throws RuntimeException when docker rmi output contains
'Error' or 'conflict' so the API returns success:false with the message
- Added docker/sync-orphans endpoint (admin only) to register existing
Docker containers not tracked in the NovaCPX DB (e.g. after a restore)