Files
AtreidesAssetsFree/client/integrations/failure-log.js
T

72 lines
4.0 KiB
JavaScript
Raw Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:4da5cbe36b669344f62663079e6a6414b09f2f6d56a746c2f3daa2989f1e621a
import { t } from '../i18n/index.js';
import { listIntegrationCalls, lastIntegrationFailures } from '../data/integration-log.js';
import { listProviders } from '../data/providers.js';
import { esc } from '../ui/escape.js';
import * as dialog from '../ui/dialog.js';
import { selectLabel } from '../ui/select-label.js';
import { localDateTimeNl } from '../../shared/time.js';
import { CONFIG } from '../../shared/config.js';
const DAYS = CONFIG.INTEGRATION_LOG.WINDOW_DAYS;
const sourceOf = row => (row.target === 'service' ? 'service' : row.providerKey);
const reasonOf = row => row.detail || row.errorKey || t(`integrationLog.outcome_${row.outcome}`);
export async function lastFailureHtml(source) {
const row = (await lastIntegrationFailures()).find(r => sourceOf(r) === source);
if (!row) return '';
const status = row.httpStatus ? ` (${row.httpStatus})` : '';
return `<div class="t-body-sm ilog-last">
<span class="t-neg">${esc(t('integrationLog.lastFailure'))}</span>
${esc(localDateTimeNl(row.occurredAt))} · ${esc(reasonOf(row))}${esc(status)}
<button class="btn btn-ghost btn-sm" data-ilog-open="${esc(source)}" title="${esc(t('integrationLog.openHint', { n: DAYS }))}">${esc(t('integrationLog.open'))}</button>
</div>`;
}
export function wireFailureLog(host) {
host.querySelectorAll('[data-ilog-open]').forEach(b => {
b.onclick = (e) => { e.stopPropagation(); openFailureLog(b.dataset.ilogOpen); };
});
}
function tableHtml(rows, names) {
if (!rows.length) return `<div class="t-body-sm t-muted">${esc(t('integrationLog.noFailures', { n: DAYS }))}</div>`;
const head = ['colTime', 'colSource', 'colOperation', 'colSubject', 'colOutcome', 'colStatus', 'colKey', 'colDetail', 'colRequestId']
.map(k => `<th${k === 'colRequestId' ? ` title="${esc(t('integrationLog.requestIdHint'))}"` : ''}>${esc(t(`integrationLog.${k}`))}</th>`).join('');
const body = rows.map(r => `<tr>
<td class="ilog-nowrap">${esc(localDateTimeNl(r.occurredAt))}</td>
<td>${esc(names.get(sourceOf(r)) || sourceOf(r))}</td>
<td><code>${esc(r.operation)}</code></td>
<td>${esc(r.subject ?? '')}</td>
<td class="${r.outcome === 'fail' ? 't-neg' : 't-muted'}">${esc(t(`integrationLog.outcome_${r.outcome}`))}</td>
<td class="num">${esc(r.httpStatus ?? '')}</td>
<td><code>${esc(r.errorKey ?? '')}</code></td>
<td>${esc(r.detail ?? '')}</td>
<td><code class="ilog-id">${esc(r.requestId ?? '')}</code></td>
</tr>`).join('');
return `<div class="ilog-scroll"><table class="ilog-table"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;
}
export async function openFailureLog(source = '') {
const [rows, providers] = await Promise.all([listIntegrationCalls({ days: DAYS }), listProviders()]);
const names = new Map([['service', t('integrationLog.service')], ...providers.map(p => [p.key, p.name])]);
const sources = [...new Set([...rows.map(sourceOf), ...(source ? [source] : [])])]
.sort((a, b) => (a === 'service' ? -1 : b === 'service' ? 1 : String(names.get(a) || a).localeCompare(String(names.get(b) || b))));
const options = [`<option value="">${esc(t('integrationLog.allSources'))}</option>`,
...sources.map(s => `<option value="${esc(s)}"${s === source ? ' selected' : ''}>${esc(names.get(s) || s)}</option>`)].join('');
dialog.detail({
title: t('integrationLog.title'), size: 'xl',
asideHtml: selectLabel(`<select class="ilog-filter" data-ilog-filter>${options}</select>`),
html: '<div data-ilog-body></div>',
});
const overlays = document.querySelectorAll('.dialog-overlay');
const d = overlays[overlays.length - 1];
if (!d) return;
const bodyEl = d.querySelector('[data-ilog-body]');
const filter = d.querySelector('[data-ilog-filter]');
const paint = () => { bodyEl.innerHTML = tableHtml(rows.filter(r => !filter.value || sourceOf(r) === filter.value), names); };
filter.onchange = paint;
paint();
}