import { define } from '@because/forge' import type { App, LogLine as LogLineType } from '../../shared/types' import { getLogDates, getLogsForDate } from '../api' import { LogLine, LogsContainer, LogTime, Section, SectionTitle } from '../styles' import { theme } from '../themes' import { update } from '../update' type LogsState = { dates: string[] historicalLogs: string[] loadingDates: boolean loadingLogs: boolean searchFilter: string selectedDate: string } const logsState = new Map() let currentApp: App | null = null const getState = (appName: string): LogsState => { if (!logsState.has(appName)) { logsState.set(appName, { dates: [], historicalLogs: [], loadingDates: false, loadingLogs: false, searchFilter: '', selectedDate: 'live', }) } return logsState.get(appName)! } const LogsHeader = define('LogsHeader', { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 12, }) const LogsControls = define('LogsControls', { display: 'flex', alignItems: 'center', gap: 8, }) const SmallSelect = define('SmallSelect', { base: 'select', padding: '4px 8px', background: theme('colors-bgSubtle'), border: `1px solid ${theme('colors-border')}`, borderRadius: '4px', color: theme('colors-text'), fontSize: 12, cursor: 'pointer', selectors: { '&:focus': { outline: 'none', borderColor: theme('colors-primary'), }, }, }) const SmallInput = define('SmallInput', { base: 'input', padding: '4px 8px', background: theme('colors-bgSubtle'), border: `1px solid ${theme('colors-border')}`, borderRadius: '4px', color: theme('colors-text'), fontSize: 12, width: 120, selectors: { '&:focus': { outline: 'none', borderColor: theme('colors-primary'), }, '&::placeholder': { color: theme('colors-textFaint'), }, }, }) function filterLogs( logs: T[], filter: string, getText: (log: T) => string ): T[] { if (!filter) return logs const lower = filter.toLowerCase() return logs.filter(log => getText(log).toLowerCase().includes(lower)) } function LogsContent() { if (!currentApp) return null const state = getState(currentApp.name) const isLive = state.selectedDate === 'live' const filteredLiveLogs = filterLogs(currentApp.logs ?? [], state.searchFilter, l => l.text) const filteredHistoricalLogs = filterLogs(state.historicalLogs, state.searchFilter, l => l) return ( <> {state.loadingLogs && ( --:--:-- Loading... )} {!state.loadingLogs && isLive && ( filteredLiveLogs.length ? ( filteredLiveLogs.map((line, i) => ( {new Date(line.time).toLocaleTimeString()} {line.text} )) ) : ( --:--:-- {state.searchFilter ? 'No matching logs' : 'No logs yet'} ) )} {!state.loadingLogs && !isLive && ( filteredHistoricalLogs.length ? ( filteredHistoricalLogs.map((line, i) => ( {line.match(/^\[([^\]]+)\]/)?.[1]?.split('T')[1]?.slice(0, 8) ?? '--:--:--'} {line.replace(/^\[[^\]]+\] \[[^\]]+\] /, '')} )) ) : ( --:--:-- {state.searchFilter ? 'No matching logs' : 'No logs for this date'} ) )} ) } const updateLogsContent = () => update('#logs-content', ) async function loadDates(appName: string) { const state = getState(appName) if (state.loadingDates || state.dates.length > 0) return state.loadingDates = true try { const dates = await getLogDates(appName) state.dates = dates } catch (e) { console.error('Failed to load log dates:', e) } finally { state.loadingDates = false } } async function loadHistoricalLogs(appName: string, date: string) { const state = getState(appName) state.loadingLogs = true updateLogsContent() try { const logs = await getLogsForDate(appName, date) state.historicalLogs = logs } catch (e) { console.error('Failed to load logs:', e) state.historicalLogs = [] } finally { state.loadingLogs = false updateLogsContent() } } function handleDateChange(appName: string, date: string) { const state = getState(appName) state.selectedDate = date state.historicalLogs = [] if (date !== 'live') { loadHistoricalLogs(appName, date) } else { updateLogsContent() } } function handleSearchChange(appName: string, value: string) { const state = getState(appName) state.searchFilter = value updateLogsContent() } export function LogsSection({ app }: { app: App }) { currentApp = app const state = getState(app.name) // Load dates on first render if (state.dates.length === 0 && !state.loadingDates) { loadDates(app.name) } return (
Logs handleSearchChange(app.name, (e.target as HTMLInputElement).value)} /> handleDateChange(app.name, (e.target as HTMLSelectElement).value)} > {state.dates.map(date => ( ))}
) }