三方系统密码加密
This commit is contained in:
parent
3f061e996e
commit
62523bc382
@ -0,0 +1,390 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogBody,
|
||||
DialogDescription,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import {
|
||||
getTeamEnvironmentConfig,
|
||||
updateTeamEnvironmentConfig,
|
||||
getNotificationChannels,
|
||||
} from '../service';
|
||||
|
||||
// 表单验证 Schema
|
||||
const formSchema = z
|
||||
.object({
|
||||
notificationConfig: z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
notificationChannelId: z.number().nullish(),
|
||||
deployNotificationEnabled: z.boolean().default(false),
|
||||
buildNotificationEnabled: z.boolean().default(false),
|
||||
buildFailureFileEnabled: z.boolean().default(false),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// 如果启用了部署通知或构建通知,则通知渠道必填
|
||||
if (
|
||||
(data.notificationConfig?.deployNotificationEnabled ||data.notificationConfig?.buildNotificationEnabled) && !data.notificationConfig?.notificationChannelId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: '启用通知时必须选择通知渠道',
|
||||
path: ['notificationConfig', 'notificationChannelId'],
|
||||
}
|
||||
);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
interface NotificationChannel {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface NotificationConfigDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
teamId: number;
|
||||
environmentId: number;
|
||||
environmentName: string;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export const NotificationConfigDialog: React.FC<
|
||||
NotificationConfigDialogProps
|
||||
> = ({ open, onOpenChange, teamId, environmentId, environmentName, onSuccess }) => {
|
||||
const { toast } = useToast();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [configId, setConfigId] = useState<number | null>(null);
|
||||
const [currentConfig, setCurrentConfig] = useState<any>(null); // 存储完整的环境配置
|
||||
const [notificationChannels, setNotificationChannels] = useState<
|
||||
NotificationChannel[]
|
||||
>([]);
|
||||
const [loadingChannels, setLoadingChannels] = useState(false);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
notificationConfig: {
|
||||
notificationChannelId: undefined,
|
||||
deployNotificationEnabled: false,
|
||||
buildNotificationEnabled: false,
|
||||
buildFailureFileEnabled: false
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 加载通知渠道列表
|
||||
const loadNotificationChannels = async () => {
|
||||
setLoadingChannels(true);
|
||||
try {
|
||||
const channels = await getNotificationChannels();
|
||||
setNotificationChannels(channels || []);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: '加载失败',
|
||||
description: error.message || '无法加载通知渠道列表',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setLoadingChannels(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载环境配置
|
||||
const loadEnvironmentConfig = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const config = await getTeamEnvironmentConfig(teamId, environmentId);
|
||||
|
||||
if (config) {
|
||||
setConfigId(config.id);
|
||||
setCurrentConfig(config); // 保存完整配置
|
||||
form.reset({
|
||||
notificationConfig: config.notificationConfig || {
|
||||
notificationChannelId: undefined,
|
||||
deployNotificationEnabled: false,
|
||||
buildNotificationEnabled: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
// 配置不存在
|
||||
toast({
|
||||
title: '提示',
|
||||
description: '请先配置该环境的基本信息',
|
||||
variant: 'destructive',
|
||||
});
|
||||
onOpenChange(false);
|
||||
} else {
|
||||
toast({
|
||||
title: '加载失败',
|
||||
description: error.message || '无法加载环境配置',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 对话框打开时初始化
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
loadNotificationChannels();
|
||||
loadEnvironmentConfig();
|
||||
}
|
||||
}, [open, teamId, environmentId]);
|
||||
|
||||
const handleSubmit = async (data: FormData) => {
|
||||
if (!configId || !currentConfig) {
|
||||
toast({
|
||||
title: '错误',
|
||||
description: '请先配置该环境的基本信息',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
// 提交完整的环境配置,只更新通知部分
|
||||
const payload: any = {
|
||||
teamId: currentConfig.teamId,
|
||||
environmentId: currentConfig.environmentId,
|
||||
approvalRequired: currentConfig.approvalRequired,
|
||||
approverUserIds: currentConfig.approverUserIds || [],
|
||||
requireCodeReview: currentConfig.requireCodeReview,
|
||||
remark: currentConfig.remark,
|
||||
// 更新通知配置
|
||||
notificationConfig:
|
||||
data.notificationConfig &&
|
||||
(data.notificationConfig.deployNotificationEnabled ||
|
||||
data.notificationConfig.buildNotificationEnabled ||
|
||||
data.notificationConfig.buildFailureFileEnabled)
|
||||
? {
|
||||
id: data.notificationConfig.id,
|
||||
notificationChannelId:
|
||||
data.notificationConfig.notificationChannelId,
|
||||
deployNotificationEnabled:
|
||||
data.notificationConfig.deployNotificationEnabled,
|
||||
buildNotificationEnabled:
|
||||
data.notificationConfig.buildNotificationEnabled,
|
||||
buildFailureFileEnabled:
|
||||
data.notificationConfig.buildFailureFileEnabled,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
await updateTeamEnvironmentConfig(configId, payload);
|
||||
|
||||
toast({
|
||||
title: '保存成功',
|
||||
description: '通知配置已更新',
|
||||
});
|
||||
|
||||
onSuccess?.();
|
||||
onOpenChange(false);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: '保存失败',
|
||||
description: error.message || '无法保存通知配置',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>通知配置</DialogTitle>
|
||||
<DialogDescription>
|
||||
配置 {environmentName} 环境的通知设置
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<DialogBody>
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
</DialogBody>
|
||||
) : (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<DialogBody className="space-y-4">
|
||||
{/* 通知渠道选择 - 始终显示 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notificationConfig.notificationChannelId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>通知渠道</FormLabel>
|
||||
<Select
|
||||
value={field.value?.toString() || ''}
|
||||
onValueChange={(value) =>
|
||||
field.onChange(value ? Number(value) : undefined)
|
||||
}
|
||||
disabled={loadingChannels}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger
|
||||
clearable
|
||||
hasValue={!!field.value}
|
||||
onClear={() => field.onChange(undefined)}
|
||||
>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
loadingChannels
|
||||
? '加载中...'
|
||||
: '请选择通知渠道'
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{notificationChannels.map((channel) => (
|
||||
<SelectItem
|
||||
key={channel.id}
|
||||
value={channel.id.toString()}
|
||||
>
|
||||
{channel.name} ({channel.type})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
<div className="text-sm text-muted-foreground">
|
||||
选择通知渠道后,启用下方的通知类型
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 部署通知 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notificationConfig.deployNotificationEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">部署通知</FormLabel>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
部署状态变更时发送通知
|
||||
</div>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 构建通知 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notificationConfig.buildNotificationEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">构建通知</FormLabel>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
构建状态变更时发送通知
|
||||
</div>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 构建通知 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notificationConfig.buildFailureFileEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">失败日志</FormLabel>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
构建失败时是否发送日志
|
||||
</div>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter>
|
||||
<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" />
|
||||
)}
|
||||
保存
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user