download taken zichtbaar gemaakt
This commit is contained in:
Binary file not shown.
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)
|
||||||
|
|||||||
+151
-10
@@ -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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadHistoryForSettings() {
|
function renderTaskItems(items) {
|
||||||
const elements = settingsElements();
|
const elements = settingsElements();
|
||||||
elements.logsError.textContent = "";
|
elements.tasksList.innerHTML = "";
|
||||||
elements.logsList.innerHTML = '<div class="popup-meta">Loading...</div>';
|
if (!Array.isArray(items) || items.length === 0) {
|
||||||
try {
|
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");
|
const data = await apiRequest("GET", "/api/history");
|
||||||
renderHistoryItems(data.items || []);
|
renderHistoryItems(data.items || []);
|
||||||
settingsState.logsLoaded = true;
|
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 = '<div class="popup-meta">Loading...</div>';
|
||||||
|
elements.logsList.innerHTML = '<div class="popup-meta">Loading...</div>';
|
||||||
|
try {
|
||||||
|
await Promise.all([loadTasksForSettings(), loadHistoryForSettings()]);
|
||||||
} 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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user