Loading html/admin.html +38 −42 Original line number Diff line number Diff line Loading @@ -622,54 +622,44 @@ document.getElementById('fileImport').addEventListener('change', async (e) => { } const prog = document.getElementById('dbProgress'); const bar = document.getElementById('dbBar'); prog.style.display = 'block'; bar.style.width = '10%'; prog.style.display = 'block'; bar.style.width = '5%'; try { let startOk = false; let startErr = null; let recoveredStatus = null; try { const res = await fetch(API + '/api/import', { // Fire the PUT request but don't await it — it blocks until the // synchronous import finishes. Instead, poll /api/import/status // so the progress bar stays alive. let putDone = false, putOk = false, putErr = null; fetch(API + '/api/import', { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/octet-stream' }, body: file }); }).then(async (res) => { const data = await res.json().catch(() => ({})); if (!res.ok && res.status !== 202) throw new Error(data.error || 'Import failed'); startOk = true; } catch (e) { startErr = e; } if (!startOk) { try { const sr = await fetch(API + '/api/import/status', {headers: {'Authorization': 'Bearer ' + token}}); const sd = await sr.json(); recoveredStatus = sd; if (sd.status === 'running' || sd.status === 'done') startOk = true; if (sd.status === 'failed') throw new Error(sd.error || 'Import failed'); } catch (e) { if (!startErr) startErr = e; } } if (!startOk) throw (startErr || new Error('Import failed')); bar.style.width = '40%'; putDone = true; putOk = res.ok || res.status === 202; if (!putOk) putErr = data.error || 'Import failed'; }).catch((err) => { putDone = true; putErr = err.message; }); // Give the server a moment to register import_running await new Promise(r => setTimeout(r, 500)); bar.style.width = '10%'; showMsg('Import started — processing…', true); // Poll for completion // Poll for completion (up to 10 minutes) let done = false; if (recoveredStatus && recoveredStatus.status === 'done') { for (let i = 0; i < 600 && !done; i++) { await new Promise(r => setTimeout(r, 1000)); // Check if PUT already returned with a result if (putDone && putOk) { done = true; bar.style.width = '100%'; showMsg(recoveredStatus.message || 'Import successful', true); showMsg('Import successful', true); loadStores(); break; } for (let i = 0; i < 600 && !done; i++) { await new Promise(r => setTimeout(r, 1000)); if (putDone && putErr) { throw new Error(putErr); } try { const sr = await fetch(API + '/api/import/status', {headers: {'Authorization': 'Bearer ' + token}}); const sd = await sr.json(); Loading @@ -682,7 +672,13 @@ document.getElementById('fileImport').addEventListener('change', async (e) => { done = true; throw new Error(sd.error || 'Import failed'); } else { bar.style.width = Math.min(40 + i, 90) + '%'; // Show progress if available, otherwise animate slowly if (sd.media_total > 0) { const pct = Math.min(10 + Math.round(90 * sd.media_done / sd.media_total), 95); bar.style.width = pct + '%'; } else { bar.style.width = Math.min(10 + i * 0.15, 90) + '%'; } } } catch (pe) { if (pe.message && pe.message !== 'Import failed') { /* network glitch, retry */ } Loading src/app.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -469,6 +469,7 @@ HttpResponse App::start_import(const std::uint8_t* data, std::size_t len) { if (!ok) import_error_ = "invalid archive format or replication failed"; import_running_.store(false); } if (g_Cluster) g_Cluster->setImportRunning(false); std::cerr << "[import] finished ok=" << ok << "\n"; if (ok) { Loading Loading @@ -499,6 +500,7 @@ HttpResponse App::start_import_file(const std::string& path) { if (!ok) import_error_ = "invalid archive format or replication failed"; import_running_.store(false); } if (g_Cluster) g_Cluster->setImportRunning(false); std::cerr << "[import] finished ok=" << ok << "\n"; if (ok) { Loading Loading
html/admin.html +38 −42 Original line number Diff line number Diff line Loading @@ -622,54 +622,44 @@ document.getElementById('fileImport').addEventListener('change', async (e) => { } const prog = document.getElementById('dbProgress'); const bar = document.getElementById('dbBar'); prog.style.display = 'block'; bar.style.width = '10%'; prog.style.display = 'block'; bar.style.width = '5%'; try { let startOk = false; let startErr = null; let recoveredStatus = null; try { const res = await fetch(API + '/api/import', { // Fire the PUT request but don't await it — it blocks until the // synchronous import finishes. Instead, poll /api/import/status // so the progress bar stays alive. let putDone = false, putOk = false, putErr = null; fetch(API + '/api/import', { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/octet-stream' }, body: file }); }).then(async (res) => { const data = await res.json().catch(() => ({})); if (!res.ok && res.status !== 202) throw new Error(data.error || 'Import failed'); startOk = true; } catch (e) { startErr = e; } if (!startOk) { try { const sr = await fetch(API + '/api/import/status', {headers: {'Authorization': 'Bearer ' + token}}); const sd = await sr.json(); recoveredStatus = sd; if (sd.status === 'running' || sd.status === 'done') startOk = true; if (sd.status === 'failed') throw new Error(sd.error || 'Import failed'); } catch (e) { if (!startErr) startErr = e; } } if (!startOk) throw (startErr || new Error('Import failed')); bar.style.width = '40%'; putDone = true; putOk = res.ok || res.status === 202; if (!putOk) putErr = data.error || 'Import failed'; }).catch((err) => { putDone = true; putErr = err.message; }); // Give the server a moment to register import_running await new Promise(r => setTimeout(r, 500)); bar.style.width = '10%'; showMsg('Import started — processing…', true); // Poll for completion // Poll for completion (up to 10 minutes) let done = false; if (recoveredStatus && recoveredStatus.status === 'done') { for (let i = 0; i < 600 && !done; i++) { await new Promise(r => setTimeout(r, 1000)); // Check if PUT already returned with a result if (putDone && putOk) { done = true; bar.style.width = '100%'; showMsg(recoveredStatus.message || 'Import successful', true); showMsg('Import successful', true); loadStores(); break; } for (let i = 0; i < 600 && !done; i++) { await new Promise(r => setTimeout(r, 1000)); if (putDone && putErr) { throw new Error(putErr); } try { const sr = await fetch(API + '/api/import/status', {headers: {'Authorization': 'Bearer ' + token}}); const sd = await sr.json(); Loading @@ -682,7 +672,13 @@ document.getElementById('fileImport').addEventListener('change', async (e) => { done = true; throw new Error(sd.error || 'Import failed'); } else { bar.style.width = Math.min(40 + i, 90) + '%'; // Show progress if available, otherwise animate slowly if (sd.media_total > 0) { const pct = Math.min(10 + Math.round(90 * sd.media_done / sd.media_total), 95); bar.style.width = pct + '%'; } else { bar.style.width = Math.min(10 + i * 0.15, 90) + '%'; } } } catch (pe) { if (pe.message && pe.message !== 'Import failed') { /* network glitch, retry */ } Loading
src/app.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -469,6 +469,7 @@ HttpResponse App::start_import(const std::uint8_t* data, std::size_t len) { if (!ok) import_error_ = "invalid archive format or replication failed"; import_running_.store(false); } if (g_Cluster) g_Cluster->setImportRunning(false); std::cerr << "[import] finished ok=" << ok << "\n"; if (ok) { Loading Loading @@ -499,6 +500,7 @@ HttpResponse App::start_import_file(const std::string& path) { if (!ok) import_error_ = "invalid archive format or replication failed"; import_running_.store(false); } if (g_Cluster) g_Cluster->setImportRunning(false); std::cerr << "[import] finished ok=" << ok << "\n"; if (ok) { Loading