|
|
@@ -17,25 +17,26 @@ const ToolsCallLogList = () => {
|
|
|
pageSize: 10,
|
|
|
total: 0,
|
|
|
});
|
|
|
-
|
|
|
+ const [stats, setStats] = useState({ successCount: 0, failureCount: 0, successRate: 0 });
|
|
|
+
|
|
|
// 搜索条件
|
|
|
const [searchParams, setSearchParams] = useState({
|
|
|
user: "",
|
|
|
mcp_tools_name: "",
|
|
|
- status: "",
|
|
|
+ status: null,
|
|
|
timeRange: null,
|
|
|
});
|
|
|
|
|
|
// 格式化时间戳为 YYYY-MM-DD HH:mm:ss
|
|
|
const formatTimestamp = (timestamp) => {
|
|
|
if (!timestamp) return "-";
|
|
|
- return dayjs(timestamp).format("YYYY-MM-DD HH:mm:ss");
|
|
|
+ return dayjs(timestamp * 1000).format("YYYY-MM-DD HH:mm:ss");
|
|
|
};
|
|
|
|
|
|
// 计算耗时(秒)
|
|
|
const calculateDuration = (callTime, finishTime) => {
|
|
|
if (!callTime || !finishTime) return "-";
|
|
|
- return ((finishTime - callTime) / 1000).toFixed(2) + "s";
|
|
|
+ return (finishTime - callTime).toFixed(1) + "s";
|
|
|
};
|
|
|
|
|
|
// 获取数据
|
|
|
@@ -49,17 +50,22 @@ const ToolsCallLogList = () => {
|
|
|
...params,
|
|
|
};
|
|
|
|
|
|
- // 处理时间范围
|
|
|
- if (searchParams.timeRange && searchParams.timeRange.length === 2) {
|
|
|
- queryParams.startTime = searchParams.timeRange[0].valueOf();
|
|
|
- queryParams.endTime = searchParams.timeRange[1].valueOf();
|
|
|
+ // 处理时间范围(优先使用传入的覆盖参数;即便为 null 也要生效)
|
|
|
+ const hasTimeRangeInParams = Object.prototype.hasOwnProperty.call(params, "timeRange");
|
|
|
+ const appliedTimeRange = hasTimeRangeInParams ? params.timeRange : searchParams.timeRange;
|
|
|
+ if (appliedTimeRange && appliedTimeRange.length === 2) {
|
|
|
+ // 将 Dayjs 转为“秒”时间戳,并将结束时间设为当天 23:59:59 以便区间包含整天
|
|
|
+ const startSec = dayjs(appliedTimeRange[0]).startOf("day").unix();
|
|
|
+ const endSec = dayjs(appliedTimeRange[1]).endOf("day").unix();
|
|
|
+ queryParams.startTime = startSec;
|
|
|
+ queryParams.endTime = endSec;
|
|
|
}
|
|
|
|
|
|
const response = await toolsCallLogApi.getList(queryParams);
|
|
|
const { data: list, total } = response.data;
|
|
|
|
|
|
setData(list);
|
|
|
- setPagination(prev => ({
|
|
|
+ setPagination((prev) => ({
|
|
|
...prev,
|
|
|
total,
|
|
|
}));
|
|
|
@@ -71,10 +77,32 @@ const ToolsCallLogList = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ // 获取统计数据(可带筛选参数)
|
|
|
+ const fetchStats = async (params = {}) => {
|
|
|
+ try {
|
|
|
+ const queryParams = { ...searchParams, ...params };
|
|
|
+ const hasTimeRangeInParams = Object.prototype.hasOwnProperty.call(params, "timeRange");
|
|
|
+ const appliedTimeRange = hasTimeRangeInParams ? params.timeRange : searchParams.timeRange;
|
|
|
+ if (appliedTimeRange && appliedTimeRange.length === 2) {
|
|
|
+ const startSec = dayjs(appliedTimeRange[0]).startOf("day").unix();
|
|
|
+ const endSec = dayjs(appliedTimeRange[1]).endOf("day").unix();
|
|
|
+ queryParams.startTime = startSec;
|
|
|
+ queryParams.endTime = endSec;
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await toolsCallLogApi.getStats(queryParams);
|
|
|
+ const { successCount = 0, failureCount = 0, successRate = 0 } = res.data || {};
|
|
|
+ setStats({ successCount, failureCount, successRate });
|
|
|
+ } catch (error) {
|
|
|
+ console.error("获取统计数据失败:", error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
// 搜索
|
|
|
const handleSearch = () => {
|
|
|
- setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
+ setPagination((prev) => ({ ...prev, current: 1 }));
|
|
|
fetchData({ page: 1 });
|
|
|
+ fetchStats();
|
|
|
};
|
|
|
|
|
|
// 重置搜索
|
|
|
@@ -82,19 +110,19 @@ const ToolsCallLogList = () => {
|
|
|
setSearchParams({
|
|
|
user: "",
|
|
|
mcp_tools_name: "",
|
|
|
- status: "",
|
|
|
+ status: null,
|
|
|
timeRange: null,
|
|
|
});
|
|
|
- setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
- setTimeout(() => {
|
|
|
- fetchData({
|
|
|
- page: 1,
|
|
|
- user: "",
|
|
|
- mcp_tools_name: "",
|
|
|
- status: "",
|
|
|
- timeRange: null,
|
|
|
- });
|
|
|
- }, 0);
|
|
|
+ setPagination((prev) => ({ ...prev, current: 1 }));
|
|
|
+ const params = {
|
|
|
+ page: 1,
|
|
|
+ user: "",
|
|
|
+ mcp_tools_name: "",
|
|
|
+ status: null,
|
|
|
+ timeRange: null,
|
|
|
+ };
|
|
|
+ fetchData(params);
|
|
|
+ fetchStats(params);
|
|
|
};
|
|
|
|
|
|
// 查看详情
|
|
|
@@ -122,9 +150,7 @@ const ToolsCallLogList = () => {
|
|
|
key: "status",
|
|
|
width: 100,
|
|
|
render: (status) => (
|
|
|
- <Tag color={status === "success" ? "green" : "red"}>
|
|
|
- {status === "success" ? "成功" : "失败"}
|
|
|
- </Tag>
|
|
|
+ <Tag color={status === "success" ? "green" : "red"}>{status === "success" ? "成功" : "失败"}</Tag>
|
|
|
),
|
|
|
},
|
|
|
{
|
|
|
@@ -160,7 +186,10 @@ const ToolsCallLogList = () => {
|
|
|
key: "action",
|
|
|
width: 100,
|
|
|
render: (_, record) => (
|
|
|
- <Button type="link" onClick={() => handleViewDetail(record)}>
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ onClick={() => handleViewDetail(record)}
|
|
|
+ >
|
|
|
详情
|
|
|
</Button>
|
|
|
),
|
|
|
@@ -169,7 +198,7 @@ const ToolsCallLogList = () => {
|
|
|
|
|
|
// 分页变化
|
|
|
const handleTableChange = (paginationConfig) => {
|
|
|
- setPagination(prev => ({
|
|
|
+ setPagination((prev) => ({
|
|
|
...prev,
|
|
|
current: paginationConfig.current,
|
|
|
pageSize: paginationConfig.pageSize,
|
|
|
@@ -182,31 +211,55 @@ const ToolsCallLogList = () => {
|
|
|
|
|
|
useEffect(() => {
|
|
|
fetchData();
|
|
|
+ fetchStats();
|
|
|
}, []);
|
|
|
|
|
|
return (
|
|
|
<div style={{ padding: "24px" }}>
|
|
|
- <h2>工具调用日志</h2>
|
|
|
-
|
|
|
{/* 搜索区域 */}
|
|
|
<div style={{ marginBottom: "16px", padding: "16px", background: "#fafafa", borderRadius: "6px" }}>
|
|
|
+ {/* 统计信息 */}
|
|
|
+ <div className="mt-[12px] mb-[12px] flex gap-[24px]">
|
|
|
+ <div>
|
|
|
+ 成功数: <span style={{ color: "#52c41a", fontWeight: 700 }}>{stats.successCount}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ 失败数: <span style={{ color: "#ff4d4f", fontWeight: 700 }}>{stats.failureCount}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ 成功率: <span style={{ color: "#52c41a", fontWeight: 700 }}>{stats.successRate}%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<Space wrap>
|
|
|
<Input
|
|
|
placeholder="用户名"
|
|
|
value={searchParams.user}
|
|
|
- onChange={(e) => setSearchParams(prev => ({ ...prev, user: e.target.value }))}
|
|
|
+ onChange={(e) => setSearchParams((prev) => ({ ...prev, user: e.target.value }))}
|
|
|
style={{ width: 120 }}
|
|
|
/>
|
|
|
<Input
|
|
|
placeholder="工具名称"
|
|
|
value={searchParams.mcp_tools_name}
|
|
|
- onChange={(e) => setSearchParams(prev => ({ ...prev, mcp_tools_name: e.target.value }))}
|
|
|
+ onChange={(e) => setSearchParams((prev) => ({ ...prev, mcp_tools_name: e.target.value }))}
|
|
|
style={{ width: 150 }}
|
|
|
/>
|
|
|
<Select
|
|
|
- placeholder="状态"
|
|
|
+ placeholder="全部"
|
|
|
value={searchParams.status}
|
|
|
- onChange={(value) => setSearchParams(prev => ({ ...prev, status: value }))}
|
|
|
+ onChange={(value) => {
|
|
|
+ const next = { ...searchParams, status: value };
|
|
|
+ setSearchParams(next);
|
|
|
+ setPagination((prev) => ({ ...prev, current: 1 }));
|
|
|
+ const params = {
|
|
|
+ page: 1,
|
|
|
+ status: value,
|
|
|
+ user: next.user,
|
|
|
+ mcp_tools_name: next.mcp_tools_name,
|
|
|
+ timeRange: next.timeRange,
|
|
|
+ };
|
|
|
+ fetchData(params);
|
|
|
+ fetchStats(params);
|
|
|
+ }}
|
|
|
style={{ width: 120 }}
|
|
|
allowClear
|
|
|
>
|
|
|
@@ -216,14 +269,33 @@ const ToolsCallLogList = () => {
|
|
|
<RangePicker
|
|
|
placeholder={["开始时间", "结束时间"]}
|
|
|
value={searchParams.timeRange}
|
|
|
- onChange={(dates) => setSearchParams(prev => ({ ...prev, timeRange: dates }))}
|
|
|
- showTime
|
|
|
- format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ onChange={(dates) => {
|
|
|
+ const next = { ...searchParams, timeRange: dates };
|
|
|
+ setSearchParams(next);
|
|
|
+ setPagination((prev) => ({ ...prev, current: 1 }));
|
|
|
+ const params = {
|
|
|
+ page: 1,
|
|
|
+ user: next.user,
|
|
|
+ mcp_tools_name: next.mcp_tools_name,
|
|
|
+ status: next.status,
|
|
|
+ timeRange: dates,
|
|
|
+ };
|
|
|
+ fetchData(params);
|
|
|
+ fetchStats(params);
|
|
|
+ }}
|
|
|
+ format="YYYY-MM-DD"
|
|
|
/>
|
|
|
- <Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<SearchOutlined />}
|
|
|
+ onClick={handleSearch}
|
|
|
+ >
|
|
|
搜索
|
|
|
</Button>
|
|
|
- <Button icon={<ReloadOutlined />} onClick={handleReset}>
|
|
|
+ <Button
|
|
|
+ icon={<ReloadOutlined />}
|
|
|
+ onClick={handleReset}
|
|
|
+ >
|
|
|
重置
|
|
|
</Button>
|
|
|
</Space>
|
|
|
@@ -248,4 +320,4 @@ const ToolsCallLogList = () => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-export default ToolsCallLogList;
|
|
|
+export default ToolsCallLogList;
|