You've already forked DataMate
init datamate
This commit is contained in:
359
frontend/src/pages/DataCollection/Create/CreateTask.tsx
Normal file
359
frontend/src/pages/DataCollection/Create/CreateTask.tsx
Normal file
@@ -0,0 +1,359 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Input,
|
||||
Button,
|
||||
Select,
|
||||
Radio,
|
||||
Form,
|
||||
Divider,
|
||||
InputNumber,
|
||||
TimePicker,
|
||||
App,
|
||||
} from "antd";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { createTaskUsingPost } from "../collection.apis";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface ScheduleConfig {
|
||||
type: "immediate" | "scheduled";
|
||||
scheduleType?: "day" | "week" | "month" | "custom";
|
||||
time?: string;
|
||||
dayOfWeek?: string;
|
||||
dayOfMonth?: string;
|
||||
cronExpression?: string;
|
||||
maxRetries?: number;
|
||||
}
|
||||
|
||||
const defaultTemplates = [
|
||||
{
|
||||
id: "nas-to-local",
|
||||
name: "NAS到本地",
|
||||
description: "从NAS文件系统导入数据到本地文件系统",
|
||||
config: {
|
||||
reader: "nasreader",
|
||||
writer: "localwriter",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "obs-to-local",
|
||||
name: "OBS到本地",
|
||||
description: "从OBS文件系统导入数据到本地文件系统",
|
||||
config: {
|
||||
reader: "obsreader",
|
||||
writer: "localwriter",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "web-tolocal",
|
||||
name: "Web到本地",
|
||||
description: "从Web URL导入数据到本地文件系统",
|
||||
config: {
|
||||
reader: "webreader",
|
||||
writer: "localwriter",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function CollectionTaskCreate() {
|
||||
return <DevelopmentInProgress />;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
const { message } = App.useApp();
|
||||
|
||||
const [templateType, setTemplateType] = useState<"default" | "custom">(
|
||||
"default"
|
||||
);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState("");
|
||||
const [customConfig, setCustomConfig] = useState("");
|
||||
|
||||
const [scheduleConfig, setScheduleConfig] = useState<ScheduleConfig>({
|
||||
type: "immediate",
|
||||
maxRetries: 10,
|
||||
scheduleType: "daily",
|
||||
});
|
||||
|
||||
const [isCreateDataset, setIsCreateDataset] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const formData = await form.validateFields();
|
||||
if (templateType === "default" && !selectedTemplate) {
|
||||
window.alert("请选择默认模板");
|
||||
return;
|
||||
}
|
||||
if (templateType === "custom" && !customConfig.trim()) {
|
||||
window.alert("请填写自定义配置");
|
||||
return;
|
||||
}
|
||||
// Create task logic here
|
||||
const params = {
|
||||
...formData,
|
||||
templateType,
|
||||
selectedTemplate: templateType === "default" ? selectedTemplate : null,
|
||||
customConfig: templateType === "custom" ? customConfig : null,
|
||||
scheduleConfig,
|
||||
};
|
||||
console.log("Creating task:", params);
|
||||
await createTaskUsingPost(params);
|
||||
message.success("任务创建成功");
|
||||
navigate("/data/collection");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<Link to="/data/collection">
|
||||
<Button type="text">
|
||||
<ArrowLeft className="w-4 h-4 mr-1" />
|
||||
</Button>
|
||||
</Link>
|
||||
<h1 className="text-xl font-bold bg-clip-text">创建归集任务</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
name: "",
|
||||
datasetName: "",
|
||||
fileFormat: "",
|
||||
description: "",
|
||||
cronExpression: "",
|
||||
retryCount: 3,
|
||||
timeout: 3600,
|
||||
incrementalField: "",
|
||||
}}
|
||||
onValuesChange={(_, allValues) => {
|
||||
// 文件格式变化时重置模板选择
|
||||
if (_.fileFormat !== undefined) setSelectedTemplate("");
|
||||
}}
|
||||
>
|
||||
{/* 基本信息 */}
|
||||
<h2 className="font-medium text-gray-900 text-lg mb-4">基本信息</h2>
|
||||
|
||||
<Form.Item
|
||||
label="任务名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "请输入任务名称" }]}
|
||||
>
|
||||
<Input placeholder="请输入任务名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="描述" name="description">
|
||||
<TextArea placeholder="请输入任务描述" rows={3} />
|
||||
</Form.Item>
|
||||
<Form.Item label="文件格式" name="fileFormat">
|
||||
<Input placeholder="请填写文件格式,使用正则表达式" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 同步配置 */}
|
||||
<h2 className="font-medium text-gray-900 my-4 text-lg">同步配置</h2>
|
||||
<Form.Item label="同步方式">
|
||||
<Radio.Group
|
||||
value={scheduleConfig.type}
|
||||
onChange={(e) =>
|
||||
setScheduleConfig({
|
||||
type: e.target.value as ScheduleConfig["type"],
|
||||
})
|
||||
}
|
||||
>
|
||||
<Radio value="immediate">立即同步</Radio>
|
||||
<Radio value="scheduled">定时同步</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{scheduleConfig.type === "scheduled" && (
|
||||
<div className="w-full grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Form.Item label="调度类型">
|
||||
<Select
|
||||
options={[
|
||||
{ label: "每日", value: "day" },
|
||||
{ label: "每周", value: "week" },
|
||||
{ label: "每月", value: "month" },
|
||||
{ label: "自定义Cron", value: "custom" },
|
||||
]}
|
||||
value={scheduleConfig.scheduleType}
|
||||
onChange={(value) =>
|
||||
setScheduleConfig((prev) => ({
|
||||
...prev,
|
||||
scheduleType: value as ScheduleConfig["scheduleType"],
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
{scheduleConfig.scheduleType === "custom" ? (
|
||||
<Form.Item
|
||||
label="Cron表达式"
|
||||
name="cronExpression"
|
||||
rules={[{ required: true, message: "请输入Cron表达式" }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="例如:0 0 * * * 表示每天午夜执行"
|
||||
value={scheduleConfig.cronExpression}
|
||||
onChange={(e) =>
|
||||
setScheduleConfig((prev) => ({
|
||||
...prev,
|
||||
cronExpression: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item label="执行时间" className="w-full">
|
||||
{scheduleConfig.scheduleType === "day" ? (
|
||||
<TimePicker />
|
||||
) : (
|
||||
<Select
|
||||
options={
|
||||
scheduleConfig.scheduleType === "week"
|
||||
? [
|
||||
{ label: "周一", value: "1" },
|
||||
{ label: "周二", value: "2" },
|
||||
{ label: "周三", value: "3" },
|
||||
{ label: "周四", value: "4" },
|
||||
{ label: "周五", value: "5" },
|
||||
{ label: "周六", value: "6" },
|
||||
{ label: "周日", value: "0" },
|
||||
]
|
||||
: [
|
||||
{ label: "每月1日", value: "1" },
|
||||
{ label: "每月5日", value: "5" },
|
||||
{ label: "每月10日", value: "10" },
|
||||
{ label: "每月15日", value: "15" },
|
||||
{ label: "每月20日", value: "20" },
|
||||
{ label: "每月25日", value: "25" },
|
||||
{ label: "每月30日", value: "30" },
|
||||
]
|
||||
}
|
||||
placeholder={
|
||||
scheduleConfig.scheduleType === "week"
|
||||
? "选择星期几"
|
||||
: "选择日期"
|
||||
}
|
||||
value={scheduleConfig.dayOfWeek}
|
||||
onChange={(value) =>
|
||||
setScheduleConfig((prev) => ({
|
||||
...prev,
|
||||
dayOfWeek: value as string,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Form.Item label="最大执行次数">
|
||||
<InputNumber
|
||||
min={1}
|
||||
value={scheduleConfig.maxRetries}
|
||||
onChange={(value) =>
|
||||
setScheduleConfig((prev) => ({
|
||||
...prev,
|
||||
maxRetries: value,
|
||||
}))
|
||||
}
|
||||
className="w-full"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 模板配置 */}
|
||||
<h2 className="font-medium text-gray-900 my-4 text-lg">模板配置</h2>
|
||||
<Form.Item label="模板类型">
|
||||
<Radio.Group
|
||||
value={templateType}
|
||||
onChange={(e) => setTemplateType(e.target.value)}
|
||||
>
|
||||
<Radio value="default">使用默认模板</Radio>
|
||||
<Radio value="custom">自定义DataX JSON配置</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{templateType === "default" && (
|
||||
<Form.Item label="选择模板">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{defaultTemplates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`border p-4 rounded-md hover:shadow-lg transition-shadow ${
|
||||
selectedTemplate === template.id
|
||||
? "border-blue-500"
|
||||
: "border-gray-300"
|
||||
}`}
|
||||
onClick={() => setSelectedTemplate(template.id)}
|
||||
>
|
||||
<div className="font-medium">{template.name}</div>
|
||||
<div className="text-gray-500">{template.description}</div>
|
||||
<div className="text-gray-400">
|
||||
{template.config.reader} → {template.config.writer}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{templateType === "custom" && (
|
||||
<Form.Item label="DataX JSON配置">
|
||||
<TextArea
|
||||
placeholder="请输入DataX JSON配置..."
|
||||
value={customConfig}
|
||||
onChange={(e) => setCustomConfig(e.target.value)}
|
||||
rows={12}
|
||||
className="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{/* 数据集配置 */}
|
||||
{templateType === "default" && (
|
||||
<>
|
||||
<h2 className="font-medium text-gray-900 my-4 text-lg">
|
||||
数据集配置
|
||||
</h2>
|
||||
<Form.Item
|
||||
label="是否创建数据集"
|
||||
name="createDataset"
|
||||
required
|
||||
rules={[{ required: true, message: "请选择是否创建数据集" }]}
|
||||
>
|
||||
<Radio.Group
|
||||
value={isCreateDataset}
|
||||
onChange={(e) => setIsCreateDataset(e.target.value)}
|
||||
>
|
||||
<Radio value={true}>是</Radio>
|
||||
<Radio value={false}>否</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{isCreateDataset && (
|
||||
<>
|
||||
<Form.Item
|
||||
label="数据集名称"
|
||||
name="datasetName"
|
||||
rules={[{ required: true, message: "请输入数据集名称" }]}
|
||||
>
|
||||
<Input placeholder="请输入数据集名称" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 提交按钮 */}
|
||||
<Divider />
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={() => navigate("/data/collection")}>取消</Button>
|
||||
<Button type="primary" onClick={handleSubmit}>
|
||||
创建任务
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
frontend/src/pages/DataCollection/Home/DataCollection.tsx
Normal file
44
frontend/src/pages/DataCollection/Home/DataCollection.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useState } from "react";
|
||||
import { Button, Tabs } from "antd";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import TaskManagement from "./components/TaskManagement";
|
||||
import ExecutionLog from "./components/ExecutionLog";
|
||||
import { useNavigate } from "react-router";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
export default function DataCollection() {
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState("task-management");
|
||||
|
||||
return <DevelopmentInProgress />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-gray-900 mb-2">数据归集</h1>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => navigate("/data/collection/create-task")}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
创建任务
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
items={[
|
||||
{ label: "任务管理", key: "task-management" },
|
||||
{ label: "执行日志", key: "execution-log" },
|
||||
]}
|
||||
onChange={(tab) => {
|
||||
setActiveTab(tab);
|
||||
}}
|
||||
/>
|
||||
{activeTab === "task-management" ? <TaskManagement /> : <ExecutionLog />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Card, Badge, Table } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import type { CollectionLog } from "@/pages/DataCollection/collection.model";
|
||||
import { queryExecutionLogUsingPost } from "../../collection.apis";
|
||||
import { LogStatusMap, LogTriggerTypeMap } from "../../collection.const";
|
||||
import useFetchData from "@/hooks/useFetchData";
|
||||
|
||||
const filterOptions = [
|
||||
{
|
||||
key: "status",
|
||||
label: "状态筛选",
|
||||
options: Object.values(LogStatusMap),
|
||||
},
|
||||
{
|
||||
key: "triggerType",
|
||||
label: "触发类型",
|
||||
options: Object.values(LogTriggerTypeMap),
|
||||
},
|
||||
];
|
||||
|
||||
export default function ExecutionLog() {
|
||||
const handleReset = () => {
|
||||
setSearchParams({
|
||||
keyword: "",
|
||||
filters: {},
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
dateRange: null,
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
loading,
|
||||
tableData,
|
||||
pagination,
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
handleFiltersChange,
|
||||
} = useFetchData(queryExecutionLogUsingPost);
|
||||
|
||||
const columns: ColumnsType<CollectionLog> = [
|
||||
{
|
||||
title: "任务名称",
|
||||
dataIndex: "taskName",
|
||||
key: "taskName",
|
||||
fixed: "left",
|
||||
render: (text: string) => <span style={{ fontWeight: 500 }}>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
render: (status: string) => (
|
||||
<Badge
|
||||
text={LogStatusMap[status]?.label}
|
||||
color={LogStatusMap[status]?.color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "触发类型",
|
||||
dataIndex: "triggerType",
|
||||
key: "triggerType",
|
||||
render: (type: string) => LogTriggerTypeMap[type].label,
|
||||
},
|
||||
{
|
||||
title: "开始时间",
|
||||
dataIndex: "startTime",
|
||||
key: "startTime",
|
||||
},
|
||||
{
|
||||
title: "结束时间",
|
||||
dataIndex: "endTime",
|
||||
key: "endTime",
|
||||
},
|
||||
{
|
||||
title: "执行时长",
|
||||
dataIndex: "duration",
|
||||
key: "duration",
|
||||
},
|
||||
{
|
||||
title: "重试次数",
|
||||
dataIndex: "retryCount",
|
||||
key: "retryCount",
|
||||
},
|
||||
{
|
||||
title: "进程ID",
|
||||
dataIndex: "processId",
|
||||
key: "processId",
|
||||
render: (text: string) => (
|
||||
<span style={{ fontFamily: "monospace" }}>{text}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "错误信息",
|
||||
dataIndex: "errorMessage",
|
||||
key: "errorMessage",
|
||||
render: (msg?: string) =>
|
||||
msg ? (
|
||||
<span style={{ color: "#f5222d" }} title={msg}>
|
||||
{msg}
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: "#bbb" }}>-</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Filter Controls */}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<SearchControls
|
||||
searchTerm={searchParams.keyword}
|
||||
onSearchChange={(keyword: string) =>
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
})
|
||||
}
|
||||
filters={filterOptions}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
showViewToggle={false}
|
||||
onClearFilters={() =>
|
||||
setSearchParams((prev) => ({
|
||||
...prev,
|
||||
filters: {},
|
||||
}))
|
||||
}
|
||||
showDatePicker
|
||||
dateRange={searchParams.dateRange || [null, null]}
|
||||
onDateChange={(date) =>
|
||||
setSearchParams((prev) => ({ ...prev, dateRange: date }))
|
||||
}
|
||||
onReload={handleReset}
|
||||
searchPlaceholder="搜索任务名称、进程ID或错误信息..."
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
<Card>
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={pagination}
|
||||
scroll={{ x: "max-content" }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import { Card, Button, Badge, Table, Dropdown, App } from "antd";
|
||||
import { EllipsisOutlined } from "@ant-design/icons";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import {
|
||||
deleteTaskByIdUsingDelete,
|
||||
executeTaskByIdUsingPost,
|
||||
queryTasksUsingGet,
|
||||
stopTaskByIdUsingPost,
|
||||
} from "../../collection.apis";
|
||||
import { TaskStatus, type CollectionTask } from "../../collection.model";
|
||||
import { StatusMap, SyncModeMap } from "../../collection.const";
|
||||
import useFetchData from "@/hooks/useFetchData";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export default function TaskManagement() {
|
||||
const { message } = App.useApp();
|
||||
const navigate = useNavigate();
|
||||
const filters = [
|
||||
{
|
||||
key: "status",
|
||||
label: "状态筛选",
|
||||
options: [
|
||||
{ value: "all", label: "全部状态" },
|
||||
...Object.values(StatusMap),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const {
|
||||
loading,
|
||||
tableData,
|
||||
pagination,
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
fetchData,
|
||||
handleFiltersChange,
|
||||
} = useFetchData(queryTasksUsingGet);
|
||||
|
||||
const handleStartTask = async (taskId: string) => {
|
||||
await executeTaskByIdUsingPost(taskId);
|
||||
message.success("任务启动请求已发送");
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleStopTask = async (taskId: string) => {
|
||||
await stopTaskByIdUsingPost(taskId);
|
||||
message.success("任务停止请求已发送");
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleDeleteTask = async (taskId: string) => {
|
||||
await deleteTaskByIdUsingDelete(taskId);
|
||||
message.success("任务已删除");
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "任务名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
fixed: "left",
|
||||
render: (text: string, record: CollectionTask) => (
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => navigate("`/data-collection/tasks/${record.id}`)}>")}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
render: (status: string) =>
|
||||
StatusMap[status] ? (
|
||||
<Badge
|
||||
color={StatusMap[status].color}
|
||||
text={StatusMap[status].label}
|
||||
/>
|
||||
) : (
|
||||
<Badge text={status} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "同步方式",
|
||||
dataIndex: "syncMode",
|
||||
key: "syncMode",
|
||||
render: (text: string) => <span>{SyncModeMap[text]?.label}</span>,
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
dataIndex: "createdAt",
|
||||
key: "createdAt",
|
||||
},
|
||||
{
|
||||
title: "更新时间",
|
||||
dataIndex: "updatedAt",
|
||||
key: "updatedAt",
|
||||
},
|
||||
{
|
||||
title: "最近执行ID",
|
||||
dataIndex: "lastExecutionId",
|
||||
key: "lastExecutionId",
|
||||
},
|
||||
{
|
||||
title: "描述",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
fixed: "right" as const,
|
||||
render: (_: any, record: Task) => (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
record.status === TaskStatus.STOPPED
|
||||
? {
|
||||
key: "start",
|
||||
label: "启动",
|
||||
onClick: () => handleStartTask(record.id),
|
||||
}
|
||||
: {
|
||||
key: "stop",
|
||||
label: "停止",
|
||||
onClick: () => handleStopTask(record.id),
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
label: "编辑",
|
||||
onClick: () => handleViewDetail(record),
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
danger: true,
|
||||
onClick: () => handleDeleteTask(record.id),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EllipsisOutlined style={{ fontSize: 20 }} />}
|
||||
/>
|
||||
</Dropdown>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Header Actions */}
|
||||
<SearchControls
|
||||
searchTerm={searchParams.keyword}
|
||||
onSearchChange={(newSearchTerm) =>
|
||||
setSearchParams((prev) => ({
|
||||
...prev,
|
||||
keyword: newSearchTerm,
|
||||
current: 1,
|
||||
}))
|
||||
}
|
||||
searchPlaceholder="搜索任务名称或描述..."
|
||||
filters={filters}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
showViewToggle={false}
|
||||
onClearFilters={() =>
|
||||
setSearchParams((prev) => ({
|
||||
...prev,
|
||||
filters: {},
|
||||
}))
|
||||
}
|
||||
className="mb-4"
|
||||
/>
|
||||
|
||||
{/* Tasks Table */}
|
||||
<Card>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
...pagination,
|
||||
current: searchParams.current,
|
||||
pageSize: searchParams.pageSize,
|
||||
total: pagination.total,
|
||||
}}
|
||||
scroll={{ x: "max-content" }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
60
frontend/src/pages/DataCollection/collection.apis.ts
Normal file
60
frontend/src/pages/DataCollection/collection.apis.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { get, post, put, del } from "@/utils/request";
|
||||
|
||||
// 数据源任务相关接口
|
||||
export function queryTasksUsingGet(params?: any) {
|
||||
return get("/api/data-collection/tasks", params);
|
||||
}
|
||||
|
||||
export function createTaskUsingPost(data: any) {
|
||||
return post("/api/data-collection/tasks", data);
|
||||
}
|
||||
|
||||
export function queryTaskByIdUsingGet(id: string | number) {
|
||||
return get(`/api/data-collection/tasks/${id}`);
|
||||
}
|
||||
|
||||
export function updateTaskByIdUsingPut(
|
||||
id: string | number,
|
||||
data: any
|
||||
) {
|
||||
return put(`/api/data-collection/tasks/${id}`, data);
|
||||
}
|
||||
|
||||
export function queryTaskDetailsByIdUsingGet(id: string | number) {
|
||||
return get(`/api/data-collection/tasks/${id}`);
|
||||
}
|
||||
|
||||
export function queryDataXTemplatesUsingGet(params?: any) {
|
||||
return get("/api/data-collection/templates", params);
|
||||
}
|
||||
export function deleteTaskByIdUsingDelete(id: string | number) {
|
||||
return del(`/api/data-collection/tasks/${id}`);
|
||||
}
|
||||
|
||||
export function executeTaskByIdUsingPost(
|
||||
id: string | number,
|
||||
data?: any
|
||||
) {
|
||||
return post(`/api/data-collection/tasks/${id}/execute`, data);
|
||||
}
|
||||
|
||||
export function stopTaskByIdUsingPost(
|
||||
id: string | number,
|
||||
data?: any
|
||||
) {
|
||||
return post(`/api/data-collection/tasks/${id}/stop`, data);
|
||||
}
|
||||
|
||||
// 执行日志相关接口
|
||||
export function queryExecutionLogUsingPost(params?: any) {
|
||||
return post("/api/data-collection/executions", params);
|
||||
}
|
||||
|
||||
export function queryExecutionLogByIdUsingGet(id: string | number) {
|
||||
return get(`/api/data-collection/executions/${id}`);
|
||||
}
|
||||
|
||||
// 监控统计相关接口
|
||||
export function queryCollectionStatisticsUsingGet(params?: any) {
|
||||
return get("/api/data-collection/monitor/statistics", params);
|
||||
}
|
||||
69
frontend/src/pages/DataCollection/collection.const.ts
Normal file
69
frontend/src/pages/DataCollection/collection.const.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { LogStatus, SyncMode, TaskStatus, TriggerType } from "./collection.model";
|
||||
|
||||
export const StatusMap: Record<
|
||||
TaskStatus,
|
||||
{ label: string; color: string; value: TaskStatus }
|
||||
> = {
|
||||
[TaskStatus.RUNNING]: {
|
||||
label: "运行",
|
||||
color: "blue",
|
||||
value: TaskStatus.RUNNING,
|
||||
},
|
||||
[TaskStatus.STOPPED]: {
|
||||
label: "停止",
|
||||
color: "gray",
|
||||
value: TaskStatus.STOPPED,
|
||||
},
|
||||
[TaskStatus.FAILED]: {
|
||||
label: "错误",
|
||||
color: "red",
|
||||
value: TaskStatus.FAILED,
|
||||
},
|
||||
[TaskStatus.SUCCESS]: {
|
||||
label: "成功",
|
||||
color: "green",
|
||||
value: TaskStatus.SUCCESS,
|
||||
},
|
||||
[TaskStatus.DRAFT]: {
|
||||
label: "草稿",
|
||||
color: "orange",
|
||||
value: TaskStatus.DRAFT,
|
||||
},
|
||||
[TaskStatus.READY]: { label: "就绪", color: "cyan", value: TaskStatus.READY },
|
||||
};
|
||||
|
||||
export const SyncModeMap: Record<SyncMode, { label: string; value: SyncMode }> =
|
||||
{
|
||||
[SyncMode.ONCE]: { label: "立即同步", value: SyncMode.ONCE },
|
||||
[SyncMode.SCHEDULED]: { label: "定时同步", value: SyncMode.SCHEDULED },
|
||||
};
|
||||
|
||||
export const LogStatusMap: Record<
|
||||
LogStatus,
|
||||
{ label: string; color: string; value: LogStatus }
|
||||
> = {
|
||||
[LogStatus.SUCCESS]: {
|
||||
label: "成功",
|
||||
color: "green",
|
||||
value: LogStatus.SUCCESS,
|
||||
},
|
||||
[LogStatus.FAILED]: {
|
||||
label: "失败",
|
||||
color: "red",
|
||||
value: LogStatus.FAILED,
|
||||
},
|
||||
[LogStatus.RUNNING]: {
|
||||
label: "运行中",
|
||||
color: "blue",
|
||||
value: LogStatus.RUNNING,
|
||||
},
|
||||
};
|
||||
|
||||
export const LogTriggerTypeMap: Record<
|
||||
TriggerType,
|
||||
{ label: string; value: TriggerType }
|
||||
> = {
|
||||
[TriggerType.MANUAL]: { label: "手动", value: TriggerType.MANUAL },
|
||||
[TriggerType.SCHEDULED]: { label: "定时", value: TriggerType.SCHEDULED },
|
||||
[TriggerType.API]: { label: "API", value: TriggerType.API },
|
||||
};
|
||||
52
frontend/src/pages/DataCollection/collection.model.ts
Normal file
52
frontend/src/pages/DataCollection/collection.model.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export enum TaskStatus {
|
||||
DRAFT = "DRAFT",
|
||||
READY = "READY",
|
||||
RUNNING = "RUNNING",
|
||||
SUCCESS = "SUCCESS",
|
||||
FAILED = "FAILED",
|
||||
STOPPED = "STOPPED",
|
||||
}
|
||||
|
||||
export enum SyncMode {
|
||||
ONCE = "ONCE",
|
||||
SCHEDULED = "SCHEDULED",
|
||||
}
|
||||
|
||||
export interface CollectionTask {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
config: object; // 具体配置结构根据实际需求定义
|
||||
status: TaskStatus;
|
||||
syncMode: SyncMode;
|
||||
scheduleExpression?: string; // 仅当 syncMode 为 SCHEDULED 时存在
|
||||
lastExecutionId: string;
|
||||
createdAt: string; // ISO date string
|
||||
updatedAt: string; // ISO date string
|
||||
}
|
||||
|
||||
export enum LogStatus {
|
||||
RUNNING = "RUNNING",
|
||||
SUCCESS = "SUCCESS",
|
||||
FAILED = "FAILED",
|
||||
}
|
||||
|
||||
export enum TriggerType {
|
||||
MANUAL = "MANUAL",
|
||||
SCHEDULED = "SCHEDULED",
|
||||
API = "API",
|
||||
}
|
||||
|
||||
export interface CollectionLog {
|
||||
id: string;
|
||||
taskId: string;
|
||||
taskName: string;
|
||||
status: TaskStatus; // 任务执行状态
|
||||
triggerType: TriggerType; // 触发类型,如手动触发、定时触发等
|
||||
startTime: string; // ISO date string
|
||||
endTime: string; // ISO date string
|
||||
duration: string; // 格式化的持续时间字符串
|
||||
retryCount: number;
|
||||
processId: string;
|
||||
errorMessage?: string; // 可选,错误信息
|
||||
}
|
||||
Reference in New Issue
Block a user