You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmftools/bookscraper/static/js/dashboard.js

201 lines
5.7 KiB

/* =======================================================================
File: static/js/dashboard.js
Purpose:
Dashboard interactions:
- select book
- refresh logs
- refresh progress
NOTE:
$ / $$ / autoScroll komen uit helpers.js
======================================================================= */
/* ---------------------------------------------------------
Simple fetch wrapper
--------------------------------------------------------- */
async function apiGet(url) {
try {
const r = await fetch(url);
if (!r.ok) return null;
return await r.json();
} catch (e) {
console.error("API GET failed:", url, e);
return null;
}
}
/* ---------------------------------------------------------
Dashboard state
--------------------------------------------------------- */
let ACTIVE_BOOK = null;
let REFRESH_INTERVAL = null;
console.log(">>> dashboard.js LOADED");
/* ---------------------------------------------------------
DOM Ready → setup
--------------------------------------------------------- */
document.addEventListener("DOMContentLoaded", () => {
console.log(">>> dashboard.js DOMContentLoaded");
// =====================================================
// GLOBAL FALLBACK POLLING — ALWAYS FETCH LOGS
// Runs when no books exist or no selection has been made
// =====================================================
console.log(">>> dashboard.js: enabling global fallback polling");
setInterval(() => {
// if no active book → fetch global logs
if (!ACTIVE_BOOK) {
refreshBook(null); // triggers /logs
}
}, 2000);
const items = $$(".book-list-item");
console.log(">>> dashboard.js found book-list items:", items.length);
// Geen boeken → geen polling starten
// if (!items || items.length === 0) {
// console.log(">>> dashboard.js: geen boeken aanwezig, polling uit.");
// return;
// }
// Book selection listener
items.forEach((item) => {
item.addEventListener("click", () => {
console.log(">>> dashboard.js: user clicked book:", item.dataset.bookId);
selectBook(item.dataset.bookId);
});
});
// Auto-select first book
if (!ACTIVE_BOOK && items[0]) {
console.log(
">>> dashboard.js: auto-select first book:",
items[0].dataset.bookId
);
selectBook(items[0].dataset.bookId);
}
});
/* ---------------------------------------------------------
Select a book (updates UI + starts polling)
--------------------------------------------------------- */
function selectBook(bookId) {
console.log(">>> selectBook(", bookId, ")");
ACTIVE_BOOK = bookId;
// Highlight
$$(".book-list-item").forEach((el) => {
el.classList.toggle("active", el.dataset.bookId === bookId);
});
// Reset previous polling
if (REFRESH_INTERVAL) {
console.log(">>> dashboard.js: clearing previous polling interval");
clearInterval(REFRESH_INTERVAL);
}
// Start new polling
console.log(">>> dashboard.js: starting polling for bookId =", bookId);
REFRESH_INTERVAL = setInterval(() => {
refreshBook(ACTIVE_BOOK);
}, 2000);
// Immediate refresh
refreshBook(ACTIVE_BOOK);
}
setInterval(refreshActiveBooks, 2000);
async function refreshActiveBooks() {
const books = await apiGet("/api/books");
if (!books) return;
const container = $("#book-list");
if (!container) return;
// Herbouw de lijst
container.innerHTML = "";
books.forEach((b) => {
const div = document.createElement("div");
div.className = "book-list-item";
div.dataset.bookId = b.book_id;
div.innerHTML = `
<div class="book-title">${b.title}</div>
<div class="book-status">${b.status}</div>
<div class="book-progress">
${b.download_done}/${b.download_total} downloaded,
${b.audio_done}/${b.audio_total} audio
</div>
<button class="abort-btn" onclick="abortBook('${b.book_id}')">Abort</button>
`;
// Event listener opnieuw koppelen
div.addEventListener("click", () => selectBook(b.book_id));
container.appendChild(div);
});
// Als ACTIVE_BOOK nog niet bekend → auto-selecteer eerste boek
if (!ACTIVE_BOOK && books.length > 0) {
selectBook(books[0].book_id);
}
}
/* ---------------------------------------------------------
Fetch logs + progress from API
--------------------------------------------------------- */
async function refreshBook(bookId) {
console.log(">>> refreshBook(", bookId, ")");
// 1) Als er GEEN bookId is → haal alleen globale logs op
if (!bookId) {
console.log(">>> refreshBook: no active book → fetch /logs");
const data = await apiGet("/logs");
if (data && data.logs) updateLogs(data.logs);
return; // klaar
}
// 2) Als er WEL een boek is → haal book status + logs op
const state = await apiGet(`/api/book/${bookId}/status`);
const logs = await apiGet(`/api/book/${bookId}/logs`);
console.log(">>> refreshBook state =", state);
console.log(">>> refreshBook logs =", logs);
if (state) updateProgressBars(state);
if (logs) updateLogs(logs);
}
/* ---------------------------------------------------------
Update LOG VIEW panel
--------------------------------------------------------- */
function updateLogs(logList) {
const output = $("#log-output");
if (!output) {
console.warn(">>> updateLogs: no #log-output element found");
return;
}
output.innerHTML = "";
logList.forEach((line) => logAppend(line));
autoScroll(output);
}
function abortBook(book_id) {
if (!confirm(`Abort tasks for book ${book_id}?`)) return;
fetch(`/abort/${book_id}`, { method: "POST" })
.then((r) => r.json())
.then((data) => {
console.log("Abort:", data);
})
.catch((err) => {
console.error("Abort failed:", err);
});
}