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"
/>
+