diff --git a/static/app.js b/static/app.js
index 63096e9..efce031 100644
--- a/static/app.js
+++ b/static/app.js
@@ -93,7 +93,7 @@ async function refreshMissingBadge() {
// ---- navigation ----
// the view lives in the hash so reload, back button and links keep it
-const VIEWS = ["search", "missing", "imported", "import", "settings"];
+const VIEWS = ["search", "missing", "imported", "import", "activity", "settings"];
function showView() {
const hash = location.hash.slice(1);
@@ -101,6 +101,7 @@ function showView() {
$$("nav button[data-view]").forEach((b) => b.classList.toggle("active", b.dataset.view === name));
$$("main > section").forEach((s) => (s.hidden = s.id !== "view-" + name));
if (name === "missing" || name === "imported") loadRequests(name);
+ if (name === "activity") loadHistory();
if (name === "settings") loadLibraries().catch((err) => toast(err.message, true));
}
@@ -622,6 +623,39 @@ $("#imported-search").addEventListener("input", () => renderRequests("imported")
$("#missing-bulk-delete").addEventListener("click", () => bulkDeleteRequests("missing"));
+// ---- activity ----
+let historyRows = [];
+const HISTORY_STATE = { imported: "done", appended: "done", failed: "failed" };
+
+async function loadHistory() {
+ try {
+ historyRows = await api("/api/history");
+ } catch (err) {
+ toast(err.message, true);
+ }
+ renderHistory();
+}
+
+function renderHistory() {
+ const tbody = $("#history-table tbody");
+ if (!historyRows.length) {
+ tbody.innerHTML = `
| ${t("common.nothingHere")} |
`;
+ return;
+ }
+ const locale = lang === "de" ? "de-DE" : "en-GB";
+ tbody.innerHTML = historyRows
+ .map((h) => {
+ const where = h.message || (h.source && h.dest ? `${h.source} → ${h.dest}` : h.dest || h.source);
+ return `
+ | ${esc(new Date(h.at).toLocaleString(locale))} |
+ ${esc(t("history." + h.event))} |
+ ${esc(h.title)} ${esc(h.media_type)} |
+ ${esc(where)} |
+
`;
+ })
+ .join("");
+}
+
// ---- request detail dialog ----
let detailRequest = null;
@@ -1758,6 +1792,7 @@ function onLangChange() {
renderSearchResults();
renderRequests("missing");
renderRequests("imported");
+ renderHistory();
if (libraries.length) loadLibraries();
const editId = $("#lib-form").dataset.editId;
editId ? startLibraryEdit(parseInt(editId)) : endLibraryEdit();
diff --git a/static/i18n.js b/static/i18n.js
index 5920450..f0755a7 100644
--- a/static/i18n.js
+++ b/static/i18n.js
@@ -46,7 +46,16 @@ const STRINGS = {
"nav.imported": "Importiert",
"nav.import": "Import",
"nav.settings": "Libraries",
+ "nav.activity": "Aktivität",
"nav.langToggle": "Switch to English",
+ "history.time": "Zeit",
+ "history.event": "Ereignis",
+ "history.imported": "importiert",
+ "history.appended": "ergänzt",
+ "history.relocated": "verschoben",
+ "history.retagged": "neu getaggt",
+ "history.deleted": "gelöscht",
+ "history.failed": "fehlgeschlagen",
"search.languageTitle": "Sprache der Ausgabe — filtert Ebook- und Audiobook-Suche",
"search.allLanguages": "Alle Sprachen",
@@ -270,7 +279,16 @@ const STRINGS = {
"nav.imported": "Imported",
"nav.import": "Import",
"nav.settings": "Libraries",
+ "nav.activity": "Activity",
"nav.langToggle": "Auf Deutsch umschalten",
+ "history.time": "Time",
+ "history.event": "Event",
+ "history.imported": "imported",
+ "history.appended": "appended",
+ "history.relocated": "relocated",
+ "history.retagged": "retagged",
+ "history.deleted": "deleted",
+ "history.failed": "failed",
"search.languageTitle": "Edition language — filters the ebook and audiobook search",
"search.allLanguages": "All languages",
diff --git a/static/index.html b/static/index.html
index 7de74ab..43cb977 100644
--- a/static/index.html
+++ b/static/index.html
@@ -20,6 +20,7 @@
+
@@ -269,6 +270,22 @@
+
+