mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
[功能] 调整访问日志分页大小为20,并更新相关组件以支持动态分页
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
|
.cache
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
upload
|
upload
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ func ListNodes() (nodes []*Node, err error) {
|
|||||||
return nodes, err
|
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) {
|
func GetNodeByNodeID(nodeID string) (*Node, error) {
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
err := DB.Where("node_id = ?", nodeID).First(node).Error
|
err := DB.Where("node_id = ?", nodeID).First(node).Error
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultAccessLogPageSize = 50
|
defaultAccessLogPageSize = 20
|
||||||
maxAccessLogPageSize = 200
|
maxAccessLogPageSize = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +51,19 @@ func ListAccessLogs(nodeID string, page int, pageSize int) (*AccessLogList, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,3 +98,44 @@ func TestListAccessLogsIncludesSummaryTotals(t *testing.T) {
|
|||||||
t.Fatalf("expected filtered items=2, got %d", len(filtered.Items))
|
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';
|
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 normalizedNodeId = nodeId?.trim();
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
p: String(Math.max(page, 0)),
|
p: String(Math.max(page, 0)),
|
||||||
|
|||||||
@@ -15,11 +15,14 @@ import type { AccessLogItem } from '@/features/access-logs/types';
|
|||||||
import {
|
import {
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
ResourceInput,
|
ResourceInput,
|
||||||
|
ResourceSelect,
|
||||||
SecondaryButton,
|
SecondaryButton,
|
||||||
} from '@/features/shared/components/resource-primitives';
|
} from '@/features/shared/components/resource-primitives';
|
||||||
import { formatDateTime, formatRelativeTime } from '@/lib/utils/date';
|
import { formatDateTime, formatRelativeTime } from '@/lib/utils/date';
|
||||||
|
|
||||||
const accessLogsQueryKey = (nodeId: string) => ['access-logs', nodeId] as const;
|
const accessLogsQueryKey = (nodeId: string) => ['access-logs', nodeId] as const;
|
||||||
|
const defaultPageSize = 20;
|
||||||
|
const accessLogPageSizeOptions = [20, 50, 100, 200];
|
||||||
|
|
||||||
function getErrorMessage(error: unknown) {
|
function getErrorMessage(error: unknown) {
|
||||||
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
|
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
|
||||||
@@ -47,15 +50,16 @@ export function AccessLogsPage() {
|
|||||||
const [nodeFilterInput, setNodeFilterInput] = useState('');
|
const [nodeFilterInput, setNodeFilterInput] = useState('');
|
||||||
const [nodeFilter, setNodeFilter] = useState('');
|
const [nodeFilter, setNodeFilter] = useState('');
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
const [pageSize, setPageSize] = useState(defaultPageSize);
|
||||||
|
|
||||||
const logsQuery = useQuery({
|
const logsQuery = useQuery({
|
||||||
queryKey: [...accessLogsQueryKey(nodeFilter), page],
|
queryKey: [...accessLogsQueryKey(nodeFilter), page, pageSize],
|
||||||
queryFn: () => getAccessLogs(page, nodeFilter),
|
queryFn: () => getAccessLogs(page, nodeFilter, pageSize),
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
});
|
});
|
||||||
|
|
||||||
const logs = useMemo(() => logsQuery.data?.items ?? [], [logsQuery.data]);
|
const logs = useMemo(() => logsQuery.data?.items ?? [], [logsQuery.data]);
|
||||||
const hasMore = logsQuery.data?.has_more ?? false;
|
const hasMore = logsQuery.data?.has_more ?? false;
|
||||||
const pageSize = logsQuery.data?.page_size ?? 50;
|
|
||||||
const summary = useMemo(
|
const summary = useMemo(
|
||||||
() =>
|
() =>
|
||||||
buildSummary(
|
buildSummary(
|
||||||
@@ -125,6 +129,21 @@ export function AccessLogsPage() {
|
|||||||
placeholder="输入 node_id 过滤访问日志"
|
placeholder="输入 node_id 过滤访问日志"
|
||||||
className="lg:max-w-md"
|
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">
|
<div className="flex flex-wrap gap-2">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user