ca27ea5522
component.js now creates an AbortController for each page mount, passing it to load(). On unmount, the controller is aborted to cancel in-flight requests that would otherwise mutate unmounted state. Page load functions consistently pass the signal to apiFetch and guard state mutations with abort checks. This eliminates the need for per-page abortController boilerplate and prevents stale errors from appearing on rapid navigation. Users page now guards catch block and loading state cleanup against aborted requests, matching passkeys.js pattern.
56 lines
2.0 KiB
JavaScript
56 lines
2.0 KiB
JavaScript
import { html, PageHeader, Tabs, esc, definePage, renderGuard, modelFetch, getModel } from '/static/hoover/index.js?v=10';
|
|
|
|
const logTabs = [
|
|
{ key: 'journal', label: 'Journal' },
|
|
{ key: 'nginx-access', label: 'Nginx Access' },
|
|
{ key: 'nginx-error', label: 'Nginx Error' },
|
|
{ key: 'dnsmasq', label: 'Dnsmasq' },
|
|
{ key: 'app', label: 'App' },
|
|
];
|
|
|
|
export default definePage({
|
|
init() {
|
|
return {
|
|
logs: getModel('logs'),
|
|
activeTab: 'journal',
|
|
};
|
|
},
|
|
render(state) {
|
|
const logData = state.logs.data;
|
|
const stale = logData?.tab !== state.activeTab;
|
|
const guard = renderGuard(state.logs, 'Logs', 'System and service logs', stale ? undefined : logData?.data);
|
|
if (guard) return guard;
|
|
|
|
const lines = logData.data || [];
|
|
const tab = logTabs.find(t => t.key === state.activeTab) || logTabs[0];
|
|
const lineVnodes = lines.map((line, i) =>
|
|
html`<div class="log-line" key=${i}>${esc(line)}</div>`
|
|
);
|
|
|
|
const tabsBody = Tabs({
|
|
state,
|
|
tabs: logTabs.map(t => t.key),
|
|
formatLabel: (k) => {
|
|
const t = logTabs.find(t => t.key === k);
|
|
return t ? t.label : k.charAt(0).toUpperCase() + k.slice(1);
|
|
},
|
|
onTabClick: (key) => modelFetch('logs', key),
|
|
});
|
|
|
|
return [
|
|
PageHeader({ title: 'Logs', subtitle: 'System and service logs' }),
|
|
html`<div class="card" key="log-card">
|
|
${tabsBody}
|
|
<div class="card-header">
|
|
<span>${tab.label}</span>
|
|
<button class="btn btn-sm btn-outline" style="float:right"
|
|
onClick=${() => modelFetch('logs', state.activeTab)}>\u21BB</button>
|
|
</div>
|
|
<div class="card-body log-body">
|
|
<pre>${lineVnodes}</pre>
|
|
</div>
|
|
</div>`,
|
|
];
|
|
},
|
|
});
|