|
@@ -0,0 +1,251 @@
|
|
|
|
|
+import React, { useState, useEffect } from "react";
|
|
|
|
|
+import { Table, Button, Input, Select, DatePicker, Space, message, Tag } from "antd";
|
|
|
|
|
+import { SearchOutlined, ReloadOutlined } from "@ant-design/icons";
|
|
|
|
|
+import { useNavigate } from "react-router-dom";
|
|
|
|
|
+import { toolsCallLogApi } from "../services/api";
|
|
|
|
|
+import dayjs from "dayjs";
|
|
|
|
|
+
|
|
|
|
|
+const { RangePicker } = DatePicker;
|
|
|
|
|
+const { Option } = Select;
|
|
|
|
|
+
|
|
|
|
|
+const ToolsCallLogList = () => {
|
|
|
|
|
+ const navigate = useNavigate();
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
|
|
+ const [data, setData] = useState([]);
|
|
|
|
|
+ const [pagination, setPagination] = useState({
|
|
|
|
|
+ current: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索条件
|
|
|
|
|
+ const [searchParams, setSearchParams] = useState({
|
|
|
|
|
+ user: "",
|
|
|
|
|
+ mcp_tools_name: "",
|
|
|
|
|
+ status: "",
|
|
|
|
|
+ timeRange: null,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 格式化时间戳为 YYYY-MM-DD HH:mm:ss
|
|
|
|
|
+ const formatTimestamp = (timestamp) => {
|
|
|
|
|
+ if (!timestamp) return "-";
|
|
|
|
|
+ return dayjs(timestamp).format("YYYY-MM-DD HH:mm:ss");
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 计算耗时(秒)
|
|
|
|
|
+ const calculateDuration = (callTime, finishTime) => {
|
|
|
|
|
+ if (!callTime || !finishTime) return "-";
|
|
|
|
|
+ return ((finishTime - callTime) / 1000).toFixed(2) + "s";
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 获取数据
|
|
|
|
|
+ const fetchData = async (params = {}) => {
|
|
|
|
|
+ setLoading(true);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const queryParams = {
|
|
|
|
|
+ page: pagination.current,
|
|
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
|
|
+ ...searchParams,
|
|
|
|
|
+ ...params,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理时间范围
|
|
|
|
|
+ if (searchParams.timeRange && searchParams.timeRange.length === 2) {
|
|
|
|
|
+ queryParams.startTime = searchParams.timeRange[0].valueOf();
|
|
|
|
|
+ queryParams.endTime = searchParams.timeRange[1].valueOf();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const response = await toolsCallLogApi.getList(queryParams);
|
|
|
|
|
+ const { data: list, total } = response.data;
|
|
|
|
|
+
|
|
|
|
|
+ setData(list);
|
|
|
|
|
+ setPagination(prev => ({
|
|
|
|
|
+ ...prev,
|
|
|
|
|
+ total,
|
|
|
|
|
+ }));
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ message.error("获取数据失败");
|
|
|
|
|
+ console.error("获取工具调用日志失败:", error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索
|
|
|
|
|
+ const handleSearch = () => {
|
|
|
|
|
+ setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
|
|
+ fetchData({ page: 1 });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 重置搜索
|
|
|
|
|
+ const handleReset = () => {
|
|
|
|
|
+ setSearchParams({
|
|
|
|
|
+ user: "",
|
|
|
|
|
+ mcp_tools_name: "",
|
|
|
|
|
+ status: "",
|
|
|
|
|
+ timeRange: null,
|
|
|
|
|
+ });
|
|
|
|
|
+ setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ fetchData({
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ user: "",
|
|
|
|
|
+ mcp_tools_name: "",
|
|
|
|
|
+ status: "",
|
|
|
|
|
+ timeRange: null,
|
|
|
|
|
+ });
|
|
|
|
|
+ }, 0);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 查看详情
|
|
|
|
|
+ const handleViewDetail = (record) => {
|
|
|
|
|
+ navigate(`/tools-call-log/detail/${record.id}`);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 表格列配置
|
|
|
|
|
+ const columns = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "用户名",
|
|
|
|
|
+ dataIndex: "user",
|
|
|
|
|
+ key: "user",
|
|
|
|
|
+ width: 120,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "工具名称",
|
|
|
|
|
+ dataIndex: "mcp_tools_name",
|
|
|
|
|
+ key: "mcp_tools_name",
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "状态",
|
|
|
|
|
+ dataIndex: "status",
|
|
|
|
|
+ key: "status",
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ render: (status) => (
|
|
|
|
|
+ <Tag color={status === "success" ? "green" : "red"}>
|
|
|
|
|
+ {status === "success" ? "成功" : "失败"}
|
|
|
|
|
+ </Tag>
|
|
|
|
|
+ ),
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "失败原因",
|
|
|
|
|
+ dataIndex: "fail_msg",
|
|
|
|
|
+ key: "fail_msg",
|
|
|
|
|
+ width: 200,
|
|
|
|
|
+ render: (text) => text || "-",
|
|
|
|
|
+ ellipsis: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "调用时间",
|
|
|
|
|
+ dataIndex: "call_timestamp",
|
|
|
|
|
+ key: "call_timestamp",
|
|
|
|
|
+ width: 160,
|
|
|
|
|
+ render: formatTimestamp,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "完成时间",
|
|
|
|
|
+ dataIndex: "finish_timestamp",
|
|
|
|
|
+ key: "finish_timestamp",
|
|
|
|
|
+ width: 160,
|
|
|
|
|
+ render: formatTimestamp,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "耗时",
|
|
|
|
|
+ key: "duration",
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ render: (_, record) => calculateDuration(record.call_timestamp, record.finish_timestamp),
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "操作",
|
|
|
|
|
+ key: "action",
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ render: (_, record) => (
|
|
|
|
|
+ <Button type="link" onClick={() => handleViewDetail(record)}>
|
|
|
|
|
+ 详情
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ ),
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 分页变化
|
|
|
|
|
+ const handleTableChange = (paginationConfig) => {
|
|
|
|
|
+ setPagination(prev => ({
|
|
|
|
|
+ ...prev,
|
|
|
|
|
+ current: paginationConfig.current,
|
|
|
|
|
+ pageSize: paginationConfig.pageSize,
|
|
|
|
|
+ }));
|
|
|
|
|
+ fetchData({
|
|
|
|
|
+ page: paginationConfig.current,
|
|
|
|
|
+ pageSize: paginationConfig.pageSize,
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ fetchData();
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div style={{ padding: "24px" }}>
|
|
|
|
|
+ <h2>工具调用日志</h2>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 搜索区域 */}
|
|
|
|
|
+ <div style={{ marginBottom: "16px", padding: "16px", background: "#fafafa", borderRadius: "6px" }}>
|
|
|
|
|
+ <Space wrap>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ placeholder="用户名"
|
|
|
|
|
+ value={searchParams.user}
|
|
|
|
|
+ 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 }))}
|
|
|
|
|
+ style={{ width: 150 }}
|
|
|
|
|
+ />
|
|
|
|
|
+ <Select
|
|
|
|
|
+ placeholder="状态"
|
|
|
|
|
+ value={searchParams.status}
|
|
|
|
|
+ onChange={(value) => setSearchParams(prev => ({ ...prev, status: value }))}
|
|
|
|
|
+ style={{ width: 120 }}
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ >
|
|
|
|
|
+ <Option value="success">成功</Option>
|
|
|
|
|
+ <Option value="failure">失败</Option>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ <RangePicker
|
|
|
|
|
+ placeholder={["开始时间", "结束时间"]}
|
|
|
|
|
+ value={searchParams.timeRange}
|
|
|
|
|
+ onChange={(dates) => setSearchParams(prev => ({ ...prev, timeRange: dates }))}
|
|
|
|
|
+ showTime
|
|
|
|
|
+ format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
|
|
|
|
|
+ 搜索
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button icon={<ReloadOutlined />} onClick={handleReset}>
|
|
|
|
|
+ 重置
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </Space>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 表格 */}
|
|
|
|
|
+ <Table
|
|
|
|
|
+ columns={columns}
|
|
|
|
|
+ dataSource={data}
|
|
|
|
|
+ rowKey="id"
|
|
|
|
|
+ loading={loading}
|
|
|
|
|
+ pagination={{
|
|
|
|
|
+ ...pagination,
|
|
|
|
|
+ showSizeChanger: true,
|
|
|
|
|
+ showQuickJumper: true,
|
|
|
|
|
+ showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`,
|
|
|
|
|
+ }}
|
|
|
|
|
+ onChange={handleTableChange}
|
|
|
|
|
+ scroll={{ x: 1200 }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default ToolsCallLogList;
|