diff --git a/templates/index.html b/templates/index.html
index 7c7c8be..8289747 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -843,7 +843,7 @@
fd.append("batch_id", batchId);
try {
- const uploadResp = await uploadFileXHR(fd, (loaded, total) => {
+ const uploadResp = await uploadFileWithRetry(fd, (loaded, total) => {
const filePct = (loaded / total) * 100;
showLive(
`Nalaganje ${i + 1}/${totalFiles}`,
@@ -861,8 +861,12 @@
});
} catch (e) {
console.error(`Failed to upload ${f.name}: ${e.message}`);
- liveFail(`Napaka pri ${f.name}: ${e.message}`);
- // Continue z naslednjimi
+ showLive(
+ `⚠ Preskočil ${i + 1}/${totalFiles}`,
+ `${f.name}: ${e.message} (nadaljujem z naslednjim)`,
+ ((i + 1) / totalFiles) * 100
+ );
+ // Continue z naslednjimi (ne breakaj cel loop)
}
}
@@ -887,6 +891,8 @@
function uploadFileXHR(formData, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
+ // 10 min timeout per file (za velike komade)
+ xhr.timeout = 10 * 60 * 1000;
xhr.upload.onprogress = e => {
if (e.lengthComputable && onProgress) {
onProgress(e.loaded, e.total);
@@ -896,12 +902,32 @@
if (xhr.status === 200) resolve(xhr.responseText);
else reject(new Error(`HTTP ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));
};
- xhr.onerror = () => reject(new Error("Upload error"));
+ xhr.onerror = () => reject(new Error("Network error (offline?)"));
+ xhr.ontimeout = () => reject(new Error("Timeout (10min)"));
xhr.upload.onerror = () => reject(new Error("Upload transfer error"));
+ xhr.upload.ontimeout = () => reject(new Error("Upload timeout"));
xhr.open("POST", "/api/upload");
xhr.send(formData);
});
}
+
+ // Retry helper: če upload faila, poskusi 2x ponovno z malim delay-om
+ async function uploadFileWithRetry(formData, onProgress, maxRetries = 2) {
+ let lastErr = null;
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
+ try {
+ return await uploadFileXHR(formData, onProgress);
+ } catch (e) {
+ lastErr = e;
+ console.warn(`Upload attempt ${attempt + 1} failed: ${e.message}`);
+ if (attempt < maxRetries) {
+ // Eksponentni delay: 2s, 4s
+ await new Promise(r => setTimeout(r, 2000 * (attempt + 1)));
+ }
+ }
+ }
+ throw lastErr;
+ }
// ─── Watch job (SSE) ────────────────────────────
function watchJob(jobId) {