三方系统密码加密

This commit is contained in:
dengqichen 2025-11-11 18:08:04 +08:00
parent 3f061e996e
commit 62523bc382

View File

@ -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>
);
};