fix: data collection create task page (#33)

* feat: Update site name to DataMate and refine text for AI data processing

* feat: Refactor settings page and implement model access functionality

- Created a new ModelAccess component for managing model configurations.
- Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig.
- Added SystemConfig component for managing system settings.
- Implemented WebhookConfig component for managing webhook configurations.
- Updated API functions for model management in settings.apis.ts.
- Adjusted routing to point to the new SettingsPage component.

* feat: Implement Data Collection Page with Task Management and Execution Log

- Created DataCollectionPage component to manage data collection tasks.
- Added TaskManagement and ExecutionLog components for task handling and logging.
- Integrated task operations including start, stop, edit, and delete functionalities.
- Implemented filtering and searching capabilities in task management.
- Introduced SimpleCronScheduler for scheduling tasks with cron expressions.
- Updated CreateTask component to utilize new scheduling and template features.
- Enhanced BasicInformation component to conditionally render fields based on visibility settings.
- Refactored ImportConfiguration component to remove NAS import section.

* feat: Update task creation API endpoint and enhance task creation form with new fields and validation
This commit is contained in:
chenghh-9609
2025-10-28 17:41:59 +08:00
committed by GitHub
parent 3f484e988d
commit 0614157c0b
8 changed files with 213 additions and 135 deletions

View File

@@ -3,7 +3,7 @@ const { addMockPrefix } = require("./mock-core/util.cjs");
const MockAPI = {
// 数据归集接口
queryTasksUsingGet: "/data-collection/tasks", // 获取数据源任务列表
createTaskUsingPost: "/data-collection/tasks/create", // 创建数据源任务
createTaskUsingPost: "/data-collection/tasks", // 创建数据源任务
queryTaskByIdUsingGet: "/data-collection/tasks/:id", // 根据ID获取数据源任务详情
updateTaskByIdUsingPut: "/data-collection/tasks/:id", // 更新数据源任务
queryDataXTemplatesUsingGet: "/data-collection/templates", // 获取DataX数据源模板列表

View File

@@ -4,19 +4,14 @@ import { Link, useNavigate } from "react-router";
import { ArrowLeft } from "lucide-react";
import { createTaskUsingPost } from "../collection.apis";
import SimpleCronScheduler from "@/pages/DataCollection/Create/SimpleCronScheduler";
import RadioCard from "@/components/RadioCard";
import { datasetTypes } from "@/pages/DataManagement/dataset.const";
import { SyncModeMap } from "../collection.const";
import { SyncMode } from "../collection.model";
import { DatasetSubType } from "@/pages/DataManagement/dataset.model";
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",
@@ -47,6 +42,8 @@ const defaultTemplates = [
},
];
const syncModeOptions = Object.values(SyncModeMap);
enum TemplateType {
NAS = "nas",
OBS = "obs",
@@ -64,15 +61,24 @@ export default function CollectionTaskCreate() {
const [selectedTemplate, setSelectedTemplate] = useState("nas");
const [customConfig, setCustomConfig] = useState("");
const [scheduleConfig, setScheduleConfig] = useState<ScheduleConfig>({
type: "immediate",
const [newTask, setNewTask] = useState({
name: "",
description: "",
syncMode: SyncMode.ONCE,
cronExpression: "",
maxRetries: 10,
scheduleType: "daily",
dataset: {},
});
const [scheduleExpression, setScheduleExpression] = useState({
type: SyncMode.SCHEDULED,
time: "00:00",
cronExpression: "0 0 0 * * ?",
});
const [isCreateDataset, setIsCreateDataset] = useState(false);
const handleSubmit = async () => {
try {
const formData = await form.validateFields();
if (templateType === "default" && !selectedTemplate) {
window.alert("请选择默认模板");
@@ -83,17 +89,12 @@ export default function CollectionTaskCreate() {
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);
await createTaskUsingPost(newTask);
message.success("任务创建成功");
navigate("/data/collection");
} catch (error) {
message.error(`${error?.data?.message}${error?.data?.data}`);
}
};
return (
@@ -114,17 +115,16 @@ export default function CollectionTaskCreate() {
<Form
form={form}
layout="vertical"
initialValues={scheduleConfig}
initialValues={newTask}
onValuesChange={(_, allValues) => {
// 文件格式变化时重置模板选择
if (_.fileFormat !== undefined) setSelectedTemplate("");
setNewTask({ ...newTask, ...allValues });
}}
>
{/* 基本信息 */}
<h2 className="font-medium text-gray-900 text-lg mb-2"></h2>
<Form.Item
label="任务名称"
label="名称"
name="name"
rules={[{ required: true, message: "请输入任务名称" }]}
>
@@ -138,32 +138,37 @@ export default function CollectionTaskCreate() {
<h2 className="font-medium text-gray-900 pt-6 mb-2 text-lg">
</h2>
<Form.Item label="同步方式">
<Form.Item name="syncMode" 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>
value={newTask.syncMode}
options={syncModeOptions}
onChange={(e) => {
const value = e.target.value;
setNewTask({
...newTask,
scheduleExpression:
value === SyncMode.SCHEDULED
? scheduleExpression.cronExpression
: "",
});
}}
></Radio.Group>
</Form.Item>
{scheduleConfig.type === "scheduled" && (
{newTask.syncMode === SyncMode.SCHEDULED && (
<Form.Item
label=""
name="cronExpression"
rules={[{ required: true, message: "请输入Cron表达式" }]}
>
<SimpleCronScheduler
className="px-2 rounded"
value={scheduleConfig.cronExpression || "* * * * *"}
showYear
onChange={(value) =>
setScheduleConfig({ ...scheduleConfig, cron: value })
}
value={scheduleExpression}
onChange={(value) => {
setScheduleExpression(value);
setNewTask({
...newTask,
scheduleExpression: value.cronExpression,
});
}}
/>
</Form.Item>
)}
@@ -213,29 +218,25 @@ export default function CollectionTaskCreate() {
{selectedTemplate === TemplateType.NAS && (
<div className="grid grid-cols-2 gap-3 px-2 rounded">
<Form.Item
name="nasPath"
name={["config", "ip"]}
rules={[{ required: true, message: "请输入NAS地址" }]}
label="NAS地址"
>
<Input placeholder="192.168.1.100" />
</Form.Item>
<Form.Item
name="sharePath"
name={["config", "path"]}
rules={[{ required: true, message: "请输入共享路径" }]}
label="共享路径"
>
<Input placeholder="/share/importConfig" />
</Form.Item>
<Form.Item
name="fileList"
name={["config", "files"]}
label="文件列表"
className="col-span-2"
>
<Select
placeholder="请选择文件列表"
mode="tags"
multiple
/>
<Select placeholder="请选择文件列表" mode="tags" />
</Form.Item>
</div>
)}
@@ -309,7 +310,19 @@ export default function CollectionTaskCreate() {
>
<Radio.Group
value={isCreateDataset}
onChange={(e) => setIsCreateDataset(e.target.value)}
onChange={(e) => {
const value = e.target.value;
if (value === false) {
form.setFieldsValue({
dataset: {},
});
setNewTask({
...newTask,
dataset: {},
});
}
setIsCreateDataset(e.target.value);
}}
>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
@@ -319,10 +332,40 @@ export default function CollectionTaskCreate() {
<>
<Form.Item
label="数据集名称"
name="datasetName"
rules={[{ required: true, message: "请输入数据集名称" }]}
name={["dataset", "name"]}
required
>
<Input placeholder="请输入数据集名称" />
<Input
placeholder="输入数据集名称"
onChange={(e) => {
setNewTask({
...newTask,
dataset: {
...newTask.dataset,
name: e.target.value,
},
});
}}
/>
</Form.Item>
<Form.Item
label="数据集类型"
name={["dataset", "datasetType"]}
rules={[{ required: true, message: "请选择数据集类型" }]}
>
<RadioCard
options={datasetTypes}
value={newTask.dataset.datasetType}
onChange={(type) => {
form.setFieldValue(["dataset", "datasetType"], type);
setNewTask({
...newTask,
dataset: {
datasetType: type as DatasetSubType,
},
});
}}
/>
</Form.Item>
</>
)}

View File

@@ -130,6 +130,7 @@ const SimpleCronScheduler: React.FC<SimpleCronSchedulerProps> = ({
return (
<Space direction="vertical" className={`w-full ${className || ""}`}>
{/* 执行周期选择 */}
<div className="grid grid-cols-2 gap-4">
<Form.Item label="执行周期" required>
<Select value={config.type} onChange={handleTypeChange}>
<Select.Option value="once"></Select.Option>
@@ -139,32 +140,6 @@ const SimpleCronScheduler: React.FC<SimpleCronSchedulerProps> = ({
</Select>
</Form.Item>
{/* 时间选择 */}
<Form.Item label="执行时间" required>
<Space wrap>
<TimePicker
format="HH:mm"
value={config.time ? dayjs(config.time, "HH:mm") : null}
onChange={handleTimeChange}
placeholder="选择时间"
/>
<Space wrap className="mt-2">
{commonTimePresets.map((preset) => (
<Button
key={preset.value}
size="small"
className={
config.time === preset.value ? "ant-btn-primary" : ""
}
onClick={() => handleTimePreset(preset.value)}
>
{preset.label}
</Button>
))}
</Space>
</Space>
</Form.Item>
{/* 周几选择 */}
{config.type === "weekly" && (
<Form.Item label="执行日期" required>
@@ -190,6 +165,33 @@ const SimpleCronScheduler: React.FC<SimpleCronSchedulerProps> = ({
></Select>
</Form.Item>
)}
</div>
{/* 时间选择 */}
<Form.Item label="执行时间" required>
<Space wrap>
<TimePicker
format="HH:mm"
value={config.time ? dayjs(config.time, "HH:mm") : null}
onChange={handleTimeChange}
placeholder="选择时间"
/>
<Space wrap className="mt-2">
{commonTimePresets.map((preset) => (
<Button
key={preset.value}
size="small"
className={
config.time === preset.value ? "ant-btn-primary" : ""
}
onClick={() => handleTimePreset(preset.value)}
>
{preset.label}
</Button>
))}
</Space>
</Space>
</Form.Item>
{/* Cron 表达式预览 */}
{/* <div className="mt-4 pt-4 border-t border-gray-200">

View File

@@ -4,14 +4,11 @@ import { PlusOutlined } from "@ant-design/icons";
import TaskManagement from "./TaskManagement";
import ExecutionLog from "./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 showTime="2025.10.30" />;
return (
<div className="gap-4 h-full flex flex-col">
<div className="flex justify-between items-end">
@@ -32,7 +29,7 @@ export default function DataCollection() {
activeKey={activeTab}
items={[
{ label: "任务管理", key: "task-management" },
{ label: "执行日志", key: "execution-log" },
// { label: "执行日志", key: "execution-log" },
]}
onChange={(tab) => {
setActiveTab(tab);

View File

@@ -87,15 +87,15 @@ export default function TaskManagement() {
onClick: () => handleStopTask(record.id),
};
const items = [
isStopped ? startButton : stopButton,
{
key: "edit",
label: "编辑",
icon: <EditOutlined />,
onClick: () => {
showEditTaskModal(record);
},
},
// isStopped ? startButton : stopButton,
// {
// key: "edit",
// label: "编辑",
// icon: <EditOutlined />,
// onClick: () => {
// showEditTaskModal(record);
// },
// },
{
key: "delete",
label: "删除",
@@ -119,11 +119,15 @@ export default function TaskManagement() {
dataIndex: "name",
key: "name",
fixed: "left",
width: 150,
ellipsis: true,
},
{
title: "状态",
dataIndex: "status",
key: "status",
width: 150,
ellipsis: true,
render: (status: string) => (
<Badge text={status.label} color={status.color} />
),
@@ -132,28 +136,37 @@ export default function TaskManagement() {
title: "同步方式",
dataIndex: "syncMode",
key: "syncMode",
width: 150,
ellipsis: true,
render: (text: string) => <span>{SyncModeMap[text]?.label}</span>,
},
{
title: "创建时间",
dataIndex: "createdAt",
key: "createdAt",
width: 150,
ellipsis: true,
},
{
title: "更新时间",
dataIndex: "updatedAt",
key: "updatedAt",
width: 150,
ellipsis: true,
},
{
title: "最近执行ID",
dataIndex: "lastExecutionId",
key: "lastExecutionId",
width: 150,
ellipsis: true,
},
{
title: "描述",
dataIndex: "description",
key: "description",
ellipsis: true,
width: 200,
},
{
title: "操作",
@@ -215,6 +228,7 @@ export default function TaskManagement() {
filters: {},
}))
}
onReload={fetchData}
/>
{/* Tasks Table */}

View File

@@ -13,7 +13,7 @@ import TaskUpload from "./TaskUpload";
const AsiderAndHeaderLayout = () => {
const { pathname } = useLocation();
const navigate = useNavigate();
const [activeItem, setActiveItem] = useState<string>("management");
const [activeItem, setActiveItem] = useState<string>("");
const [sidebarOpen, setSidebarOpen] = useState(true);
const [taskCenterVisible, setTaskCenterVisible] = useState(false);
@@ -33,6 +33,7 @@ const AsiderAndHeaderLayout = () => {
return;
}
}
console.log(pathname);
};
useEffect(() => {
@@ -134,7 +135,15 @@ const AsiderAndHeaderLayout = () => {
</Button>
</Popover>
<Button block onClick={() => navigate("/data/settings")}>
<Button
block
color={pathname === "/data/settings" ? "primary" : "default"}
variant={pathname === "/data/settings" ? "filled" : "outlined"}
onClick={() => {
setActiveItem("");
navigate("/data/settings");
}}
>
</Button>
</div>
@@ -156,7 +165,15 @@ const AsiderAndHeaderLayout = () => {
></Button>
</Popover>
</div>
<Button block onClick={() => navigate("/data/settings")}>
<Button
block
color={pathname === "/data/settings" ? "primary" : "default"}
variant={pathname === "/data/settings" ? "filled" : "outlined"}
onClick={() => {
setActiveItem("");
navigate("/data/settings");
}}
>
<SettingOutlined />
</Button>
</div>

View File

@@ -79,6 +79,7 @@ export default function EnvironmentAccess() {
} = useFetchData(queryModelListUsingGet);
const handleAddModel = async () => {
try {
const formValues = await form.validateFields();
const fn = isEditMode
? () => updateModelByIdUsingPut(newModel.id, formValues)
@@ -87,6 +88,9 @@ export default function EnvironmentAccess() {
setShowModelDialog(false);
fetchData();
message.success("模型添加成功");
} catch (error) {
message.error(`${error?.data?.message}${error?.data?.data}`);
}
};
const [providerOptions, setProviderOptions] = useState<ProviderI[]>([]);
@@ -303,14 +307,6 @@ export default function EnvironmentAccess() {
}}
layout="vertical"
>
<Form.Item
name="modelName"
label="模型名称"
required
rules={[{ required: true, message: "请输入模型名称" }]}
>
<Input placeholder="输入模型名称" />
</Form.Item>
<Form.Item
name="provider"
label="服务提供商"
@@ -342,6 +338,15 @@ export default function EnvironmentAccess() {
>
<Input placeholder="输入接口地址,如:https://api.openai.com" />
</Form.Item>
<Form.Item
name="modelName"
label="模型名称"
required
tooltip="请输入模型名称"
rules={[{ required: true, message: "请输入模型名称" }]}
>
<Input placeholder="输入模型名称" />
</Form.Item>
<Form.Item
name="apiKey"
label="API密钥"

View File

@@ -201,7 +201,7 @@ class Request {
try {
const errorData = await processedResponse.json();
error.data = errorData;
// message.error(`请求失败,错误信息: ${processedResponse.statusText}`);
// message.error(`请求失败,错误信息: ${errorData.message}`);
} catch {
// 忽略JSON解析错误
}