三方系统密码加密
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