mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
[功能] 调整访问日志分页大小为20,并更新相关组件以支持动态分页
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
|
||||
.cache
|
||||
.idea
|
||||
.vscode
|
||||
upload
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<ResourceSelect
|
||||
value={String(pageSize)}
|
||||
onChange={(event) => {
|
||||
setPageSize(Number(event.target.value) || defaultPageSize);
|
||||
setPage(0);
|
||||
}}
|
||||
className="lg:w-40"
|
||||
aria-label="每页条数"
|
||||
>
|
||||
{accessLogPageSizeOptions.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
每页 {option} 条
|
||||
</option>
|
||||
))}
|
||||
</ResourceSelect>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<PrimaryButton
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user