三方系统密码加密

This commit is contained in:
dengqichen 2025-11-11 18:07:49 +08:00
parent a974b6fea4
commit 3f061e996e
6 changed files with 138 additions and 213 deletions

View File

@ -1,6 +1,6 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { Check, ChevronDown, ChevronUp, X } from "lucide-react"
import { cn } from "@/lib/utils"
@ -10,10 +10,35 @@ const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
interface SelectTriggerProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
clearable?: boolean;
onClear?: () => void;
hasValue?: boolean;
}
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
SelectTriggerProps
>(({ className, children, clearable, onClear, hasValue, ...props }, ref) => {
const clearButtonRef = React.useRef<HTMLSpanElement>(null);
const handleClear = (e: React.MouseEvent | React.PointerEvent) => {
e.preventDefault();
e.stopPropagation();
onClear?.();
};
const handleTriggerPointerDown = (e: React.PointerEvent<HTMLButtonElement>) => {
// 如果点击的是清空按钮或其子元素,阻止触发器的默认行为
if (clearButtonRef.current && (e.target === clearButtonRef.current || clearButtonRef.current.contains(e.target as Node))) {
e.preventDefault();
e.stopPropagation();
return;
}
props.onPointerDown?.(e);
};
return (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
@ -21,13 +46,28 @@ const SelectTrigger = React.forwardRef<
className
)}
{...props}
onPointerDown={handleTriggerPointerDown}
>
{children}
<div className="flex items-center gap-1">
{clearable && hasValue && (
<span
ref={clearButtonRef}
role="button"
onClick={handleClear}
className="flex items-center justify-center hover:bg-accent rounded-sm p-0.5 transition-colors cursor-pointer z-10"
aria-label="清空选择"
>
<X className="h-3.5 w-3.5 opacity-50 hover:opacity-100" />
</span>
)}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</div>
</SelectPrimitive.Trigger>
))
);
})
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<

View File

@ -7,7 +7,6 @@ import {
Package,
GitBranch,
Rocket,
Server,
CheckCircle2,
XCircle,
TrendingUp,
@ -85,39 +84,6 @@ export const ApplicationCard: React.FC<ApplicationCardProps> = ({
)}
</div>
{/* 工作流 */}
<div className="flex items-center gap-1.5">
<Rocket className="h-3 w-3 shrink-0" />
{app.workflowDefinitionName ? (
<span className="truncate">{app.workflowDefinitionName}</span>
) : (
<Skeleton className="h-3 w-20" />
)}
</div>
{/* Jenkins */}
{app.deploySystemName ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1.5 cursor-default">
<Server className="h-3 w-3 shrink-0" />
<span className="truncate">{app.deploySystemName}</span>
</div>
</TooltipTrigger>
{app.deployJob && (
<TooltipContent>
<p>{app.deploySystemName} ({app.deployJob})</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
) : (
<div className="flex items-center gap-1.5">
<Server className="h-3 w-3 shrink-0" />
<Skeleton className="h-3 w-24" />
</div>
)}
</div>
{/* 部署统计信息 */}

View File

@ -50,7 +50,6 @@ import {
getTeamEnvironmentConfig,
createTeamEnvironmentConfig,
updateTeamEnvironmentConfig,
getNotificationChannels,
} from '../service';
interface User {
@ -64,23 +63,9 @@ const formSchema = z.object({
environmentId: z.number().min(1, '请选择环境'),
approvalRequired: z.boolean().default(false),
approverUserIds: z.array(z.number()).default([]),
notificationChannelId: z.number().nullish(),
notificationEnabled: z.boolean().default(false),
requireCodeReview: z.boolean().default(false),
remark: z.string().max(100, '备注最多100个字符').optional(),
}).refine(
(data) => {
// 如果启用了通知,则通知渠道必填
if (data.notificationEnabled && !data.notificationChannelId) {
return false;
}
return true;
},
{
message: '启用通知时必须选择通知渠道',
path: ['notificationChannelId'],
}
).refine(
(data) => {
// 如果需要审批,则审批人必填
if (data.approvalRequired && (!data.approverUserIds || data.approverUserIds.length === 0)) {
@ -96,12 +81,6 @@ const formSchema = z.object({
type FormData = z.infer<typeof formSchema>;
interface NotificationChannel {
id: number;
name: string;
type: string;
}
interface TeamEnvironmentConfigDialogProps{
open: boolean;
onOpenChange: (open: boolean) => void;
@ -115,8 +94,6 @@ interface TeamEnvironmentConfigDialogProps {
initialData?: {
environmentId: number;
workflowDefinitionId?: number;
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
};
@ -138,10 +115,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [configId, setConfigId] = useState<number | null>(null);
const [notificationChannels, setNotificationChannels] = useState<
NotificationChannel[]
>([]);
const [loadingChannels, setLoadingChannels] = useState(false);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
@ -149,8 +122,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: initialData?.environmentId || editEnvironmentId || 0,
approvalRequired: false,
approverUserIds: [],
notificationChannelId: initialData?.notificationChannelId,
notificationEnabled: initialData?.notificationEnabled || false,
requireCodeReview: initialData?.requireCodeReview || false,
remark: initialData?.remark || '',
},
@ -159,7 +130,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
// 对话框打开时初始化
useEffect(() => {
if (open) {
loadNotificationChannels();
if (editEnvironmentId) {
// 编辑模式或绑定新环境设置环境ID并加载配置
@ -172,8 +142,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: initialData.environmentId,
approvalRequired: false,
approverUserIds: [],
notificationChannelId: initialData.notificationChannelId,
notificationEnabled: initialData.notificationEnabled || false,
requireCodeReview: initialData.requireCodeReview || false,
remark: initialData.remark || '',
});
@ -184,8 +152,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: 0,
approvalRequired: false,
approverUserIds: [],
notificationChannelId: undefined,
notificationEnabled: false,
requireCodeReview: false,
remark: '',
});
@ -206,8 +172,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: config.environmentId,
approvalRequired: config.approvalRequired || false,
approverUserIds: config.approverUserIds || [],
notificationChannelId: config.notificationChannelId,
notificationEnabled: config.notificationEnabled || false,
requireCodeReview: config.requireCodeReview || false,
remark: config.remark || '',
});
@ -220,8 +184,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: editEnvironmentId,
approvalRequired: false,
approverUserIds: [],
notificationChannelId: undefined,
notificationEnabled: false,
requireCodeReview: false,
remark: '',
});
@ -237,22 +199,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
}
};
const loadNotificationChannels = async () => {
setLoadingChannels(true);
try {
const channels = await getNotificationChannels();
setNotificationChannels(channels);
} catch (error) {
toast({
title: '加载失败',
description: '无法加载通知渠道列表',
variant: 'destructive',
});
} finally {
setLoadingChannels(false);
}
};
const handleSubmit = async (data: FormData) => {
console.log('表单提交开始', data);
setSubmitting(true);
@ -263,9 +209,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
approvalRequired: data.approvalRequired,
// 如果不需要审批,清空审批人列表
approverUserIds: data.approvalRequired ? data.approverUserIds : [],
// 如果未启用通知,清空通知渠道
notificationChannelId: data.notificationEnabled ? data.notificationChannelId : undefined,
notificationEnabled: data.notificationEnabled,
requireCodeReview: data.requireCodeReview,
remark: data.remark,
};
@ -493,77 +436,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
/>
)}
{/* 启用通知 */}
<FormField
control={form.control}
name="notificationEnabled"
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={(checked) => {
field.onChange(checked);
// 取消通知时自动清空通知渠道
if (!checked) {
form.setValue('notificationChannelId', undefined);
}
}}
/>
</FormControl>
</FormItem>
)}
/>
{/* 通知渠道选择 - 仅在启用通知时显示 */}
{form.watch('notificationEnabled') && (
<FormField
control={form.control}
name="notificationChannelId"
render={({ field }) => (
<FormItem>
<FormLabel> *</FormLabel>
<Select
value={field.value?.toString() || ''}
onValueChange={(value) =>
field.onChange(value ? Number(value) : undefined)
}
disabled={loadingChannels}
>
<FormControl>
<SelectTrigger>
<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 />
</FormItem>
)}
/>
)}
{/* 需要代码审查 */}
<FormField
control={form.control}

View File

@ -20,12 +20,13 @@ import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { useToast } from '@/components/ui/use-toast';
import { Settings, AppWindow, Trash2, Plus, Loader2 } from 'lucide-react';
import { Settings, AppWindow, Trash2, Plus, Loader2, Bell } from 'lucide-react';
import type { Environment } from '@/pages/Deploy/Environment/List/types';
import type { TeamEnvironmentConfig } from '../types';
import { getTeamEnvironmentConfigs, deleteTeamEnvironmentConfig } from '../service';
import { TeamEnvironmentConfigDialog } from './TeamEnvironmentConfigDialog';
import { TeamApplicationManageDialog } from './TeamApplicationManageDialog';
import { NotificationConfigDialog } from './NotificationConfigDialog';
interface User {
id: number;
@ -69,8 +70,13 @@ export const TeamEnvironmentManageDialog: React.FC<
// 应用管理对话框状态
const [appManageDialogOpen, setAppManageDialogOpen] = useState(false);
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<
number | undefined
>();
number | null
>(null);
// 通知配置对话框状态
const [notificationDialogOpen, setNotificationDialogOpen] = useState(false);
const [notificationEnvironmentId, setNotificationEnvironmentId] = useState<number | null>(null);
const [notificationEnvironmentName, setNotificationEnvironmentName] = useState<string>('');
// 删除确认对话框状态
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@ -123,6 +129,12 @@ export const TeamEnvironmentManageDialog: React.FC<
setAppManageDialogOpen(true);
};
const handleOpenNotificationDialog = (config: TeamEnvironmentConfig) => {
setNotificationEnvironmentId(config.environmentId);
setNotificationEnvironmentName(config.environmentName || '');
setNotificationDialogOpen(true);
};
const handleOpenDeleteDialog = (config: TeamEnvironmentConfig) => {
setDeletingConfig(config);
setDeleteDialogOpen(true);
@ -202,15 +214,30 @@ export const TeamEnvironmentManageDialog: React.FC<
)}
</TableCell>
<TableCell>
{config.notificationEnabled ? (
<Badge variant="outline">
{config.notificationChannelName || '已启用'}
{config.notificationConfig ? (
<div className="flex flex-col gap-1">
{config.notificationConfig.deployNotificationEnabled && (
<Badge variant="outline" className="text-xs">
</Badge>
) : (
)}
{config.notificationConfig.buildNotificationEnabled && (
<Badge variant="outline" className="text-xs">
</Badge>
)}
{!config.notificationConfig.deployNotificationEnabled &&
!config.notificationConfig.buildNotificationEnabled && (
<span className="text-muted-foreground text-sm">
</span>
)}
</div>
) : (
<span className="text-muted-foreground text-sm">
</span>
)}
</TableCell>
<TableCell>
<span className="text-sm">
@ -247,6 +274,14 @@ export const TeamEnvironmentManageDialog: React.FC<
<Settings className="h-4 w-4 mr-1" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenNotificationDialog(config)}
>
<Bell className="h-4 w-4 mr-1" />
</Button>
<Button
variant="ghost"
size="sm"
@ -337,6 +372,18 @@ export const TeamEnvironmentManageDialog: React.FC<
/>
)}
{/* 通知配置对话框 */}
{notificationEnvironmentId && (
<NotificationConfigDialog
open={notificationDialogOpen}
onOpenChange={setNotificationDialogOpen}
teamId={teamId}
environmentId={notificationEnvironmentId}
environmentName={notificationEnvironmentName}
onSuccess={handleConfigSuccess}
/>
)}
{/* 删除确认对话框 */}
<ConfirmDialog
open={deleteDialogOpen}

View File

@ -156,16 +156,7 @@ export const getTeamEnvironmentConfig = (teamId: number, environmentId: number)
/**
*
*/
export const createTeamEnvironmentConfig = (data: {
teamId: number;
environmentId: number;
approvalRequired?: boolean;
approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
}) =>
export const createTeamEnvironmentConfig = (data: TeamEnvironmentConfigRequest) =>
request.post('/api/v1/team-environment-config', data);
/**
@ -173,16 +164,7 @@ export const createTeamEnvironmentConfig = (data: {
*/
export const updateTeamEnvironmentConfig = (
id: number,
data: {
teamId: number;
environmentId: number;
approvalRequired?: boolean;
approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
}
data: TeamEnvironmentConfigRequest
) =>
request.put(`/api/v1/team-environment-config/${id}`, data);

View File

@ -45,6 +45,19 @@ export interface TeamRequest {
// ==================== 团队环境配置相关 ====================
/**
*
*/
export interface NotificationConfig {
id?: number;
teamId?: number;
environmentId?: number;
notificationChannelId?: number;
deployNotificationEnabled?: boolean;
buildNotificationEnabled?: boolean;
notificationChannelName?: string; // 关联数据
}
/**
*
*/
@ -53,13 +66,12 @@ export interface TeamEnvironmentConfig extends BaseResponse {
environmentId: number;
approvalRequired?: boolean;
approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
// 嵌套的通知配置
notificationConfig?: NotificationConfig;
// 关联数据
environmentName?: string;
notificationChannelName?: string;
applicationCount?: number;
}
@ -71,10 +83,16 @@ export interface TeamEnvironmentConfigRequest {
environmentId: number;
approvalRequired?: boolean;
approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
// 嵌套的通知配置
notificationConfig?: {
id?: number;
notificationChannelId?: number;
deployNotificationEnabled?: boolean;
buildNotificationEnabled?: boolean;
buildFailureFileEnabled?: boolean;
};
}
// ==================== 团队应用关联相关 ====================