From 827a4e8297499624d1dfce4b1510f9f468c3e8fd Mon Sep 17 00:00:00 2001 From: NovaCPX Admin Date: Wed, 15 Jul 2026 14:33:36 -0500 Subject: [PATCH] fix: File Manager upload had no size limit for large files and no progress feedback 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. --- deploy/nginx-panel.conf | 9 ++++- panel/assets/js/admin.js | 81 +++++++++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/deploy/nginx-panel.conf b/deploy/nginx-panel.conf index 91e6a36..8a15a42 100644 --- a/deploy/nginx-panel.conf +++ b/deploy/nginx-panel.conf @@ -14,6 +14,9 @@ http { sendfile on; gzip on; + # Large uploads via the admin File Manager (e.g. ISOs, installers) can be multi-GB. + client_max_body_size 20g; + # ── Admin Panel (8882) ───────────────────────────────────────────────────── server { listen 8882 ssl; @@ -43,7 +46,11 @@ http { include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME /srv/novacpx/public/api/index.php; fastcgi_param SERVER_PORT 8882; - fastcgi_read_timeout 300; + fastcgi_read_timeout 1800; + fastcgi_param PHP_VALUE "upload_max_filesize=20G +post_max_size=20G +max_execution_time=1800 +max_input_time=1800"; } } diff --git a/panel/assets/js/admin.js b/panel/assets/js/admin.js index 817b8ef..f6531d3 100644 --- a/panel/assets/js/admin.js +++ b/panel/assets/js/admin.js @@ -4802,21 +4802,84 @@ window.fmSubmitChown = (path) => { }); }; window.fmUpload = () => { - Nova.modal('Upload File', `
`, - ` - `); + Nova.modal('Upload File', ` +
+
+
+ `, + ` + `); }; + +function fmFormatBytes(n) { + if (n >= 1073741824) return (n / 1073741824).toFixed(2) + ' GB'; + if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB'; + if (n >= 1024) return (n / 1024).toFixed(1) + ' KB'; + return n + ' B'; +} + window.fmSubmitUpload = () => { const fileInput = document.getElementById('fm-upfile'); - if (!fileInput?.files[0]) return; - document.querySelector('.modal-overlay')?.remove(); - fmGuard(_fmPath, 'upload a file into', async (confirm_dangerous) => { + const file = fileInput?.files[0]; + if (!file) return; + fmGuard(_fmPath, 'upload a file into', (confirm_dangerous) => { + // Switch the modal from the file-picker to a progress view; keep it open for the whole transfer. + document.getElementById('fm-upload-form').style.display = 'none'; + document.getElementById('fm-upload-progress').style.display = 'block'; + document.getElementById('fm-upload-filename').textContent = file.name; + document.getElementById('fm-upload-bytes').textContent = '0 B / ' + fmFormatBytes(file.size); + document.getElementById('fm-upload-cancel-btn').style.display = 'none'; + const goBtn = document.getElementById('fm-upload-go-btn'); + goBtn.disabled = true; + goBtn.textContent = 'Uploading…'; + const fd = new FormData(); - fd.append('file', fileInput.files[0]); + fd.append('file', file); fd.append('path', _fmPath); fd.append('confirm_dangerous', confirm_dangerous ? '1' : ''); - const res = await fetch(`/api/files/upload?path=${encodeURIComponent(_fmPath)}`, { method: 'POST', credentials: 'include', body: fd }).then(r => r.json()); - if (res?.success) { Nova.toast('Uploaded', 'success'); fmNav(_fmPath); } else Nova.toast(res?.message || 'Upload failed', 'error'); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', `/api/files/upload?path=${encodeURIComponent(_fmPath)}`, true); + xhr.withCredentials = true; + + xhr.upload.addEventListener('progress', (e) => { + if (!e.lengthComputable) return; + const pct = Math.round((e.loaded / e.total) * 100); + const bar = document.getElementById('fm-upload-bar'); + const pctEl = document.getElementById('fm-upload-pct'); + const bytesEl = document.getElementById('fm-upload-bytes'); + if (bar) bar.style.width = pct + '%'; + if (pctEl) pctEl.textContent = pct + '%'; + if (bytesEl) bytesEl.textContent = fmFormatBytes(e.loaded) + ' / ' + fmFormatBytes(e.total); + }); + + xhr.onload = () => { + let res = null; + try { res = JSON.parse(xhr.responseText); } catch (e) { /* non-JSON error page, e.g. nginx 413 */ } + if (xhr.status >= 200 && xhr.status < 300 && res?.success) { + document.querySelector('.modal-overlay')?.remove(); + Nova.toast('Uploaded', 'success'); + fmNav(_fmPath); + } else { + document.querySelector('.modal-overlay')?.remove(); + const msg = res?.message || (xhr.status === 413 ? 'File too large for the server\'s current upload limit' : `Upload failed (HTTP ${xhr.status})`); + Nova.toast(msg, 'error'); + } + }; + xhr.onerror = () => { + document.querySelector('.modal-overlay')?.remove(); + Nova.toast('Upload failed — connection error', 'error'); + }; + xhr.send(fd); }); }; window.fmExtract = (path) => {