diff --git a/.gitignore b/.gitignore index e5bbc8ef..505622db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +.cache .idea .vscode upload diff --git a/openflare_server/model/node.go b/openflare_server/model/node.go index 0c869b6a..42afd2b2 100644 --- a/openflare_server/model/node.go +++ b/openflare_server/model/node.go @@ -34,6 +34,14 @@ func ListNodes() (nodes []*Node, err error) { return nodes, err } +func ListNodesByNodeIDs(nodeIDs []string) (nodes []*Node, err error) { + if len(nodeIDs) == 0 { + return []*Node{}, nil + } + err = DB.Where("node_id IN ?", nodeIDs).Find(&nodes).Error + return nodes, err +} + func GetNodeByNodeID(nodeID string) (*Node, error) { node := &Node{} err := DB.Where("node_id = ?", nodeID).First(node).Error diff --git a/openflare_server/service/access_log.go b/openflare_server/service/access_log.go index 54646fb6..696c9674 100644 --- a/openflare_server/service/access_log.go +++ b/openflare_server/service/access_log.go @@ -7,7 +7,7 @@ import ( ) const ( - defaultAccessLogPageSize = 50 + defaultAccessLogPageSize = 20 maxAccessLogPageSize = 200 ) @@ -51,7 +51,19 @@ func ListAccessLogs(nodeID string, page int, pageSize int) (*AccessLogList, erro if err != nil { return nil, err } - nodes, err := model.ListNodes() + nodeIDs := make([]string, 0, len(logs)) + seenNodeIDs := make(map[string]struct{}, len(logs)) + for _, item := range logs { + if item == nil { + continue + } + if _, exists := seenNodeIDs[item.NodeID]; exists { + continue + } + seenNodeIDs[item.NodeID] = struct{}{} + nodeIDs = append(nodeIDs, item.NodeID) + } + nodes, err := model.ListNodesByNodeIDs(nodeIDs) if err != nil { return nil, err } diff --git a/openflare_server/service/access_log_test.go b/openflare_server/service/access_log_test.go index 2f37658a..920f860b 100644 --- a/openflare_server/service/access_log_test.go +++ b/openflare_server/service/access_log_test.go @@ -98,3 +98,44 @@ func TestListAccessLogsIncludesSummaryTotals(t *testing.T) { t.Fatalf("expected filtered items=2, got %d", len(filtered.Items)) } } + +func TestListAccessLogsUsesDefaultPageSize(t *testing.T) { + setupServiceTestDB(t) + + now := time.Now() + if err := model.DB.Create(&model.Node{ + NodeID: "node-default-page-size", + Name: "edge-default-page-size", + }).Error; err != nil { + t.Fatalf("failed to seed node: %v", err) + } + + logs := make([]*model.NodeAccessLog, 0, 25) + for index := range 25 { + logs = append(logs, &model.NodeAccessLog{ + NodeID: "node-default-page-size", + LoggedAt: now.Add(-time.Duration(index) * time.Minute), + RemoteAddr: "1.1.1.1", + Host: "example.com", + Path: "/default-page-size", + StatusCode: 200, + }) + } + if err := model.DB.Create(&logs).Error; err != nil { + t.Fatalf("failed to seed access logs: %v", err) + } + + result, err := ListAccessLogs("", 0, 0) + if err != nil { + t.Fatalf("ListAccessLogs failed: %v", err) + } + if result.PageSize != 20 { + t.Fatalf("expected default page_size=20, got %d", result.PageSize) + } + if len(result.Items) != 20 { + t.Fatalf("expected current page items=20, got %d", len(result.Items)) + } + if !result.HasMore { + t.Fatal("expected has_more to be true") + } +} diff --git a/openflare_server/web/features/access-logs/api/access-logs.ts b/openflare_server/web/features/access-logs/api/access-logs.ts index ee3a9c34..676e53f1 100644 --- a/openflare_server/web/features/access-logs/api/access-logs.ts +++ b/openflare_server/web/features/access-logs/api/access-logs.ts @@ -2,7 +2,7 @@ import { apiRequest } from '@/lib/api/client'; import type { AccessLogList } from '@/features/access-logs/types'; -export function getAccessLogs(page: number, nodeId?: string, pageSize = 50) { +export function getAccessLogs(page: number, nodeId?: string, pageSize = 20) { const normalizedNodeId = nodeId?.trim(); const searchParams = new URLSearchParams({ p: String(Math.max(page, 0)), diff --git a/openflare_server/web/features/access-logs/components/access-logs-page.tsx b/openflare_server/web/features/access-logs/components/access-logs-page.tsx index 71450b9b..f54a7851 100644 --- a/openflare_server/web/features/access-logs/components/access-logs-page.tsx +++ b/openflare_server/web/features/access-logs/components/access-logs-page.tsx @@ -15,11 +15,14 @@ import type { AccessLogItem } from '@/features/access-logs/types'; import { PrimaryButton, ResourceInput, + ResourceSelect, SecondaryButton, } from '@/features/shared/components/resource-primitives'; import { formatDateTime, formatRelativeTime } from '@/lib/utils/date'; const accessLogsQueryKey = (nodeId: string) => ['access-logs', nodeId] as const; +const defaultPageSize = 20; +const accessLogPageSizeOptions = [20, 50, 100, 200]; function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : '请求失败,请稍后重试。'; @@ -47,15 +50,16 @@ export function AccessLogsPage() { const [nodeFilterInput, setNodeFilterInput] = useState(''); const [nodeFilter, setNodeFilter] = useState(''); const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(defaultPageSize); const logsQuery = useQuery({ - queryKey: [...accessLogsQueryKey(nodeFilter), page], - queryFn: () => getAccessLogs(page, nodeFilter), + queryKey: [...accessLogsQueryKey(nodeFilter), page, pageSize], + queryFn: () => getAccessLogs(page, nodeFilter, pageSize), + placeholderData: (previousData) => previousData, }); 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( @@ -125,6 +129,21 @@ export function AccessLogsPage() { placeholder="输入 node_id 过滤访问日志" className="lg:max-w-md" /> + { + setPageSize(Number(event.target.value) || defaultPageSize); + setPage(0); + }} + className="lg:w-40" + aria-label="每页条数" + > + {accessLogPageSizeOptions.map((option) => ( + + ))} +