import { memo, useEffect, useState } from 'react' import { useNavigate } from 'react-router' import { useTranslation } from 'react-i18next' import '@/i18n/domains' import { BarChart3, Boxes, FileWarning, Loader2, RefreshCw, ScrollText, TriangleAlert } from 'lucide-react' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { PageContainer } from '@/components/PageContainer' import { EmptyState } from '@/components/EmptyState' import { ErrorState } from '@/components/ErrorState' import { LoadingSpinner } from '@/components/LoadingSpinner' import { StatCard } from '@/components/StatCard' import { TimeRangePicker } from '@/components/TimeRangePicker' import { logsApi, type LogSource } from '@/api/logs' import { getUser } from '@/lib/auth-storage' import { useAnalysisMetrics } from './hooks/useAnalysisMetrics' import { useDailyVolume } from './hooks/useDailyVolume' import { TrendChart } from './components/TrendChart' import { AppStreamChart } from './components/AppStreamChart' import { DailyVolumeChart } from './components/DailyVolumeChart' import { NamespacePie } from './components/NamespacePie' import { TopAppsTable } from './components/TopAppsTable' import { IngressAnalysisSection } from './components/IngressAnalysisSection' const toLocal = (date: Date) => new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, 19) /** * 日志分析页(admin 专用):SLS 风格仪表盘 — 统计卡 + 趋势折线 + 命名空间饼图 + * 应用流图 + 应用排行表;数据来自 5 条 LogQL 指标查询(POST /logs/metrics)。 */ const AnalysisPage = memo(function AnalysisPage() { const { t } = useTranslation('analysis') const navigate = useNavigate() const isAdmin = getUser()?.role === 'admin' const [sources, setSources] = useState([]) const [sourceId, setSourceId] = useState(0) const [start, setStart] = useState(() => toLocal(new Date(Date.now() - 3600000))) const [end, setEnd] = useState(() => toLocal(new Date())) useEffect(() => { logsApi.sources() .then(result => { setSources(result.list) setSourceId(current => result.list.some(source => source.id === current) ? current : result.list.find(source => source.isDefault)?.id ?? result.list[0]?.id ?? 0, ) }) .catch(() => setSources([])) }, []) const { data, loading, error, appLabel, refresh } = useAnalysisMetrics({ sourceId, start, end, namespace: '' }) // 每日统计固定近 7 天,不随上方时间范围变化;错误就地展示,不影响主区 const daily = useDailyVolume({ sourceId, namespace: '', appLabel }) // 非 admin:菜单不可见,直访时给明确提示(后端 AdminAuth 403 兜底) if (!isAdmin) { return ( ) } return ( {loading ? : } {t('filter.refresh')} } > {/* 筛选栏:数据源 / 时间范围(全命名空间聚合) */}
{ setStart(nextStart); setEnd(nextEnd) }} />
{sources.length === 0 ? ( navigate('/logs')}> {t('emptySource.goLogs')} } /> ) : error ? ( ) : loading && !data ? ( ) : !data || !sourceId ? null : ( <>
{t('trend.title')}

{t('trend.subtitle')}

{t('pie.title')}

{t('pie.subtitle')}

{t('stream.title')}

{t('stream.subtitle', { count: Math.min(data.apps.length, 10) })}

{t('daily.title')} {t('daily.fixedBadge')}

{t('daily.subtitle')}

{daily.error ? ( ) : daily.loading && !daily.data ? ( ) : ( )}
{t('table.title')}

{t('table.subtitle')}

navigate(`/logs?q=${encodeURIComponent(query)}`)} />
)}
) }) export default AnalysisPage