增加团队管理页面
This commit is contained in:
parent
bd984da237
commit
e4cabb125a
@ -64,7 +64,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 flex flex-col w-full max-w-lg max-h-[90vh] translate-x-[-50%] translate-y-[-50%] border bg-background shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -85,7 +85,7 @@ const DialogHeader = ({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left flex-shrink-0 px-6 pt-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -93,13 +93,27 @@ const DialogHeader = ({
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogBody = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex-1 overflow-y-auto px-6 py-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogBody.displayName = "DialogBody"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 flex-shrink-0 px-6 pb-6 pt-4 border-t",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -142,6 +156,7 @@ export {
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
|
||||
@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
@ -234,7 +235,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-6xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FolderKanban className="h-5 w-5" />
|
||||
@ -242,6 +243,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
{!editMode ? (
|
||||
<div className="space-y-4">
|
||||
{/* 搜索栏 */}
|
||||
@ -495,18 +497,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</DialogBody>
|
||||
|
||||
{editMode && (
|
||||
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={form.handleSubmit(handleSave)}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
@ -92,11 +93,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{record ? '编辑环境' : '新建环境'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<DialogBody>
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="tenantCode">租户代码 *</Label>
|
||||
<Input
|
||||
@ -160,6 +162,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
@ -123,11 +124,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{record ? '编辑系统' : '新增系统'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<DialogBody>
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">系统名称 *</Label>
|
||||
<Input
|
||||
@ -250,6 +252,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
|
||||
@ -28,6 +28,8 @@ export interface RepositoryGroupResponse extends BaseResponse {
|
||||
repoGroupId?: number;
|
||||
/** 外部系统ID */
|
||||
externalSystemId: number;
|
||||
/** 项目数量 */
|
||||
projectCount?: number;
|
||||
/** 子仓库组列表(用于树形结构) */
|
||||
children?: RepositoryGroupResponse[];
|
||||
}
|
||||
@ -60,6 +62,8 @@ export interface RepositoryProjectResponse extends BaseResponse {
|
||||
repoGroupId?: number;
|
||||
/** 外部系统中的项目ID */
|
||||
repoProjectId?: number;
|
||||
/** 分支数量 */
|
||||
branchCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
@ -239,7 +240,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-6xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FolderKanban className="h-5 w-5" />
|
||||
@ -247,6 +248,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
{!editMode ? (
|
||||
<div className="space-y-4">
|
||||
{/* 搜索和新建 */}
|
||||
@ -370,8 +372,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
<DataTablePagination
|
||||
pageIndex={pageNum}
|
||||
pageSize={pageSize}
|
||||
totalPages={data.totalPages}
|
||||
totalElements={data.totalElements}
|
||||
pageCount={data.totalPages}
|
||||
onPageChange={setPageNum}
|
||||
/>
|
||||
)}
|
||||
@ -514,18 +515,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</DialogBody>
|
||||
|
||||
{editMode && (
|
||||
<div className="flex justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={form.handleSubmit(handleSave)}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@ -0,0 +1,430 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
FormDescription,
|
||||
} from '@/components/ui/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { createScheduleJob, updateScheduleJob } from '../service';
|
||||
import type { ScheduleJobResponse, ScheduleJobRequest, JobCategoryResponse } from '../types';
|
||||
|
||||
interface JobEditDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
job?: ScheduleJobResponse | null;
|
||||
categories: JobCategoryResponse[];
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务编辑对话框
|
||||
*/
|
||||
const JobEditDialog: React.FC<JobEditDialogProps> = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
job,
|
||||
categories,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const [submitting, setSubmitting] = React.useState(false);
|
||||
|
||||
const form = useForm<ScheduleJobRequest>({
|
||||
defaultValues: {
|
||||
jobName: '',
|
||||
jobDescription: '',
|
||||
categoryId: undefined,
|
||||
beanName: '',
|
||||
methodName: '',
|
||||
formDefinitionId: undefined,
|
||||
methodParams: '',
|
||||
cronExpression: '',
|
||||
status: 'DISABLED',
|
||||
concurrent: false,
|
||||
timeoutSeconds: 300,
|
||||
retryCount: 0,
|
||||
alertEmail: '',
|
||||
}
|
||||
});
|
||||
|
||||
// 当对话框打开或job变化时,重置表单
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (job) {
|
||||
// 编辑模式
|
||||
form.reset({
|
||||
jobName: job.jobName,
|
||||
jobDescription: job.jobDescription || '',
|
||||
categoryId: job.categoryId,
|
||||
beanName: job.beanName,
|
||||
methodName: job.methodName,
|
||||
formDefinitionId: job.formDefinitionId,
|
||||
methodParams: job.methodParams || '',
|
||||
cronExpression: job.cronExpression,
|
||||
status: job.status,
|
||||
concurrent: job.concurrent,
|
||||
timeoutSeconds: job.timeoutSeconds || 300,
|
||||
retryCount: job.retryCount,
|
||||
alertEmail: job.alertEmail || '',
|
||||
});
|
||||
} else {
|
||||
// 新建模式
|
||||
form.reset({
|
||||
jobName: '',
|
||||
jobDescription: '',
|
||||
categoryId: undefined,
|
||||
beanName: '',
|
||||
methodName: '',
|
||||
formDefinitionId: undefined,
|
||||
methodParams: '',
|
||||
cronExpression: '',
|
||||
status: 'DISABLED',
|
||||
concurrent: false,
|
||||
timeoutSeconds: 300,
|
||||
retryCount: 0,
|
||||
alertEmail: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [open, job, form]);
|
||||
|
||||
const handleSubmit = async (values: ScheduleJobRequest) => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (job) {
|
||||
await updateScheduleJob(job.id, values);
|
||||
toast({
|
||||
title: '更新成功',
|
||||
description: `任务 "${values.jobName}" 已更新`,
|
||||
});
|
||||
} else {
|
||||
await createScheduleJob(values);
|
||||
toast({
|
||||
title: '创建成功',
|
||||
description: `任务 "${values.jobName}" 已创建`,
|
||||
});
|
||||
}
|
||||
onOpenChange(false);
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: job ? '更新失败' : '创建失败',
|
||||
description: error instanceof Error ? error.message : '未知错误',
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{job ? '编辑定时任务' : '新建定时任务'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="flex flex-col flex-1 min-h-0">
|
||||
<DialogBody className="space-y-4">
|
||||
{/* 基本信息 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="jobName"
|
||||
rules={{ required: '请输入任务名称' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>任务名称 *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入任务名称" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="categoryId"
|
||||
rules={{ required: '请选择任务分类' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>任务分类 *</FormLabel>
|
||||
<Select
|
||||
value={field.value?.toString()}
|
||||
onValueChange={(value) => field.onChange(Number(value))}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择任务分类" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{categories.map((cat) => (
|
||||
<SelectItem key={cat.id} value={cat.id.toString()}>
|
||||
{cat.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="jobDescription"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>任务描述</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder="请输入任务描述" rows={2} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 执行配置 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="beanName"
|
||||
rules={{ required: '请输入Bean名称' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bean名称 *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="例如:myTaskBean" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="methodName"
|
||||
rules={{ required: '请输入方法名称' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>方法名称 *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="例如:execute" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="methodParams"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>方法参数</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder='JSON格式,例如:{"key": "value"}'
|
||||
rows={2}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
JSON格式的方法参数(可选)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 调度配置 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cronExpression"
|
||||
rules={{ required: '请输入Cron表达式' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cron表达式 *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="例如:0 0 1 * * ?" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
例如:0 0 1 * * ? (每天凌晨1点)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="status"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>任务状态</FormLabel>
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="ENABLED">启用</SelectItem>
|
||||
<SelectItem value="DISABLED">禁用</SelectItem>
|
||||
<SelectItem value="PAUSED">暂停</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 高级配置 */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="timeoutSeconds"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>超时时间(秒)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="300"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="retryCount"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>重试次数</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="formDefinitionId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>关联表单ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="可选"
|
||||
value={field.value || ''}
|
||||
onChange={(e) => field.onChange(e.target.value ? Number(e.target.value) : undefined)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="alertEmail"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>告警邮箱</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="任务失败时发送告警的邮箱地址"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="concurrent"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">允许并发执行</FormLabel>
|
||||
<FormDescription>
|
||||
是否允许任务并发执行(同时运行多个实例)
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</DialogBody>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={submitting}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit" disabled={submitting}>
|
||||
{submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{job ? '保存' : '创建'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobEditDialog;
|
||||
|
||||
@ -143,14 +143,14 @@ const JobLogDialog: React.FC<JobLogDialogProps> = ({
|
||||
{/* 筛选栏 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Select
|
||||
value={query.status || ''}
|
||||
onValueChange={(value) => setQuery({ ...query, status: value as LogStatus || undefined, pageNum: 0 })}
|
||||
value={query.status || 'all'}
|
||||
onValueChange={(value) => setQuery({ ...query, status: value !== 'all' ? value as LogStatus : undefined, pageNum: 0 })}
|
||||
>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue placeholder="全部状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">全部状态</SelectItem>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="SUCCESS">成功</SelectItem>
|
||||
<SelectItem value="FAIL">失败</SelectItem>
|
||||
<SelectItem value="TIMEOUT">超时</SelectItem>
|
||||
|
||||
@ -11,12 +11,23 @@ import {
|
||||
Clock, Activity, CheckCircle2, XCircle, FolderKanban, PlayCircle, FileText
|
||||
} from 'lucide-react';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { getScheduleJobs, getJobCategoryList, executeJobNow, pauseJob, resumeJob } from './service';
|
||||
import { getScheduleJobs, getJobCategoryList, executeJobNow, pauseJob, resumeJob, deleteScheduleJob } from './service';
|
||||
import type { ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus } from './types';
|
||||
import type { Page } from '@/types/base';
|
||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||
import JobLogDialog from './components/JobLogDialog';
|
||||
import JobEditDialog from './components/JobEditDialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/**
|
||||
@ -29,7 +40,11 @@ const ScheduleJobList: React.FC = () => {
|
||||
const [categories, setCategories] = useState<JobCategoryResponse[]>([]);
|
||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
||||
const [logDialogOpen, setLogDialogOpen] = useState(false);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [selectedJob, setSelectedJob] = useState<ScheduleJobResponse | null>(null);
|
||||
const [editJob, setEditJob] = useState<ScheduleJobResponse | null>(null);
|
||||
const [deleteJob, setDeleteJob] = useState<ScheduleJobResponse | null>(null);
|
||||
const [query, setQuery] = useState<ScheduleJobQuery>({
|
||||
pageNum: DEFAULT_CURRENT - 1,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
@ -85,6 +100,45 @@ const ScheduleJobList: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 新建任务
|
||||
const handleCreate = () => {
|
||||
setEditJob(null);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
// 编辑任务
|
||||
const handleEdit = (record: ScheduleJobResponse) => {
|
||||
setEditJob(record);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
// 打开删除确认对话框
|
||||
const handleDeleteClick = (record: ScheduleJobResponse) => {
|
||||
setDeleteJob(record);
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
// 确认删除
|
||||
const confirmDelete = async () => {
|
||||
if (!deleteJob) return;
|
||||
try {
|
||||
await deleteScheduleJob(deleteJob.id);
|
||||
toast({
|
||||
title: '删除成功',
|
||||
description: `任务 "${deleteJob.jobName}" 已删除`,
|
||||
});
|
||||
loadData();
|
||||
setDeleteDialogOpen(false);
|
||||
setDeleteJob(null);
|
||||
} catch (error) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: '删除失败',
|
||||
description: error instanceof Error ? error.message : '未知错误',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 立即执行
|
||||
const handleExecute = async (record: ScheduleJobResponse) => {
|
||||
try {
|
||||
@ -228,7 +282,7 @@ const ScheduleJobList: React.FC = () => {
|
||||
<FolderKanban className="h-4 w-4 mr-2" />
|
||||
分类管理
|
||||
</Button>
|
||||
<Button onClick={() => toast({ title: '功能开发中' })}>
|
||||
<Button onClick={handleCreate}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
新建任务
|
||||
</Button>
|
||||
@ -246,14 +300,14 @@ const ScheduleJobList: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
value={query.categoryId?.toString()}
|
||||
onValueChange={(value) => setQuery({ ...query, categoryId: value ? Number(value) : undefined, pageNum: 0 })}
|
||||
value={query.categoryId?.toString() || 'all'}
|
||||
onValueChange={(value) => setQuery({ ...query, categoryId: value !== 'all' ? Number(value) : undefined, pageNum: 0 })}
|
||||
>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectValue placeholder="全部分类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">全部分类</SelectItem>
|
||||
<SelectItem value="all">全部分类</SelectItem>
|
||||
{categories.map((cat) => (
|
||||
<SelectItem key={cat.id} value={cat.id.toString()}>
|
||||
{cat.name}
|
||||
@ -262,14 +316,14 @@ const ScheduleJobList: React.FC = () => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={query.status || ''}
|
||||
onValueChange={(value) => setQuery({ ...query, status: value as JobStatus || undefined, pageNum: 0 })}
|
||||
value={query.status || 'all'}
|
||||
onValueChange={(value) => setQuery({ ...query, status: value !== 'all' ? value as JobStatus : undefined, pageNum: 0 })}
|
||||
>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue placeholder="全部状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">全部状态</SelectItem>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="ENABLED">启用</SelectItem>
|
||||
<SelectItem value="DISABLED">禁用</SelectItem>
|
||||
<SelectItem value="PAUSED">暂停</SelectItem>
|
||||
@ -394,7 +448,7 @@ const ScheduleJobList: React.FC = () => {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => toast({ title: '功能开发中' })}
|
||||
onClick={() => handleEdit(record)}
|
||||
title="编辑"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
@ -403,7 +457,7 @@ const ScheduleJobList: React.FC = () => {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-destructive hover:text-destructive"
|
||||
onClick={() => toast({ title: '功能开发中' })}
|
||||
onClick={() => handleDeleteClick(record)}
|
||||
title="删除"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@ -450,6 +504,66 @@ const ScheduleJobList: React.FC = () => {
|
||||
onOpenChange={setLogDialogOpen}
|
||||
job={selectedJob}
|
||||
/>
|
||||
|
||||
{/* 任务编辑对话框 */}
|
||||
<JobEditDialog
|
||||
open={editDialogOpen}
|
||||
onOpenChange={setEditDialogOpen}
|
||||
job={editJob}
|
||||
categories={categories}
|
||||
onSuccess={loadData}
|
||||
/>
|
||||
|
||||
{/* 删除确认对话框 */}
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认删除定时任务</AlertDialogTitle>
|
||||
<AlertDialogDescription asChild>
|
||||
<div className="space-y-3">
|
||||
<p>您确定要删除以下定时任务吗?此操作无法撤销。</p>
|
||||
{deleteJob && (
|
||||
<div className="rounded-md border p-3 space-y-2 bg-muted/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">任务名称:</span>
|
||||
<span className="font-medium">{deleteJob.jobName}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">Bean名称:</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||
{deleteJob.beanName}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">方法名称:</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||
{deleteJob.methodName}
|
||||
</code>
|
||||
</div>
|
||||
{deleteJob.cronExpression && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">Cron表达式:</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||
{deleteJob.cronExpression}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDelete}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
确认删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
@ -231,7 +232,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-6xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FolderKanban className="h-5 w-5" />
|
||||
@ -239,6 +240,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
{!editMode ? (
|
||||
<div className="space-y-4">
|
||||
{/* 搜索栏 */}
|
||||
@ -488,18 +490,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</DialogBody>
|
||||
|
||||
{editMode && (
|
||||
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={form.handleSubmit(handleSave)}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogBody, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@ -195,7 +195,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
||||
|
||||
return (
|
||||
<Dialog open={visible} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
@ -203,6 +203,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-6">
|
||||
@ -311,6 +312,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => handleClose(false)} disabled={submitting}>
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
@ -107,10 +108,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
}
|
||||
|
||||
if (record) {
|
||||
await updateMenu(record.id, {
|
||||
...formData as MenuRequest,
|
||||
version: record.version
|
||||
});
|
||||
await updateMenu(record.id, formData as MenuRequest);
|
||||
toast({
|
||||
title: '更新成功',
|
||||
description: `菜单 "${formData.name}" 已更新`,
|
||||
@ -156,11 +154,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{record ? '编辑菜单' : '新增菜单'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<DialogBody>
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="type">菜单类型 *</Label>
|
||||
<Select
|
||||
@ -285,6 +284,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
@ -271,7 +272,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-6xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FolderKanban className="h-5 w-5" />
|
||||
@ -279,6 +280,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
{!editMode ? (
|
||||
<div className="space-y-4">
|
||||
{/* 搜索和新建 */}
|
||||
@ -567,18 +569,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</DialogBody>
|
||||
|
||||
{editMode && (
|
||||
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={form.handleSubmit(handleSave)}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogBody, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -73,12 +73,13 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
|
||||
return (
|
||||
<>
|
||||
<Dialog open={visible} onOpenChange={(open) => !open && onCancel()}>
|
||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-6xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>历史执行记录</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-4">
|
||||
<DialogBody>
|
||||
<div>
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className="text-muted-foreground">加载中...</div>
|
||||
@ -137,6 +138,7 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user