download taken zichtbaar gemaakt

This commit is contained in:
kodi
2026-03-14 15:57:45 +01:00
parent e85e51d64a
commit 5265d6458c
5 changed files with 166 additions and 9 deletions
Binary file not shown.
@@ -151,6 +151,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('id="settings-download-scan-timeout"', body) self.assertIn('id="settings-download-scan-timeout"', body)
self.assertIn('id="settings-download-symlink-policy"', 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("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="settings-logs-list"', body)
self.assertIn('id="viewer-content"', body) self.assertIn('id="viewer-content"', body)
self.assertIn('id="editor-modal"', 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 archiveTaskCountText(task)', app_js)
self.assertIn('function archiveTaskCurrentItemText(task)', app_js) self.assertIn('function archiveTaskCurrentItemText(task)', app_js)
self.assertIn('function archiveTaskProgressPercent(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 openZipDownloadModal(selectedItems)', app_js)
self.assertIn('function openSingleFileDownloadModal(selectedItem)', app_js) self.assertIn('function openSingleFileDownloadModal(selectedItem)', app_js)
self.assertIn('function markZipDownloadReady(fileName)', 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/tasks/${encodeURIComponent(taskId)}`', app_js)
self.assertIn('`/api/files/download/archive/${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('`/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 applyContextMenuSelection()', app_js)
self.assertIn('function startContextMenuOpen()', app_js) self.assertIn('function startContextMenuOpen()', app_js)
self.assertIn('function startContextMenuEdit()', app_js) self.assertIn('function startContextMenuEdit()', app_js)
+150 -9
View File
@@ -101,6 +101,8 @@ let folderUploadPickerInput = null;
let settingsState = { let settingsState = {
activeTab: "general", activeTab: "general",
logsLoaded: false, logsLoaded: false,
tasksLoaded: false,
logsPollTimer: null,
showThumbnails: false, showThumbnails: false,
preferredStartupPathLeft: null, preferredStartupPathLeft: null,
preferredStartupPathRight: null, preferredStartupPathRight: null,
@@ -903,6 +905,7 @@ function settingsElements() {
downloadScanTimeout: document.getElementById("settings-download-scan-timeout"), downloadScanTimeout: document.getElementById("settings-download-scan-timeout"),
downloadSymlinkPolicy: document.getElementById("settings-download-symlink-policy"), downloadSymlinkPolicy: document.getElementById("settings-download-symlink-policy"),
logsPanel: document.getElementById("settings-logs-panel"), logsPanel: document.getElementById("settings-logs-panel"),
tasksList: document.getElementById("settings-tasks-list"),
logsList: document.getElementById("settings-logs-list"), logsList: document.getElementById("settings-logs-list"),
logsError: document.getElementById("settings-logs-error"), 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) { function renderHistoryItems(items) {
const elements = settingsElements(); const elements = settingsElements();
elements.logsList.innerHTML = ""; 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() { 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(); const elements = settingsElements();
elements.logsError.textContent = ""; elements.logsError.textContent = "";
elements.tasksList.innerHTML = '<div class="popup-meta">Loading...</div>';
elements.logsList.innerHTML = '<div class="popup-meta">Loading...</div>'; elements.logsList.innerHTML = '<div class="popup-meta">Loading...</div>';
try { try {
const data = await apiRequest("GET", "/api/history"); await Promise.all([loadTasksForSettings(), loadHistoryForSettings()]);
renderHistoryItems(data.items || []);
settingsState.logsLoaded = true;
} catch (err) { } catch (err) {
elements.tasksList.innerHTML = "";
elements.logsList.innerHTML = ""; elements.logsList.innerHTML = "";
elements.logsError.textContent = err.message; 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) { async function handleShowThumbnailsChange(event) {
const input = event.target; const input = event.target;
try { try {
@@ -3814,6 +3943,7 @@ async function handleInterfaceSave() {
} }
function closeSettings() { function closeSettings() {
stopSettingsLogsPolling();
settingsElements().overlay.classList.add("hidden"); settingsElements().overlay.classList.add("hidden");
} }
@@ -3823,7 +3953,10 @@ async function openSettings(tab = "general") {
elements.generalError.textContent = ""; elements.generalError.textContent = "";
setSettingsTab(tab); setSettingsTab(tab);
if (settingsState.activeTab === "logs") { if (settingsState.activeTab === "logs") {
await loadHistoryForSettings(); await loadLogsAndTasksForSettings();
scheduleSettingsLogsPolling();
} else {
stopSettingsLogsPolling();
} }
(settingsState.activeTab === "logs" (settingsState.activeTab === "logs"
? elements.logsTab ? elements.logsTab
@@ -4520,12 +4653,20 @@ function setupEvents() {
const settings = settingsElements(); const settings = settingsElements();
settings.closeButton.onclick = closeSettings; settings.closeButton.onclick = closeSettings;
settings.generalTab.onclick = () => setSettingsTab("general"); settings.generalTab.onclick = () => {
settings.interfaceTab.onclick = () => setSettingsTab("interface"); stopSettingsLogsPolling();
settings.downloadsTab.onclick = () => setSettingsTab("downloads"); setSettingsTab("general");
};
settings.interfaceTab.onclick = () => {
stopSettingsLogsPolling();
setSettingsTab("interface");
};
settings.downloadsTab.onclick = () => {
stopSettingsLogsPolling();
setSettingsTab("downloads");
};
settings.logsTab.onclick = async () => { settings.logsTab.onclick = async () => {
setSettingsTab("logs"); await openSettings("logs");
await loadHistoryForSettings();
}; };
settings.showThumbnailsInput.onchange = handleShowThumbnailsChange; settings.showThumbnailsInput.onchange = handleShowThumbnailsChange;
settings.generalSaveButton.onclick = handlePreferredStartupPathSave; settings.generalSaveButton.onclick = handlePreferredStartupPathSave;
+3
View File
@@ -227,6 +227,9 @@
</div> </div>
</section> </section>
<section id="settings-logs-panel" class="settings-panel hidden" role="tabpanel" aria-labelledby="settings-logs-tab"> <section id="settings-logs-panel" class="settings-panel hidden" role="tabpanel" aria-labelledby="settings-logs-tab">
<div class="settings-placeholder-title">Tasks</div>
<div id="settings-tasks-list" class="settings-log-list"></div>
<div class="settings-placeholder-title">History</div>
<div id="settings-logs-error" class="error"></div> <div id="settings-logs-error" class="error"></div>
<div id="settings-logs-list" class="settings-log-list"></div> <div id="settings-logs-list" class="settings-log-list"></div>
</section> </section>