Add frontend UI for Scenario Mode with i18n support

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-11-08 17:31:19 +00:00
parent 712ff80448
commit 37c8e28cba
6 changed files with 1039 additions and 2 deletions

View File

@ -66,6 +66,12 @@ export default {
icon: <IconFont type="ql-icon-log" />, icon: <IconFont type="ql-icon-log" />,
component: '@/pages/log/index', component: '@/pages/log/index',
}, },
{
path: '/scenario',
name: intl.get('场景模式'),
icon: <IconFont type="ql-icon-scenario" />,
component: '@/pages/scenario/index',
},
{ {
path: '/diff', path: '/diff',
name: intl.get('对比工具'), name: intl.get('对比工具'),

View File

@ -521,5 +521,75 @@
"远程仓库缓存": "Remote repository cache", "远程仓库缓存": "Remote repository cache",
"SSH 文件缓存": "SSH file cache", "SSH 文件缓存": "SSH file cache",
"清除依赖缓存": "Clean dependency cache", "清除依赖缓存": "Clean dependency cache",
"清除成功": "Clean successful" "清除成功": "Clean successful",
"场景模式": "Scenario Mode",
"新建场景": "New Scenario",
"编辑场景": "Edit Scenario",
"场景日志": "Scenario Logs",
"确定要删除场景": "Are you sure to delete scenario",
"场景已触发": "Scenario triggered",
"使用此 URL 接收外部触发": "Use this URL to receive external triggers",
"获取 Webhook URL 失败": "Failed to get Webhook URL",
"获取 Webhook": "Get Webhook",
"手动触发": "Manual Trigger",
"查看日志": "View Logs",
"变量监听": "Variable Monitor",
"任务状态": "Task Status",
"时间触发": "Time Trigger",
"系统事件": "System Event",
"描述": "Description",
"触发类型": "Trigger Type",
"执行次数": "Execution Count",
"成功/失败": "Success/Failure",
"最后触发": "Last Triggered",
"监听路径": "Watch Path",
"留空自动生成": "Leave blank to auto-generate",
"Cron 表达式": "Cron Expression",
"事件类型": "Event Type",
"磁盘空间": "Disk Space",
"内存使用": "Memory Usage",
"阈值": "Threshold",
"检查间隔": "Check Interval",
"任务 ID": "Task ID",
"条件配置": "Condition Configuration",
"条件逻辑": "Condition Logic",
"多个条件之间的关系": "Relationship between multiple conditions",
"全部满足": "All satisfied",
"任一满足": "Any satisfied",
"字段名": "Field Name",
"操作符": "Operator",
"包含": "Contains",
"不包含": "Not Contains",
"值": "Value",
"添加条件": "Add Condition",
"动作配置": "Action Configuration",
"动作类型": "Action Type",
"运行任务": "Run Task",
"设置变量": "Set Variable",
"执行命令": "Execute Command",
"发送通知": "Send Notification",
"变量名": "Variable Name",
"变量值": "Variable Value",
"命令": "Command",
"消息": "Message",
"添加动作": "Add Action",
"高级设置": "Advanced Settings",
"延迟执行": "Delay Execution",
"秒": "seconds",
"失败熔断阈值": "Failure Threshold",
"连续失败多少次后自动禁用": "Auto-disable after N consecutive failures",
"最大重试次数": "Max Retry Count",
"重试延迟": "Retry Delay",
"退避倍数": "Backoff Multiplier",
"每次重试延迟的乘数": "Multiplier for retry delay",
"启用": "Enable",
"时间": "Time",
"条件匹配": "Condition Matched",
"执行时间": "Execution Time",
"重试次数": "Retry Count",
"错误信息": "Error Message",
"是": "Yes",
"否": "No",
"共": "Total",
"项": "items"
} }

View File

@ -521,5 +521,75 @@
"远程仓库缓存": "远程仓库缓存", "远程仓库缓存": "远程仓库缓存",
"SSH 文件缓存": "SSH 文件缓存", "SSH 文件缓存": "SSH 文件缓存",
"清除依赖缓存": "清除依赖缓存", "清除依赖缓存": "清除依赖缓存",
"清除成功": "清除成功" "清除成功": "清除成功",
"场景模式": "场景模式",
"新建场景": "新建场景",
"编辑场景": "编辑场景",
"场景日志": "场景日志",
"确定要删除场景": "确定要删除场景",
"场景已触发": "场景已触发",
"使用此 URL 接收外部触发": "使用此 URL 接收外部触发",
"获取 Webhook URL 失败": "获取 Webhook URL 失败",
"获取 Webhook": "获取 Webhook",
"手动触发": "手动触发",
"查看日志": "查看日志",
"变量监听": "变量监听",
"任务状态": "任务状态",
"时间触发": "时间触发",
"系统事件": "系统事件",
"描述": "描述",
"触发类型": "触发类型",
"执行次数": "执行次数",
"成功/失败": "成功/失败",
"最后触发": "最后触发",
"监听路径": "监听路径",
"留空自动生成": "留空自动生成",
"Cron 表达式": "Cron 表达式",
"事件类型": "事件类型",
"磁盘空间": "磁盘空间",
"内存使用": "内存使用",
"阈值": "阈值",
"检查间隔": "检查间隔",
"任务 ID": "任务 ID",
"条件配置": "条件配置",
"条件逻辑": "条件逻辑",
"多个条件之间的关系": "多个条件之间的关系",
"全部满足": "全部满足",
"任一满足": "任一满足",
"字段名": "字段名",
"操作符": "操作符",
"包含": "包含",
"不包含": "不包含",
"值": "值",
"添加条件": "添加条件",
"动作配置": "动作配置",
"动作类型": "动作类型",
"运行任务": "运行任务",
"设置变量": "设置变量",
"执行命令": "执行命令",
"发送通知": "发送通知",
"变量名": "变量名",
"变量值": "变量值",
"命令": "命令",
"消息": "消息",
"添加动作": "添加动作",
"高级设置": "高级设置",
"延迟执行": "延迟执行",
"秒": "秒",
"失败熔断阈值": "失败熔断阈值",
"连续失败多少次后自动禁用": "连续失败多少次后自动禁用",
"最大重试次数": "最大重试次数",
"重试延迟": "重试延迟",
"退避倍数": "退避倍数",
"每次重试延迟的乘数": "每次重试延迟的乘数",
"启用": "启用",
"时间": "时间",
"条件匹配": "条件匹配",
"执行时间": "执行时间",
"重试次数": "重试次数",
"错误信息": "错误信息",
"是": "是",
"否": "否",
"共": "共",
"项": "项"
} }

View File

@ -0,0 +1,318 @@
import React, { useEffect, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import {
Button,
Table,
Space,
Modal,
message,
Tag,
Switch,
Tooltip,
Dropdown,
MenuProps,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
PlayCircleOutlined,
EllipsisOutlined,
FileTextOutlined,
LinkOutlined,
} from '@ant-design/icons';
import { request } from '@/utils/http';
import intl from 'react-intl-universal';
import ScenarioModal from './modal';
import ScenarioLogModal from './logModal';
import dayjs from 'dayjs';
const Scenario = () => {
const [loading, setLoading] = useState(false);
const [scenarios, setScenarios] = useState<any[]>([]);
const [selectedScenario, setSelectedScenario] = useState<any>(null);
const [isModalVisible, setIsModalVisible] = useState(false);
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const fetchScenarios = async () => {
setLoading(true);
try {
const { code, data } = await request.get('/api/scenarios');
if (code === 200) {
setScenarios(data || []);
}
} catch (error) {
console.error('Failed to fetch scenarios:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchScenarios();
}, []);
const handleCreate = () => {
setSelectedScenario(null);
setIsModalVisible(true);
};
const handleEdit = (record: any) => {
setSelectedScenario(record);
setIsModalVisible(true);
};
const handleDelete = (record: any) => {
Modal.confirm({
title: intl.get('确认删除'),
content: `${intl.get('确定要删除场景')} "${record.name}" ${intl.get('吗')}?`,
onOk: async () => {
try {
const { code } = await request.delete('/api/scenarios', {
data: [record.id],
});
if (code === 200) {
message.success(intl.get('删除成功'));
fetchScenarios();
}
} catch (error) {
console.error('Failed to delete scenario:', error);
}
},
});
};
const handleToggleEnabled = async (record: any) => {
try {
const { code } = await request.put('/api/scenarios', {
id: record.id,
isEnabled: record.isEnabled === 1 ? 0 : 1,
});
if (code === 200) {
message.success(intl.get('更新成功'));
fetchScenarios();
}
} catch (error) {
console.error('Failed to toggle scenario:', error);
}
};
const handleTrigger = async (record: any) => {
try {
const { code } = await request.post(`/api/scenarios/${record.id}/trigger`, {});
if (code === 200) {
message.success(intl.get('场景已触发'));
}
} catch (error) {
console.error('Failed to trigger scenario:', error);
}
};
const handleViewLogs = (record: any) => {
setSelectedScenario(record);
setIsLogModalVisible(true);
};
const handleGetWebhook = async (record: any) => {
try {
const { code, data } = await request.get(`/api/scenarios/${record.id}/webhook`);
if (code === 200) {
Modal.info({
title: 'Webhook URL',
content: (
<div>
<p>{intl.get('使用此 URL 接收外部触发')}:</p>
<code style={{ wordBreak: 'break-all' }}>{data.webhookUrl}</code>
</div>
),
width: 600,
});
}
} catch (error) {
message.error(intl.get('获取 Webhook URL 失败'));
}
};
const getTriggerTypeName = (type: string) => {
const types: any = {
variable: intl.get('变量监听'),
webhook: 'Webhook',
task_status: intl.get('任务状态'),
time: intl.get('时间触发'),
system_event: intl.get('系统事件'),
};
return types[type] || type;
};
const getActionsMenu = (record: any): MenuProps => ({
items: [
{
key: 'trigger',
icon: <PlayCircleOutlined />,
label: intl.get('手动触发'),
onClick: () => handleTrigger(record),
},
{
key: 'logs',
icon: <FileTextOutlined />,
label: intl.get('查看日志'),
onClick: () => handleViewLogs(record),
},
...(record.triggerType === 'webhook'
? [
{
key: 'webhook',
icon: <LinkOutlined />,
label: intl.get('获取 Webhook'),
onClick: () => handleGetWebhook(record),
},
]
: []),
{
type: 'divider' as const,
},
{
key: 'edit',
icon: <EditOutlined />,
label: intl.get('编辑'),
onClick: () => handleEdit(record),
},
{
key: 'delete',
icon: <DeleteOutlined />,
label: intl.get('删除'),
danger: true,
onClick: () => handleDelete(record),
},
],
});
const columns = [
{
title: intl.get('名称'),
dataIndex: 'name',
key: 'name',
width: 180,
ellipsis: true,
},
{
title: intl.get('描述'),
dataIndex: 'description',
key: 'description',
ellipsis: true,
},
{
title: intl.get('触发类型'),
dataIndex: 'triggerType',
key: 'triggerType',
width: 120,
render: (type: string) => (
<Tag color="blue">{getTriggerTypeName(type)}</Tag>
),
},
{
title: intl.get('状态'),
dataIndex: 'isEnabled',
key: 'isEnabled',
width: 80,
render: (isEnabled: number, record: any) => (
<Switch
checked={isEnabled === 1}
onChange={() => handleToggleEnabled(record)}
/>
),
},
{
title: intl.get('执行次数'),
dataIndex: 'executionCount',
key: 'executionCount',
width: 100,
render: (count: number) => count || 0,
},
{
title: intl.get('成功/失败'),
key: 'stats',
width: 100,
render: (_: any, record: any) => (
<span>
<Tag color="success">{record.successCount || 0}</Tag>
<Tag color="error">{record.failureCount || 0}</Tag>
</span>
),
},
{
title: intl.get('最后触发'),
dataIndex: 'lastTriggeredAt',
key: 'lastTriggeredAt',
width: 160,
render: (date: string) =>
date ? dayjs(date).format('YYYY-MM-DD HH:mm:ss') : '-',
},
{
title: intl.get('操作'),
key: 'action',
width: 100,
fixed: 'right',
render: (_: any, record: any) => (
<Dropdown menu={getActionsMenu(record)} trigger={['click']}>
<Button type="link" icon={<EllipsisOutlined />} />
</Dropdown>
),
},
];
return (
<PageContainer
header={{
title: intl.get('场景模式'),
extra: [
<Button
key="create"
type="primary"
icon={<PlusOutlined />}
onClick={handleCreate}
>
{intl.get('新建场景')}
</Button>,
],
}}
>
<Table
columns={columns}
dataSource={scenarios}
rowKey="id"
loading={loading}
scroll={{ x: 1200 }}
pagination={{
showSizeChanger: true,
showTotal: (total) => `${intl.get('共')} ${total} ${intl.get('项')}`,
}}
/>
<ScenarioModal
visible={isModalVisible}
scenario={selectedScenario}
onCancel={() => {
setIsModalVisible(false);
setSelectedScenario(null);
}}
onSuccess={() => {
setIsModalVisible(false);
setSelectedScenario(null);
fetchScenarios();
}}
/>
<ScenarioLogModal
visible={isLogModalVisible}
scenario={selectedScenario}
onCancel={() => {
setIsLogModalVisible(false);
setSelectedScenario(null);
}}
/>
</PageContainer>
);
};
export default Scenario;

View File

@ -0,0 +1,130 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, Tag, Typography } from 'antd';
import { request } from '@/utils/http';
import intl from 'react-intl-universal';
import dayjs from 'dayjs';
const { Text } = Typography;
interface ScenarioLogModalProps {
visible: boolean;
scenario: any;
onCancel: () => void;
}
const ScenarioLogModal: React.FC<ScenarioLogModalProps> = ({
visible,
scenario,
onCancel,
}) => {
const [loading, setLoading] = useState(false);
const [logs, setLogs] = useState<any[]>([]);
useEffect(() => {
if (visible && scenario) {
fetchLogs();
}
}, [visible, scenario]);
const fetchLogs = async () => {
setLoading(true);
try {
const { code, data } = await request.get('/api/scenarios/logs', {
params: {
scenarioId: scenario.id,
limit: 100,
},
});
if (code === 200) {
setLogs(data || []);
}
} catch (error) {
console.error('Failed to fetch logs:', error);
} finally {
setLoading(false);
}
};
const columns = [
{
title: intl.get('时间'),
dataIndex: 'createdAt',
key: 'createdAt',
width: 180,
render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: intl.get('状态'),
dataIndex: 'executionStatus',
key: 'executionStatus',
width: 100,
render: (status: string) => {
const colors: any = {
success: 'success',
failure: 'error',
partial: 'warning',
};
return <Tag color={colors[status] || 'default'}>{status}</Tag>;
},
},
{
title: intl.get('条件匹配'),
dataIndex: 'conditionsMatched',
key: 'conditionsMatched',
width: 100,
render: (matched: boolean) => (
<Tag color={matched ? 'success' : 'default'}>
{matched ? intl.get('是') : intl.get('否')}
</Tag>
),
},
{
title: intl.get('执行时间'),
dataIndex: 'executionTime',
key: 'executionTime',
width: 100,
render: (time: number) => (time ? `${time}ms` : '-'),
},
{
title: intl.get('重试次数'),
dataIndex: 'retriesAttempted',
key: 'retriesAttempted',
width: 100,
render: (retries: number) => retries || 0,
},
{
title: intl.get('错误信息'),
dataIndex: 'errorMessage',
key: 'errorMessage',
ellipsis: true,
render: (error: string) => (
<Text type={error ? 'danger' : undefined}>{error || '-'}</Text>
),
},
];
return (
<Modal
title={`${intl.get('场景日志')} - ${scenario?.name || ''}`}
open={visible}
onCancel={onCancel}
footer={null}
width={1000}
destroyOnClose
>
<Table
columns={columns}
dataSource={logs}
rowKey="id"
loading={loading}
pagination={{
pageSize: 20,
showSizeChanger: true,
}}
size="small"
/>
</Modal>
);
};
export default ScenarioLogModal;

View File

@ -0,0 +1,443 @@
import React, { useState, useEffect } from 'react';
import {
Modal,
Form,
Input,
Select,
InputNumber,
Switch,
Button,
Space,
Card,
message,
Divider,
} from 'antd';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { request } from '@/utils/http';
import intl from 'react-intl-universal';
const { TextArea } = Input;
const { Option } = Select;
interface ScenarioModalProps {
visible: boolean;
scenario: any;
onCancel: () => void;
onSuccess: () => void;
}
const ScenarioModal: React.FC<ScenarioModalProps> = ({
visible,
scenario,
onCancel,
onSuccess,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [triggerType, setTriggerType] = useState('time');
useEffect(() => {
if (visible) {
if (scenario) {
form.setFieldsValue({
...scenario,
conditions: scenario.conditions || [],
actions: scenario.actions || [],
});
setTriggerType(scenario.triggerType);
} else {
form.resetFields();
form.setFieldsValue({
isEnabled: 1,
conditionLogic: 'AND',
failureThreshold: 3,
delayExecution: 0,
conditions: [],
actions: [],
});
setTriggerType('time');
}
}
}, [visible, scenario, form]);
const handleSubmit = async () => {
try {
const values = await form.validateFields();
setLoading(true);
const endpoint = scenario ? '/api/scenarios' : '/api/scenarios';
const method = scenario ? 'put' : 'post';
const payload = scenario ? { ...values, id: scenario.id } : values;
const { code } = await request[method](endpoint, payload);
if (code === 200) {
message.success(
scenario ? intl.get('更新成功') : intl.get('创建成功'),
);
onSuccess();
}
} catch (error) {
console.error('Failed to save scenario:', error);
} finally {
setLoading(false);
}
};
const renderTriggerConfig = () => {
switch (triggerType) {
case 'variable':
return (
<Form.Item
name={['triggerConfig', 'watchPath']}
label={intl.get('监听路径')}
rules={[{ required: true }]}
>
<Input placeholder="/path/to/watch" />
</Form.Item>
);
case 'webhook':
return (
<Form.Item name={['triggerConfig', 'token']} label="Token">
<Input
placeholder={intl.get('留空自动生成')}
disabled={!!scenario}
/>
</Form.Item>
);
case 'time':
return (
<Form.Item
name={['triggerConfig', 'schedule']}
label={intl.get('Cron 表达式')}
rules={[{ required: true }]}
>
<Input placeholder="0 0 * * *" />
</Form.Item>
);
case 'system_event':
return (
<>
<Form.Item
name={['triggerConfig', 'eventType']}
label={intl.get('事件类型')}
rules={[{ required: true }]}
>
<Select>
<Option value="disk_space">{intl.get('磁盘空间')}</Option>
<Option value="memory">{intl.get('内存使用')}</Option>
</Select>
</Form.Item>
<Form.Item
name={['triggerConfig', 'threshold']}
label={intl.get('阈值')}
rules={[{ required: true }]}
>
<InputNumber min={0} max={100} addonAfter="%" />
</Form.Item>
<Form.Item
name={['triggerConfig', 'checkInterval']}
label={intl.get('检查间隔')}
>
<InputNumber min={10000} addonAfter="ms" />
</Form.Item>
</>
);
case 'task_status':
return (
<Form.Item
name={['triggerConfig', 'cronId']}
label={intl.get('任务 ID')}
rules={[{ required: true }]}
>
<InputNumber min={1} />
</Form.Item>
);
default:
return null;
}
};
return (
<Modal
title={scenario ? intl.get('编辑场景') : intl.get('新建场景')}
open={visible}
onCancel={onCancel}
onOk={handleSubmit}
confirmLoading={loading}
width={800}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label={intl.get('名称')}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item name="description" label={intl.get('描述')}>
<TextArea rows={2} />
</Form.Item>
<Form.Item
name="triggerType"
label={intl.get('触发类型')}
rules={[{ required: true }]}
>
<Select onChange={setTriggerType}>
<Option value="time">{intl.get('时间触发')}</Option>
<Option value="variable">{intl.get('变量监听')}</Option>
<Option value="webhook">Webhook</Option>
<Option value="task_status">{intl.get('任务状态')}</Option>
<Option value="system_event">{intl.get('系统事件')}</Option>
</Select>
</Form.Item>
{renderTriggerConfig()}
<Divider orientation="left">{intl.get('条件配置')}</Divider>
<Form.Item
name="conditionLogic"
label={intl.get('条件逻辑')}
tooltip={intl.get('多个条件之间的关系')}
>
<Select>
<Option value="AND">AND ({intl.get('全部满足')})</Option>
<Option value="OR">OR ({intl.get('任一满足')})</Option>
</Select>
</Form.Item>
<Form.List name="conditions">
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Card
key={field.key}
size="small"
style={{ marginBottom: 8 }}
extra={
<DeleteOutlined
onClick={() => remove(field.name)}
style={{ color: 'red' }}
/>
}
>
<Space>
<Form.Item
{...field}
name={[field.name, 'field']}
noStyle
rules={[{ required: true }]}
>
<Input
placeholder={intl.get('字段名')}
style={{ width: 150 }}
/>
</Form.Item>
<Form.Item
{...field}
name={[field.name, 'operator']}
noStyle
rules={[{ required: true }]}
>
<Select placeholder={intl.get('操作符')} style={{ width: 120 }}>
<Option value="equals">=</Option>
<Option value="not_equals">!=</Option>
<Option value="greater_than">&gt;</Option>
<Option value="less_than">&lt;</Option>
<Option value="contains">{intl.get('包含')}</Option>
<Option value="not_contains">{intl.get('不包含')}</Option>
</Select>
</Form.Item>
<Form.Item
{...field}
name={[field.name, 'value']}
noStyle
rules={[{ required: true }]}
>
<Input
placeholder={intl.get('值')}
style={{ width: 150 }}
/>
</Form.Item>
</Space>
</Card>
))}
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
{intl.get('添加条件')}
</Button>
</>
)}
</Form.List>
<Divider orientation="left">{intl.get('动作配置')}</Divider>
<Form.List name="actions">
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Card
key={field.key}
size="small"
style={{ marginBottom: 8 }}
extra={
<DeleteOutlined
onClick={() => remove(field.name)}
style={{ color: 'red' }}
/>
}
>
<Form.Item
{...field}
name={[field.name, 'type']}
label={intl.get('动作类型')}
rules={[{ required: true }]}
>
<Select>
<Option value="run_task">{intl.get('运行任务')}</Option>
<Option value="set_variable">{intl.get('设置变量')}</Option>
<Option value="execute_command">{intl.get('执行命令')}</Option>
<Option value="send_notification">{intl.get('发送通知')}</Option>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.actions?.[field.name]?.type !==
currentValues.actions?.[field.name]?.type
}
>
{({ getFieldValue }) => {
const actionType = getFieldValue([
'actions',
field.name,
'type',
]);
if (actionType === 'run_task') {
return (
<Form.Item
{...field}
name={[field.name, 'cronId']}
label={intl.get('任务 ID')}
rules={[{ required: true }]}
>
<InputNumber min={1} style={{ width: '100%' }} />
</Form.Item>
);
}
if (actionType === 'set_variable') {
return (
<>
<Form.Item
{...field}
name={[field.name, 'name']}
label={intl.get('变量名')}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
{...field}
name={[field.name, 'value']}
label={intl.get('变量值')}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
</>
);
}
if (actionType === 'execute_command') {
return (
<Form.Item
{...field}
name={[field.name, 'command']}
label={intl.get('命令')}
rules={[{ required: true }]}
>
<TextArea rows={2} />
</Form.Item>
);
}
if (actionType === 'send_notification') {
return (
<Form.Item
{...field}
name={[field.name, 'message']}
label={intl.get('消息')}
rules={[{ required: true }]}
>
<TextArea rows={2} />
</Form.Item>
);
}
return null;
}}
</Form.Item>
</Card>
))}
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
{intl.get('添加动作')}
</Button>
</>
)}
</Form.List>
<Divider orientation="left">{intl.get('高级设置')}</Divider>
<Form.Item name="delayExecution" label={intl.get('延迟执行')}>
<InputNumber min={0} addonAfter={intl.get('秒')} />
</Form.Item>
<Form.Item
name="failureThreshold"
label={intl.get('失败熔断阈值')}
tooltip={intl.get('连续失败多少次后自动禁用')}
>
<InputNumber min={1} />
</Form.Item>
<Form.Item name={['retryStrategy', 'maxRetries']} label={intl.get('最大重试次数')}>
<InputNumber min={0} max={10} />
</Form.Item>
<Form.Item
name={['retryStrategy', 'retryDelay']}
label={intl.get('重试延迟')}
>
<InputNumber min={1} addonAfter={intl.get('秒')} />
</Form.Item>
<Form.Item
name={['retryStrategy', 'backoffMultiplier']}
label={intl.get('退避倍数')}
tooltip={intl.get('每次重试延迟的乘数')}
>
<InputNumber min={1} step={0.5} />
</Form.Item>
<Form.Item
name="isEnabled"
label={intl.get('启用')}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Form>
</Modal>
);
};
export default ScenarioModal;