Add queue export/import functionality to web UI

This commit is contained in:
Simon Cordt
2025-09-28 13:49:07 +02:00
parent e2e25ab6cb
commit 346bfe4cff
3 changed files with 97 additions and 1 deletions
+16
View File
@@ -22,6 +22,22 @@
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ order }),
}),
exportQueue: () =>
fetch("/api/pqueue/export", { method: "GET" }).then((r) => r.json()),
importQueue: (fileOrJson) => {
try {
if (fileOrJson instanceof File || fileOrJson instanceof Blob) {
const form = new FormData();
form.append("file", fileOrJson, fileOrJson.name || "queue.json");
return fetch("/api/pqueue/import", { method: "POST", body: form }).then((r) => r.json());
}
} catch (err) { /* fall through to JSON */ }
return fetch("/api/pqueue/import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(fileOrJson),
}).then((r) => r.json());
},
setPriority: (prompt_id, priority) =>
fetch("/api/pqueue/priority", {
method: "PATCH",
+63
View File
@@ -52,6 +52,69 @@
} catch (err) { /* noop */ }
},
async exportQueueToFile() {
refreshRefs();
try {
const data = await API.exportQueue();
const text = JSON.stringify(data, null, 2);
const blob = new Blob([text], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const dt = new Date();
const pad = (n) => String(n).padStart(2, '0');
const fname = `pqueue-${dt.getFullYear()}${pad(dt.getMonth()+1)}${pad(dt.getDate())}-${pad(dt.getHours())}${pad(dt.getMinutes())}${pad(dt.getSeconds())}.json`;
a.download = fname;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
setStatusMessage("Queue exported");
} catch (err) {
console.error("pqueue: export failed", err);
state.error = err?.message || "Failed to export queue";
UI.updateToolbarStatus();
}
},
openImportDialog() {
refreshRefs();
try {
let input = document.getElementById('pqueue-import-input');
if (!input) {
input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json,.json';
input.id = 'pqueue-import-input';
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', Events.handleImportSelected, { once: false });
}
input.value = '';
input.click();
} catch (err) { /* noop */ }
},
async handleImportSelected(ev) {
refreshRefs();
try {
const input = ev.currentTarget || ev.target;
const file = input?.files?.[0];
if (!file) return;
const result = await API.importQueue(file);
const cnt = Number(result?.count || (Array.isArray(result?.imported) ? result.imported.length : 0));
setStatusMessage(`Imported ${cnt} item${cnt === 1 ? '' : 's'}`);
await refresh({ force: true });
// Clear selection after import
state.selectedPending.clear();
UI.updateSelectionUI();
} catch (err) {
console.error("pqueue: import failed", err);
state.error = err?.message || "Failed to import queue";
UI.updateToolbarStatus();
}
},
async syncLatestForAsc() {
refreshRefs();
try {
+18 -1
View File
@@ -5,6 +5,7 @@
const API = (window.PQueue && window.PQueue.API) || window.API;
const UI = (window.PQueue && window.PQueue.UI) || (window.PQueue = (window.PQueue || {}), window.PQueue.UI = {}, window.PQueue.UI);
const Format = (window.PQueue && window.PQueue.Format) || window.Format;
const Events = (window.PQueue && window.PQueue.Events) || window.Events;
const setStatusMessage = (window.PQueue && window.PQueue.setStatusMessage) || window.setStatusMessage;
// Metrics card removed; metrics now live within the toolbar summary.
@@ -79,6 +80,22 @@
state.dom.pendingCount = footerCount;
state.dom.pendingUpdated = footerUpdated;
const exportBtn = UI.button({
id: "pqueue-export",
icon: "ti ti-download",
variant: "ghost",
subtle: true,
title: "Export queue",
onClick: () => { try { (Events && Events.exportQueueToFile) ? Events.exportQueueToFile() : null; } catch (err) {} },
});
const importBtn = UI.button({
id: "pqueue-import",
icon: "ti ti-upload",
variant: "ghost",
subtle: true,
title: "Import queue",
onClick: () => { try { (Events && Events.openImportDialog) ? Events.openImportDialog() : null; } catch (err) {} },
});
const deleteSelected = UI.button({
id: "pqueue-delete-selected",
icon: "ti ti-trash",
@@ -91,7 +108,7 @@
const footer = UI.el("div", { class: "pqueue-table__footer" }, [
UI.el("div", { class: "pqueue-table__footer-left" }, [footerCount, footerUpdated]),
UI.el("div", { class: "pqueue-table__footer-right" }, [deleteSelected]),
UI.el("div", { class: "pqueue-table__footer-right" }, [exportBtn, importBtn, deleteSelected]),
]);
// Build collapsible card (mirrors metrics behavior)