'use client'; import Link from 'next/link'; import { useMemo, useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { EmptyState } from '@/components/feedback/empty-state'; import { ErrorState } from '@/components/feedback/error-state'; import { LoadingState } from '@/components/feedback/loading-state'; import { PageHeader } from '@/components/layout/page-header'; import { AppCard } from '@/components/ui/app-card'; import { StatusBadge } from '@/components/ui/status-badge'; import { getAccessLogs } from '@/features/access-logs/api/access-logs'; import type { AccessLogItem } from '@/features/access-logs/types'; import { PrimaryButton, ResourceInput, SecondaryButton, } from '@/features/shared/components/resource-primitives'; import { formatDateTime, formatRelativeTime } from '@/lib/utils/date'; const accessLogsQueryKey = (nodeId: string) => ['access-logs', nodeId] as const; function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : '请求失败,请稍后重试。'; } function buildSummary(totalRecord = 0, totalIP = 0) { return [ { label: '访问记录', value: totalRecord }, { label: '来源 IP', value: totalIP }, ]; } function getStatusMeta(statusCode: number) { if (statusCode >= 500) { return { label: String(statusCode), variant: 'danger' as const }; } if (statusCode >= 400) { return { label: String(statusCode), variant: 'warning' as const }; } return { label: String(statusCode), variant: 'success' as const }; } export function AccessLogsPage() { const queryClient = useQueryClient(); const [nodeFilterInput, setNodeFilterInput] = useState(''); const [nodeFilter, setNodeFilter] = useState(''); const [page, setPage] = useState(0); const logsQuery = useQuery({ queryKey: [...accessLogsQueryKey(nodeFilter), page], queryFn: () => getAccessLogs(page, nodeFilter), }); const logs = useMemo(() => logsQuery.data?.items ?? [], [logsQuery.data]); const hasMore = logsQuery.data?.has_more ?? false; const pageSize = logsQuery.data?.page_size ?? 50; const summary = useMemo( () => buildSummary( logsQuery.data?.total_record ?? 0, logsQuery.data?.total_ip ?? 0, ), [logsQuery.data?.total_ip, logsQuery.data?.total_record], ); return (
返回节点页 } />
{summary.map((item) => (

{item.label}

{item.value}

))}
void queryClient.invalidateQueries({ queryKey: accessLogsQueryKey(nodeFilter), }) } > 刷新 } >
setNodeFilterInput(event.target.value)} placeholder="输入 node_id 过滤访问日志" className="lg:max-w-md" />
{ setNodeFilter(nodeFilterInput.trim()); setPage(0); }} > 筛选 { setNodeFilter(''); setNodeFilterInput(''); setPage(0); }} > 清空
{logsQuery.isLoading ? ( ) : logsQuery.isError ? ( ) : logs.length === 0 ? ( ) : (
{logs.map((item) => { const statusMeta = getStatusMeta(item.status_code); return ( ); })}
时间 原 IP 访问域名 路径 节点 状态码
{formatDateTime(item.logged_at)}
{formatRelativeTime(item.logged_at)}
{item.remote_addr || '—'}
{item.region ? (
{item.region}
) : null}
{item.host || '—'} {item.path || '—'}
{item.node_name || item.node_id}
{item.node_id}
)}

第 {page + 1} 页,每页 {pageSize} 条。

setPage((value) => Math.max(value - 1, 0))} > 上一页 setPage((value) => value + 1)} > 下一页
); }