Skip to content

Commit 814de31

Browse files
committed
feat: 补充网add的文件 && 优化回收站列表显示
1 parent 40b85fe commit 814de31

5 files changed

Lines changed: 725 additions & 2 deletions

File tree

app/endpoints/log/page.tsx

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"use client";
2+
import { Button, Input, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Spinner, DateRangePicker, Pagination, Badge } from "@heroui/react";
3+
import { useSearchParams, useRouter } from "next/navigation";
4+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5+
import { faArrowLeft, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons";
6+
import { useState, useEffect, useCallback } from "react";
7+
import { buildApiUrl } from "@/lib/utils";
8+
9+
interface LogItem {
10+
id:number;
11+
createAt:string;
12+
level:string;
13+
instanceId:string;
14+
message:string;
15+
}
16+
17+
export default function LogQueryPage(){
18+
const router=useRouter();
19+
const searchParams=useSearchParams();
20+
const endpointId=searchParams.get("id");
21+
22+
const [level,setLevel]=useState<string>("all");
23+
const [instanceId,setInstanceId]=useState<string>("");
24+
const [range,setRange]=useState<{start:any,end:any}>({start:undefined,end:undefined});
25+
const [items,setItems]=useState<LogItem[]>([]);
26+
const [total,setTotal]=useState(0);
27+
const [totalPages,setTotalPages]=useState(0);
28+
const [page,setPage]=useState(1);
29+
const [pageSize,setPageSize] = useState(20);
30+
const [loading,setLoading]=useState(false);
31+
const [recycleCount,setRecycleCount] = useState(0);
32+
33+
const formatDate=(d:any)=>{
34+
if(!d) return "";
35+
const pad=(n:number)=>n.toString().padStart(2,"0");
36+
// 处理 CalendarDate / DateValue 对象
37+
if(typeof d === "object" && "year" in d && "month" in d && "day" in d){
38+
return `${d.year}-${pad(d.month)}-${pad(d.day)}`;
39+
}
40+
const date = d instanceof Date ? d : new Date(d);
41+
return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())}`;
42+
};
43+
44+
const fetchData=useCallback(async(p:number = page)=>{
45+
if(!endpointId) return;
46+
try{
47+
setLoading(true);
48+
const params=new URLSearchParams();
49+
if(level!=="all") params.append("level",level);
50+
if(instanceId) params.append("instanceId",instanceId);
51+
if(range.start) params.append("start",formatDate(range.start));
52+
if(range.end) params.append("end",formatDate(range.end));
53+
params.append("page",String(p));
54+
params.append("size",String(pageSize));
55+
const res=await fetch(buildApiUrl(`/api/endpoints/${endpointId}/logs/search?`+params.toString()));
56+
const data=await res.json();
57+
const processed=(data.logs||[]).map((item:any)=>{
58+
const rawMsg=item.message||"";
59+
const idx=rawMsg.indexOf("[0m");
60+
const cleanMsg=idx!==-1?rawMsg.slice(idx+3):rawMsg;
61+
return { ...item, message: cleanMsg };
62+
});
63+
setItems(processed);
64+
setTotal(data.total||0);
65+
if(data.totalPages){ setTotalPages(data.totalPages); }
66+
if(data.size){ setPageSize(data.size); }
67+
}catch(e){console.error(e);}finally{setLoading(false);}
68+
},[endpointId,level,instanceId,range,pageSize,page]);
69+
70+
// 首次加载
71+
useEffect(()=>{ fetchData(1); setPage(1); },[fetchData]);
72+
73+
// 获取回收站数量
74+
useEffect(()=>{
75+
const fetchRecycle = async()=>{
76+
if(!endpointId) return;
77+
try{
78+
const res = await fetch(buildApiUrl(`/api/endpoints/${endpointId}/recycle/count`));
79+
const data = await res.json();
80+
setRecycleCount(data.count||0);
81+
}catch(e){console.error(e);}
82+
};
83+
fetchRecycle();
84+
},[endpointId]);
85+
86+
return (
87+
<div className="p-4 space-y-4">
88+
<div className="flex items-center gap-3 justify-between">
89+
<div className="flex items-center gap-3">
90+
<Button isIconOnly variant="flat" size="sm" onClick={() => router.back()} className="bg-default-100 hover:bg-default-200 dark:bg-default-100/10 dark:hover:bg-default-100/20">
91+
<FontAwesomeIcon icon={faArrowLeft} />
92+
</Button>
93+
<h1 className="text-lg md:text-2xl font-bold truncate">日志查询</h1>
94+
</div>
95+
<Button isIconOnly color="danger" variant="light" className="relative bg-default-100 hover:bg-default-200 dark:bg-default-100/10 dark:hover:bg-default-100/20" onPress={()=>router.push(`/endpoints/recycle?id=${endpointId}`)}>
96+
<Badge color="danger" size="sm" content={recycleCount} className="absolute -top-1 -right-1 pointer-events-none">
97+
<FontAwesomeIcon icon={faTrash} />
98+
</Badge>
99+
</Button>
100+
</div>
101+
{/* 查询表单 */}
102+
<div className="flex flex-wrap md:flex-nowrap items-end gap-2">
103+
{/* 日期范围选择 */}
104+
{/* @ts-ignore hero-ui DateRangePicker 类型未完善 */}
105+
<DateRangePicker locale="zh-CN" value={range} onChange={(v:any)=>setRange(v)} />
106+
<Select selectedKeys={[level]} onSelectionChange={(keys)=>setLevel(Array.from(keys)[0] as string)} >
107+
<SelectItem key="all">全部级别</SelectItem>
108+
<SelectItem key="debug">Debug</SelectItem>
109+
<SelectItem key="info">Info</SelectItem>
110+
<SelectItem key="warn">Warn</SelectItem>
111+
<SelectItem key="error">Error</SelectItem>
112+
</Select>
113+
<Input value={instanceId} onValueChange={setInstanceId} placeholder="实例ID" />
114+
<Button color="primary" startContent={<FontAwesomeIcon icon={faSearch}/>} onPress={()=>{setPage(1); fetchData(1);}}>查询</Button>
115+
<Button variant="flat" onPress={()=>{
116+
setLevel("all");
117+
setInstanceId("");
118+
setRange({start:undefined,end:undefined});
119+
setPage(1);
120+
fetchData(1);
121+
}}>重置</Button>
122+
</div>
123+
{/* 结果表格 */}
124+
<Table aria-label="日志列表" >
125+
<TableHeader>
126+
<TableColumn>时间</TableColumn>
127+
<TableColumn>级别</TableColumn>
128+
<TableColumn>实例ID</TableColumn>
129+
<TableColumn>日志</TableColumn>
130+
</TableHeader>
131+
<TableBody items={items} loadingContent={<Spinner />} isLoading={loading} emptyContent="暂无数据">
132+
{item=> (
133+
<TableRow key={item.id}>
134+
<TableCell className="w-80">{item.createAt}</TableCell>
135+
<TableCell className="w-24">{item.level}</TableCell>
136+
<TableCell className="w-40">{item.instanceId}</TableCell>
137+
<TableCell className="max-w-[600px] truncate" title={item.message}>{item.message}</TableCell>
138+
</TableRow>
139+
)}
140+
</TableBody>
141+
</Table>
142+
{/* 分页 */}
143+
<div className="flex flex-col md:flex-row md:justify-between items-center pt-4 gap-2">
144+
<span className="text-sm text-default-500">{total}</span>
145+
<Pagination
146+
page={page}
147+
total={totalPages || Math.ceil(total/pageSize)}
148+
onChange={(p)=>{setPage(p); fetchData(p);}}
149+
showControls
150+
/>
151+
</div>
152+
</div>
153+
);
154+
}

0 commit comments

Comments
 (0)