All checks were successful
Run Check Script / check (pull_request) Successful in 2m34s
Fake log streaming by re-fetching the podman-logs tail every 2s from the frontend (HTMX `every 2s`) — the pragmatic stand-in until a real TTY/streaming channel (v0.4 #13), per the agreed Ch3 scope. - log_viewer polls `load, every 2s` with `hx-sync="this:drop"` so slow/offline devices can't pile up requests. - Multi-deployment selector: the poll URL is driven by `hx-include` of the `<select>` rather than a baked deployment, so changing the selection is picked up by the next poll instead of polling a stale deployment's URL. Single/no deployment bakes it into the URL as before. - app.js: tail-follow only when the user is already at the bottom, so the 2s poll doesn't yank them down while reading history. - Render tests lock the polling trigger and the selector-driven URL behavior. Deferred: the `LogQuery<T>` Score companion. It's meant to mirror the smoke-test contract, which doesn't exist yet (Ch10) — building it now would be a speculative abstraction at n=1. Revisit when the smoke companion lands.
40 lines
1.5 KiB
JavaScript
40 lines
1.5 KiB
JavaScript
document.body.addEventListener('htmx:configRequest', (event) => {
|
|
event.detail.headers['x-csrf-token'] = '1';
|
|
});
|
|
|
|
// Open a modal dialog swapped into #modal-root. Lives here (not inline)
|
|
// because the production CSP forbids inline scripts/handlers.
|
|
document.body.addEventListener('htmx:afterSwap', (event) => {
|
|
if (!event.target || event.target.id !== 'modal-root') return;
|
|
const dialog = event.target.querySelector('dialog');
|
|
if (!dialog || typeof dialog.showModal !== 'function') return;
|
|
|
|
dialog.showModal();
|
|
// Backdrop click closes; closing clears the root so it can re-open.
|
|
dialog.addEventListener('click', (e) => {
|
|
if (e.target === dialog) dialog.close();
|
|
});
|
|
dialog.addEventListener('close', () => {
|
|
event.target.innerHTML = '';
|
|
});
|
|
});
|
|
|
|
// Log panels re-fetch every 2s. Tail-follow only when the user is already at
|
|
// the bottom, so polling doesn't yank them back down while they read history.
|
|
// Record "was pinned" before the swap; honor it after.
|
|
function logfeed(el) {
|
|
return el && el.classList && el.classList.contains('logfeed') ? el : null;
|
|
}
|
|
document.body.addEventListener('htmx:beforeSwap', (event) => {
|
|
const el = logfeed(event.target);
|
|
if (!el) return;
|
|
el.dataset.pinned =
|
|
el.scrollHeight - el.scrollTop - el.clientHeight < 40 ? '1' : '0';
|
|
});
|
|
document.body.addEventListener('htmx:afterSwap', (event) => {
|
|
const el = logfeed(event.target);
|
|
if (!el) return;
|
|
// Default (first load, no dataset) follows the tail.
|
|
if (el.dataset.pinned !== '0') el.scrollTop = el.scrollHeight;
|
|
});
|