import { useEffect, useMemo, useState } from 'react' import { Loader2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' import { SearchableValueSelect } from '@/components/SearchableValueSelect' import type { LogEntry } from '@/api/logs' import { parseIngressAccessLog } from '@/lib/ingress-access-log' import { applyLabelFilter } from '../query-utils' import { HighlightedLogLine } from './HighlightedLogLine' import { QueryEmptyState } from './QueryEmptyState' function levelBadgeClass(level: string) { const value = (level || '').toLowerCase() if (value.includes('error') || value === 'err' || value === 'fatal' || value === 'critical') { return 'rounded bg-destructive/15 px-1 text-destructive' } if (value.includes('warn')) return 'rounded bg-warning/15 px-1 text-warning' if (value.includes('info')) return 'rounded bg-info/15 px-1 text-info' if (value.includes('debug') || value.includes('trace')) return 'rounded bg-muted px-1 text-muted-foreground' return 'rounded bg-muted px-1 text-muted-foreground' } // 只保留原始日志视图;表格/折线/流图/饼图等可视化已迁至「日志分析」页(LogDetailSection) export function LogResultsPanel({ entries, searched, hasSource, pageIndex, hasNextPage, totalPages, loading, loadingStartedAt, query, terms, onQueryChange, onApplyDomain, onShowContext, onSearch, onNextPage, onPreviousPage }: { entries: LogEntry[]; searched: boolean; hasSource: boolean pageIndex: number; hasNextPage: boolean; totalPages?: number; loading: boolean; loadingStartedAt: number | null; query: string; terms: string[] onQueryChange: (query: string) => void; onApplyDomain: (domain: string) => void; onShowContext: (row: LogEntry) => void; onSearch: () => void; onNextPage: () => void; onPreviousPage: () => void }) { const { t } = useTranslation('logs') const [domainFilter, setDomainFilter] = useState('') const [clock, setClock] = useState(0) useEffect(() => { if (!loading) return const timer = window.setInterval(() => setClock(Date.now()), 1000) return () => window.clearInterval(timer) }, [loading]) const domains = useMemo(() => [...new Set(entries.map(entry => parseIngressAccessLog(entry.message)?.domain).filter((domain): domain is string => Boolean(domain)))].sort(), [entries]) const visibleEntries = useMemo(() => { const needle = domainFilter.trim().toLowerCase() return needle ? entries.filter(entry => parseIngressAccessLog(entry.message)?.domain.includes(needle)) : entries }, [entries, domainFilter]) return

{t('results.title')} {hasNextPage ? t('results.summaryMore', { page: pageIndex + 1, count: entries.length }) : t('results.summary', { page: pageIndex + 1, count: entries.length })}

{loading &&
{entries.length ? t('results.loadingPage') : t('results.loadingQuery')} · {t('results.loadingWait', { seconds: clock && loadingStartedAt ? Math.max(0, Math.floor((clock - loadingStartedAt) / 1000)) : 0 })}
} {pageIndex > 0 &&

{t('results.pagingHint')}

} {domains.length > 0 &&
{t('results.domainFilter')} {t('results.domainHint', { shown: visibleEntries.length, total: entries.length })}
} {entries.length === 0 ? : visibleEntries.length === 0 ?

{t('results.domainNoMatch')}

:
{visibleEntries.map((row, index) =>
{row.level} {Object.entries(row.labels).filter(([key]) => ['namespace', 'app', 'pod'].includes(key)).map(([key, value]) => )}
)}
} {searched && entries.length > 0 &&
{totalPages && totalPages > 1 ? t('results.pageOfTotal', { page: pageIndex + 1, total: totalPages }) : t('results.pageOf', { page: pageIndex + 1 })}
}
}