import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import type { IngressSample, IngressRouteStats } from '../ingress-insights' import { summarizeIngress } from '../ingress-insights' type Mode = 'traffic' | 'errors' | 'slow' | 'detail' type ErrorClass = 'all' | '5xx' | '4xx' const modeIds: Mode[] = ['traffic', 'errors', 'slow', 'detail'] const modeLabelKeys: Record = { traffic: 'ingress.modeTraffic', errors: 'ingress.modeErrors', slow: 'ingress.modeSlow', detail: 'ingress.modeDetail', } const heading = 'px-3 py-2 text-left font-medium whitespace-nowrap' const cell = 'px-3 py-2 tabular-nums' const formatSeconds = (seconds: number | null) => seconds === null ? '—' : `${seconds.toFixed(3)} s` const routeKey = (route: IngressRouteStats) => `${route.domain}\u0000${route.path}` function Route({ route }: { route: IngressRouteStats }) { return <>{route.domain} {route.path} } export function IngressInsightsTable({ samples, loadedCount }: { samples: IngressSample[]; loadedCount: number }) { const { t } = useTranslation('logs') const [mode, setMode] = useState('traffic') const [errorClass, setErrorClass] = useState('all') const [slowThreshold, setSlowThreshold] = useState(1) const summary = useMemo(() => summarizeIngress(samples, slowThreshold), [samples, slowThreshold]) const traffic = useMemo(() => [...summary.routes].sort((a, b) => b.pv - a.pv || b.error5xx - a.error5xx).slice(0, 10), [summary.routes]) const errors = useMemo(() => [...summary.routes] .filter(route => errorClass === '5xx' ? route.error5xx : errorClass === '4xx' ? route.error4xx : route.error4xx + route.error5xx) .sort((a, b) => { const count = (route: IngressRouteStats) => errorClass === '5xx' ? route.error5xx : errorClass === '4xx' ? route.error4xx : route.error4xx + route.error5xx return count(b) - count(a) || b.error5xx - a.error5xx }).slice(0, 10), [summary.routes, errorClass]) const slow = useMemo(() => [...summary.routes].filter(route => route.slow || route.timeoutSignals) .sort((a, b) => b.slow - a.slow || b.timeoutSignals - a.timeoutSignals || (b.maxSeconds ?? 0) - (a.maxSeconds ?? 0)).slice(0, 10), [summary.routes]) const modes = useMemo(() => modeIds.map(id => ({ id, label: t(modeLabelKeys[id]) })), [t]) return

{t('ingress.summary', { loaded: loadedCount, parsed: samples.length })}

{[ [t('ingress.statPv'), summary.pv], [t('ingress.statUv'), summary.uv], [t('ingress.statErrors'), summary.errors], [t('ingress.statSlow', { threshold: slowThreshold }), summary.slow], ].map(([label, value]) =>
{label}
{value}
)}
{modes.map(item => )}
{mode === 'errors' && } {mode === 'slow' && }
{mode === 'slow' &&

{t('ingress.slowHint')}{summary.timed < summary.pv && ` ${t('ingress.slowMissing', { count: summary.pv - summary.timed })}`}

} {mode === 'traffic' &&

{t('ingress.trafficHint')}

}
{mode === 'traffic' && {[t('ingress.thHost'), t('ingress.thPath'), 'PV', t('ingress.statUv'), t('ingress.thSuccess'), '4xx', '5xx'].map(title => )}{traffic.map(route => )}
{title}
{route.pv}{route.uv}{(route.success / route.pv * 100).toFixed(1)}%{route.error4xx}{route.error5xx}
} {mode === 'errors' && {[t('ingress.thHost'), t('ingress.thPath'), '4xx', '5xx', t('ingress.thErrorRate'), t('ingress.thStatus')].map(title => )}{errors.map(route => )}
{title}
{route.error4xx}{route.error5xx}{((route.error4xx + route.error5xx) / route.pv * 100).toFixed(1)}%{route.statusCodes}
} {mode === 'slow' && {[t('ingress.thHost'), t('ingress.thPath'), t('ingress.thSlow'), t('ingress.thMaxLatency'), t('ingress.thAvgLatency'), t('ingress.thTimeout')].map(title => )}{slow.map(route => )}
{title}
{route.slow}{formatSeconds(route.maxSeconds)}{formatSeconds(route.averageSeconds)}{route.timeoutSignals}
} {mode === 'detail' && {[t('ingress.thTime'), t('ingress.thHost'), t('ingress.thPath'), t('ingress.thClientIp'), t('ingress.thStatusShort'), t('ingress.thLatency')].map(title => )}{samples.map(({ access, timestamp }, index) => )}
{title}
{new Date(timestamp).toLocaleString()}{access.domain || '—'}{access.method} {access.path}{access.clientIp || '—'}{access.status}{access.requestTime ? `${access.requestTime} s` : '—'}
} {((mode === 'traffic' && !traffic.length) || (mode === 'errors' && !errors.length) || (mode === 'slow' && !slow.length)) &&

{t('ingress.empty')}

}
}