From 5265d6458c29fe6da2ba204340eed549cb2cd69a Mon Sep 17 00:00:00 2001 From: kodi Date: Sat, 14 Mar 2026 15:57:45 +0100 Subject: [PATCH] download taken zichtbaar gemaakt --- webui/backend/data/tasks.db | Bin 229376 -> 233472 bytes .../test_ui_smoke_golden.cpython-313.pyc | Bin 44294 -> 45496 bytes .../tests/golden/test_ui_smoke_golden.py | 13 ++ webui/html/app.js | 159 +++++++++++++++++- webui/html/index.html | 3 + 5 files changed, 166 insertions(+), 9 deletions(-) diff --git a/webui/backend/data/tasks.db b/webui/backend/data/tasks.db index 96f9121f614fe8797d373553a2132e53072c8d7a..7e5cf427f1abf71c65a720a78b61ae8070d09005 100644 GIT binary patch delta 1670 zcma)6ONbmr81AZmOdNY2y9p*^5|r%21g)9wuBz^?m1QTw3SzPo(2+2DnCj_nBksnH zCME}Ar(4m3AR9)~mw*v+5IiJ7WiSNUvj_3D5aLAbP*hhq0HsZ8}5f44gIJYg|CIBCj z>yU6a^i7K*+hT!e_X+ZB%R?k^nBxSt8H5a!(@y4bhxrsE!{Z?$zHK08k`{7phx*)i z2{kZ4X(#PIa0lEwGI|y~A*nYtUA?VMY7MQbolbUC;m_T_s&EoQ_>~yKb(nncG#p*R zWD2tez$KYN%Mfx6j7;*pDaz^F0M>mGb0A`he~bYLo-zbd=|d(`qIIa;2c?K5zy?5F zA@xlL<`T6HpFEiT9{dWfgJsCogUUbfi|i-Ls`9B4K}R{Rlodt3C0~`_%l@5sZD{BZ zPUb$$y_$O}SJKzIm)r203}9ma6sCr$ORjI@z;%#gxi%s!43UdN2N{k{LmGr8B@|fc zY(3%;${ZVoEF_4~R)DzA86ueY%xQTcZMA@zcD5|L@k6R3=6BMiHN}+%X8f@ za0xKdPTw9kS~h1kGQ-q*!?lphu!97(X>*sGxaEOLS1A4bM|nh=PBKk-zb2dDnlyd> z@J<>p#+6*XRElcPMzuq;l!|ct?}DA$s-1j0Cyyn~th_5TJO73koIa7fl$G}+E4Lx< z7(e0qdIWmT^u?lAi{FaN`FiP*s20scGm=E-%?)y%^O}-eFEhvZlz7Z*eZ{+e}v9`n|MukuU$Sx0n-mhN4rcP|uI( zf9O<1WC6=Pj&Tau!&lzf=uo_wdv&nz9~i;Mn(&$pgFZXt;^2G!yNvDLG

Bku z#oiE+eLXnI7lM$#G9UPr7han`(`l|wq@EY|YzWz=r*rB#F(OSaWHzAx#6XB61H_R= zT#e0M3<}ZM`b35~CYv!Rs6DFAFTbH4^T_>0Eo_6f&uXy5=&{()>$_9S3aMqooX*5J V3m;Baa&oz{!Mlx3TyukJ{{kBYwu}G( delta 265 zcmV+k0rviYpbmh54v-rK@&Et;IFTSf0rIh6q@M^951ar1001|W44@>lF%WMLgP*Ur zpRWOBKn_;`>;M5k1$YHc1tbLfmlW^;gqND|0U8Vi^99NUp#t9mwgQc}yzl{70Ri)q zF%a*k1oHt+1aJW?0GAd(0^kk?bOu@mKn5lT3!muP?j zFcJ-v4QmZT4H*pZ3Nrxc3JM9~x1@jq&IbYEvoR3e0=H0$0<_cv4g@Wi2Vesovm)?9 P0kc35#{#!4WCPP1p(ay2 diff --git a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc index 011e571e409780a8990de095a411616d1a39e14c..263301528919462222ea23a863c920e45f7268a7 100644 GIT binary patch delta 1287 zcmZvbe`phD7{~J_O?u8IX-B=LnQhPfXc9GTZDN0{rnXj7)1)?Qdz*Bw>zTXO!+6)_ zE^0%;KL#C6M79TYf`b-T6%YmO(XkfLscv{98q!X6K10pFBCph2l|#lt%*&3W&0;}YA0E)A<8~Up z>8XbcBPWfBuER!;A41+*qV7vClDMA6)scD#Wk^ITurcC?FEZGL)t@rhHBzsg9qmBG z{`maVT>?Nco62fAnG#4wrt}Zo^)OpQ)B$)k*X|q~MiWQyJ=ANJT+p2J z6XmF>xGTyrQ>lezFYRDmI8DNf+#b6dkB0Fm=steX`uF!^EOVA{N(=D6S6vFs7Nwjj z$`Ugx7tV3&Bt8`rDyJ5e40l?{hmBJbDTOL7QJNJBN97ArUgmhiJS0o1a8WglO%>xm z?J}YynB|sAxgbcqP?)@gJETj=QdZ9A@h=h98pQjQE0&WbMP+zS<(LEmnQP5sldpdz4@k2O~Qi}4|J`%>EtFZjr~B?TytJ_KB3tsZ@r^a(=fK;(GJ}n z-vP0^b&mMjtWG81w>yp5z4wwj6*KKm>E3deP93SFiSl`!N>b8$S>HMDbT!;?+G(gg!tsuCMugHSfWKIOV|rcj+eBUyj!wn za=nzuWN)cT7JY``*^}Q$wKLAyTq5nvz4@ZrCuYV?n~!MiV4vJ+C^311F5BjnMo$>! z-s(%`sk0~`4XQ9F#W&>qMpSULTmWefW$ZN@HJorYy9H}Hh8mw ze+460$C?1J%FP!7mhf)2NoZhXl%2dVNtIC!Ov-~v1u&@yCY8XXGMH2Wld51+4NR(o zNevJ=c~7FiLPrj6_!l*O(VzMf;ZYbMi$0%iHy-;>Atslw(rVTxc3z!My#c@{CrK?*nyN z14%_;0idXjDHA046&TVPG;B9prhR4EY?ZT;WApyf9u@ZEK*{1dkUJi*E7%lO6oE(C;K;RwIM!PC#%$$LReYlYht4&%ueR!B_N zoX0;|d=Bqqj=B7k+2;sM)>_CvxpO)H=I5(?SUK{USk1pDvAS)x-r&tBR>#ih$~c4L Vx`gIM3C%AIVD1N}$#XW!004$^(dGaE diff --git a/webui/backend/tests/golden/test_ui_smoke_golden.py b/webui/backend/tests/golden/test_ui_smoke_golden.py index aa9b09d..ae633a0 100644 --- a/webui/backend/tests/golden/test_ui_smoke_golden.py +++ b/webui/backend/tests/golden/test_ui_smoke_golden.py @@ -151,6 +151,7 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('id="settings-download-scan-timeout"', body) self.assertIn('id="settings-download-symlink-policy"', body) self.assertIn("ZIP download limits are shown for reference and cannot be changed here.", body) + self.assertIn('id="settings-tasks-list"', body) self.assertIn('id="settings-logs-list"', body) self.assertIn('id="viewer-content"', body) self.assertIn('id="editor-modal"', body) @@ -237,6 +238,13 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('function archiveTaskCountText(task)', app_js) self.assertIn('function archiveTaskCurrentItemText(task)', app_js) self.assertIn('function archiveTaskProgressPercent(task)', app_js) + self.assertIn('function formatTaskStatusLabel(task)', app_js) + self.assertIn('function inferDownloadTaskContext(task)', app_js) + self.assertIn('function formatTaskLine(task)', app_js) + self.assertIn('function renderTaskItems(items)', app_js) + self.assertIn('async function loadTasksForSettings()', app_js) + self.assertIn('async function loadLogsAndTasksForSettings()', app_js) + self.assertIn('function scheduleSettingsLogsPolling()', app_js) self.assertIn('function openZipDownloadModal(selectedItems)', app_js) self.assertIn('function openSingleFileDownloadModal(selectedItem)', app_js) self.assertIn('function markZipDownloadReady(fileName)', app_js) @@ -285,6 +293,11 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('`/api/tasks/${encodeURIComponent(taskId)}`', app_js) self.assertIn('`/api/files/download/archive/${encodeURIComponent(taskId)}`', app_js) self.assertIn('`/api/files/download/archive/${encodeURIComponent(taskId)}/cancel`', app_js) + self.assertIn('const data = await apiRequest("GET", "/api/tasks");', app_js) + self.assertIn('return "Ready for download";', app_js) + self.assertIn('return "Multi-item ZIP";', app_js) + self.assertIn('details.push(`Current: ${task.current_item}`);', app_js) + self.assertIn('details.push(`${task.done_items}/${task.total_items} items`);', app_js) self.assertIn('function applyContextMenuSelection()', app_js) self.assertIn('function startContextMenuOpen()', app_js) self.assertIn('function startContextMenuEdit()', app_js) diff --git a/webui/html/app.js b/webui/html/app.js index 554a774..6d2d0e6 100644 --- a/webui/html/app.js +++ b/webui/html/app.js @@ -101,6 +101,8 @@ let folderUploadPickerInput = null; let settingsState = { activeTab: "general", logsLoaded: false, + tasksLoaded: false, + logsPollTimer: null, showThumbnails: false, preferredStartupPathLeft: null, preferredStartupPathRight: null, @@ -903,6 +905,7 @@ function settingsElements() { downloadScanTimeout: document.getElementById("settings-download-scan-timeout"), downloadSymlinkPolicy: document.getElementById("settings-download-symlink-policy"), logsPanel: document.getElementById("settings-logs-panel"), + tasksList: document.getElementById("settings-tasks-list"), logsList: document.getElementById("settings-logs-list"), logsError: document.getElementById("settings-logs-error"), }; @@ -3726,6 +3729,68 @@ function formatHistoryLine(item) { }; } +function formatTaskStatusLabel(task) { + if (task.operation === "download") { + switch (task.status) { + case "requested": + return "Requested"; + case "preparing": + return "Preparing"; + case "ready": + return "Ready for download"; + case "failed": + return "Failed"; + case "cancelled": + return "Cancelled"; + default: + return task.status; + } + } + switch (task.status) { + case "queued": + return "Queued"; + case "running": + return "Running"; + case "completed": + return "Completed"; + case "failed": + return "Failed"; + default: + return task.status; + } +} + +function inferDownloadTaskContext(task) { + if (task.operation !== "download") { + return null; + } + if (typeof task.destination === "string" && /^kodidownload-\d{8}-\d{6}\.zip$/.test(task.destination)) { + return "Multi-item ZIP"; + } + return "Directory ZIP"; +} + +function formatTaskLine(task) { + const when = formatModified(task.finished_at || task.created_at || ""); + const details = []; + const downloadContext = inferDownloadTaskContext(task); + if (downloadContext) { + details.push(downloadContext); + } + if (typeof task.done_items === "number" && typeof task.total_items === "number") { + details.push(`${task.done_items}/${task.total_items} items`); + } + if (task.current_item) { + details.push(`Current: ${task.current_item}`); + } + return { + title: `${task.operation} · ${formatTaskStatusLabel(task)}`, + path: task.destination ? `${task.destination} · ${task.source}` : task.source || "-", + meta: [when, ...details].filter(Boolean).join(" · "), + error: task.status === "failed" ? (task.error_message || task.error_code || "") : "", + }; +} + function renderHistoryItems(items) { const elements = settingsElements(); elements.logsList.innerHTML = ""; @@ -3760,20 +3825,84 @@ function renderHistoryItems(items) { } } +function renderTaskItems(items) { + const elements = settingsElements(); + elements.tasksList.innerHTML = ""; + if (!Array.isArray(items) || items.length === 0) { + const empty = document.createElement("div"); + empty.className = "popup-meta"; + empty.textContent = "No tasks yet."; + elements.tasksList.append(empty); + return; + } + for (const task of items) { + const line = formatTaskLine(task); + const row = document.createElement("div"); + row.className = `settings-log-item status-${task.status}`; + const title = document.createElement("div"); + title.className = "settings-log-title"; + title.textContent = line.title; + const path = document.createElement("div"); + path.className = "settings-log-path"; + path.textContent = line.path; + const meta = document.createElement("div"); + meta.className = "settings-log-meta"; + meta.textContent = line.meta; + row.append(title, path, meta); + if (line.error) { + const error = document.createElement("div"); + error.className = "settings-log-error"; + error.textContent = line.error; + row.append(error); + } + elements.tasksList.append(row); + } +} + async function loadHistoryForSettings() { + const data = await apiRequest("GET", "/api/history"); + renderHistoryItems(data.items || []); + settingsState.logsLoaded = true; +} + +async function loadTasksForSettings() { + const data = await apiRequest("GET", "/api/tasks"); + renderTaskItems(data.items || []); + settingsState.tasksLoaded = true; +} + +async function loadLogsAndTasksForSettings() { const elements = settingsElements(); elements.logsError.textContent = ""; + elements.tasksList.innerHTML = '

'; elements.logsList.innerHTML = ''; try { - const data = await apiRequest("GET", "/api/history"); - renderHistoryItems(data.items || []); - settingsState.logsLoaded = true; + await Promise.all([loadTasksForSettings(), loadHistoryForSettings()]); } catch (err) { + elements.tasksList.innerHTML = ""; elements.logsList.innerHTML = ""; elements.logsError.textContent = err.message; } } +function stopSettingsLogsPolling() { + if (settingsState.logsPollTimer) { + window.clearTimeout(settingsState.logsPollTimer); + settingsState.logsPollTimer = null; + } +} + +function scheduleSettingsLogsPolling() { + stopSettingsLogsPolling(); + if (settingsState.activeTab !== "logs" || settingsElements().overlay.classList.contains("hidden")) { + return; + } + settingsState.logsPollTimer = window.setTimeout(async () => { + await loadLogsAndTasksForSettings(); + scheduleSettingsLogsPolling(); + }, 1500); +} + async function handleShowThumbnailsChange(event) { const input = event.target; try { @@ -3814,6 +3943,7 @@ async function handleInterfaceSave() { } function closeSettings() { + stopSettingsLogsPolling(); settingsElements().overlay.classList.add("hidden"); } @@ -3823,7 +3953,10 @@ async function openSettings(tab = "general") { elements.generalError.textContent = ""; setSettingsTab(tab); if (settingsState.activeTab === "logs") { - await loadHistoryForSettings(); + await loadLogsAndTasksForSettings(); + scheduleSettingsLogsPolling(); + } else { + stopSettingsLogsPolling(); } (settingsState.activeTab === "logs" ? elements.logsTab @@ -4520,12 +4653,20 @@ function setupEvents() { const settings = settingsElements(); settings.closeButton.onclick = closeSettings; - settings.generalTab.onclick = () => setSettingsTab("general"); - settings.interfaceTab.onclick = () => setSettingsTab("interface"); - settings.downloadsTab.onclick = () => setSettingsTab("downloads"); + settings.generalTab.onclick = () => { + stopSettingsLogsPolling(); + setSettingsTab("general"); + }; + settings.interfaceTab.onclick = () => { + stopSettingsLogsPolling(); + setSettingsTab("interface"); + }; + settings.downloadsTab.onclick = () => { + stopSettingsLogsPolling(); + setSettingsTab("downloads"); + }; settings.logsTab.onclick = async () => { - setSettingsTab("logs"); - await loadHistoryForSettings(); + await openSettings("logs"); }; settings.showThumbnailsInput.onchange = handleShowThumbnailsChange; settings.generalSaveButton.onclick = handlePreferredStartupPathSave; diff --git a/webui/html/index.html b/webui/html/index.html index d4e4627..504dd5e 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -227,6 +227,9 @@