88 lines
3.6 KiB
JavaScript
88 lines
3.6 KiB
JavaScript
import { h, PageHeader, Badge, StatusDot, Empty, Card, esc, enc, att_esc, $val, apiFetch, toast, dismissToast, openModal, closeModal, formModal, definePage, hComp, connect, reactive, requestUpdate, Link, createRouter, ToastContainer, render, parseZones } from '/static/hoover/index.js';
|
|
|
|
const logTabs = [
|
|
{ key: 'journal', label: 'Journal', url: '/api/logs/journal' },
|
|
{ key: 'nginx-access', label: 'Nginx Access', url: '/api/logs/nginx/access' },
|
|
{ key: 'nginx-error', label: 'Nginx Error', url: '/api/logs/nginx/error' },
|
|
{ key: 'dnsmasq', label: 'Dnsmasq', url: '/api/logs/dnsmasq' },
|
|
{ key: 'app', label: 'App', url: '/api/logs/app' },
|
|
];
|
|
|
|
|
|
async function fetchLog(state, url, signal) {
|
|
state.loading = true;
|
|
state.error = null;
|
|
try {
|
|
const res = await fetch(url, { signal });
|
|
if (signal?.aborted) return;
|
|
const text = await res.text();
|
|
if (signal?.aborted) return;
|
|
state.lines = text.split('\n').filter(l => l.length > 0);
|
|
} catch (e) {
|
|
if (signal?.aborted) return;
|
|
state.error = String(e);
|
|
}
|
|
state.loading = false;
|
|
state.refreshing = false;
|
|
}
|
|
|
|
export default definePage({
|
|
init() {
|
|
return { activeTab: 'journal', lines: [], loading: false, refreshing: false, error: null };
|
|
},
|
|
subscribe: [],
|
|
async load(state, abortController, entry) {
|
|
if (state.lines?.length) state.refreshing = true;
|
|
else state.loading = true;
|
|
const myId = entry ? entry.requestId : 0;
|
|
const tab = logTabs.find(t => t.key === state.activeTab) || logTabs[0];
|
|
await fetchLog(state, tab.url, abortController?.signal);
|
|
if (abortController?.signal.aborted || (entry && entry.requestId !== myId)) return;
|
|
},
|
|
onUnmount(state) {
|
|
state.lines = [];
|
|
},
|
|
render(state) {
|
|
const tab = logTabs.find(t => t.key === state.activeTab) || logTabs[0];
|
|
|
|
const lineVnodes = state.lines.map((line, i) =>
|
|
h('div', { class: 'log-line', key: i }, esc(line))
|
|
);
|
|
|
|
return [
|
|
PageHeader({ title: 'Logs', subtitle: 'System & service logs' }),
|
|
h('div', { class: 'tabs', key: 'log-tabs' },
|
|
logTabs.map(t => h('span', {
|
|
class: 'tab ' + (state.activeTab === t.key ? 'active' : ''),
|
|
'on:click': async () => {
|
|
state.activeTab = t.key;
|
|
await fetchLog(state, t.url);
|
|
},
|
|
style: 'cursor:pointer;',
|
|
}, t.label))
|
|
),
|
|
h('div', { class: 'card', key: 'log-card' },
|
|
h('div', { class: 'card-header' },
|
|
h('span', null, tab.label),
|
|
h('button', {
|
|
class: 'btn btn-sm btn-outline',
|
|
style: 'float:right;',
|
|
'on:click': async () => {
|
|
await fetchLog(state, tab.url);
|
|
},
|
|
}, '\u21BB')
|
|
),
|
|
h('div', { class: 'card-body log-body' },
|
|
state.loading && !state.refreshing
|
|
? h('div', { class: 'loading' }, state.refreshing ? 'Refreshing...' : 'Loading...')
|
|
: state.error
|
|
? h('div', { class: 'error-msg' }, state.error)
|
|
: lineVnodes.length > 0
|
|
? h('pre', null, lineVnodes)
|
|
: h('div', { class: 'text-muted text-sm' }, 'No log lines available')
|
|
)
|
|
),
|
|
];
|
|
},
|
|
});
|