Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot] 8adedb9759 chore: bump version to 1.0.75 [skip ci] 2026-07-15 19:34:25 +00:00
NovaCPX Admin 827a4e8297 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.
2026-07-15 14:33:36 -05:00
3 changed files with 81 additions and 11 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.74 1.0.75
+8 -1
View File
@@ -14,6 +14,9 @@ http {
sendfile on; sendfile on;
gzip 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) ───────────────────────────────────────────────────── # ── Admin Panel (8882) ─────────────────────────────────────────────────────
server { server {
listen 8882 ssl; listen 8882 ssl;
@@ -43,7 +46,11 @@ http {
include /etc/nginx/fastcgi_params; include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /srv/novacpx/public/api/index.php; fastcgi_param SCRIPT_FILENAME /srv/novacpx/public/api/index.php;
fastcgi_param SERVER_PORT 8882; 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";
} }
} }
+72 -9
View File
@@ -4802,21 +4802,84 @@ window.fmSubmitChown = (path) => {
}); });
}; };
window.fmUpload = () => { window.fmUpload = () => {
Nova.modal('Upload File', `<div class="form-group"><label class="form-label">Select File</label><input id="fm-upfile" type="file" class="form-control"></div>`, Nova.modal('Upload File', `
`<button class="btn btn-ghost" onclick="this.closest('.modal-overlay').remove()">Cancel</button> <div id="fm-upload-form">
<button class="btn btn-primary" onclick="fmSubmitUpload()">Upload</button>`); <div class="form-group"><label class="form-label">Select File</label><input id="fm-upfile" type="file" class="form-control"></div>
</div>
<div id="fm-upload-progress" style="display:none">
<div style="display:flex;justify-content:space-between;font-size:.85rem;margin-bottom:.35rem">
<span id="fm-upload-filename"></span>
<span id="fm-upload-pct">0%</span>
</div>
<div style="background:var(--bg);border-radius:4px;height:10px;overflow:hidden">
<div id="fm-upload-bar" style="background:var(--primary,#6366f1);height:100%;width:0%;transition:width .2s"></div>
</div>
<div id="fm-upload-bytes" class="text-muted text-sm" style="margin-top:.35rem"></div>
</div>`,
`<button class="btn btn-ghost" id="fm-upload-cancel-btn" onclick="this.closest('.modal-overlay').remove()">Cancel</button>
<button class="btn btn-primary" id="fm-upload-go-btn" onclick="fmSubmitUpload()">Upload</button>`);
}; };
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 = () => { window.fmSubmitUpload = () => {
const fileInput = document.getElementById('fm-upfile'); const fileInput = document.getElementById('fm-upfile');
if (!fileInput?.files[0]) return; const file = fileInput?.files[0];
document.querySelector('.modal-overlay')?.remove(); if (!file) return;
fmGuard(_fmPath, 'upload a file into', async (confirm_dangerous) => { 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(); const fd = new FormData();
fd.append('file', fileInput.files[0]); fd.append('file', file);
fd.append('path', _fmPath); fd.append('path', _fmPath);
fd.append('confirm_dangerous', confirm_dangerous ? '1' : ''); 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) => { window.fmExtract = (path) => {