三方系统密码加密

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 React from "react"
import * as SelectPrimitive from "@radix-ui/react-select" 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" import { cn } from "@/lib/utils"
@ -10,10 +10,35 @@ const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value const SelectValue = SelectPrimitive.Value
interface SelectTriggerProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
clearable?: boolean;
onClear?: () => void;
hasValue?: boolean;
}
const SelectTrigger = React.forwardRef< const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> SelectTriggerProps
>(({ className, children, ...props }, ref) => ( >(({ 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 <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
@ -21,13 +46,28 @@ const SelectTrigger = React.forwardRef<
className className
)} )}
{...props} {...props}
onPointerDown={handleTriggerPointerDown}
> >
{children} {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> <SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</div>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)) );
})
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef< const SelectScrollUpButton = React.forwardRef<

View File

@ -7,7 +7,6 @@ import {
Package, Package,
GitBranch, GitBranch,
Rocket, Rocket,
Server,
CheckCircle2, CheckCircle2,
XCircle, XCircle,
TrendingUp, TrendingUp,
@ -85,39 +84,6 @@ export const ApplicationCard: React.FC<ApplicationCardProps> = ({
)} )}
</div> </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> </div>
{/* 部署统计信息 */} {/* 部署统计信息 */}

View File

@ -50,7 +50,6 @@ import {
getTeamEnvironmentConfig, getTeamEnvironmentConfig,
createTeamEnvironmentConfig, createTeamEnvironmentConfig,
updateTeamEnvironmentConfig, updateTeamEnvironmentConfig,
getNotificationChannels,
} from '../service'; } from '../service';
interface User { interface User {
@ -64,23 +63,9 @@ const formSchema = z.object({
environmentId: z.number().min(1, '请选择环境'), environmentId: z.number().min(1, '请选择环境'),
approvalRequired: z.boolean().default(false), approvalRequired: z.boolean().default(false),
approverUserIds: z.array(z.number()).default([]), approverUserIds: z.array(z.number()).default([]),
notificationChannelId: z.number().nullish(),
notificationEnabled: z.boolean().default(false),
requireCodeReview: z.boolean().default(false), requireCodeReview: z.boolean().default(false),
remark: z.string().max(100, '备注最多100个字符').optional(), remark: z.string().max(100, '备注最多100个字符').optional(),
}).refine( }).refine(
(data) => {
// 如果启用了通知,则通知渠道必填
if (data.notificationEnabled && !data.notificationChannelId) {
return false;
}
return true;
},
{
message: '启用通知时必须选择通知渠道',
path: ['notificationChannelId'],
}
).refine(
(data) => { (data) => {
// 如果需要审批,则审批人必填 // 如果需要审批,则审批人必填
if (data.approvalRequired && (!data.approverUserIds || data.approverUserIds.length === 0)) { if (data.approvalRequired && (!data.approverUserIds || data.approverUserIds.length === 0)) {
@ -96,13 +81,7 @@ const formSchema = z.object({
type FormData = z.infer<typeof formSchema>; type FormData = z.infer<typeof formSchema>;
interface NotificationChannel { interface TeamEnvironmentConfigDialogProps{
id: number;
name: string;
type: string;
}
interface TeamEnvironmentConfigDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
teamId: number; teamId: number;
@ -115,8 +94,6 @@ interface TeamEnvironmentConfigDialogProps {
initialData?: { initialData?: {
environmentId: number; environmentId: number;
workflowDefinitionId?: number; workflowDefinitionId?: number;
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean; requireCodeReview?: boolean;
remark?: string; remark?: string;
}; };
@ -138,10 +115,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [configId, setConfigId] = useState<number | null>(null); const [configId, setConfigId] = useState<number | null>(null);
const [notificationChannels, setNotificationChannels] = useState<
NotificationChannel[]
>([]);
const [loadingChannels, setLoadingChannels] = useState(false);
const form = useForm<FormData>({ const form = useForm<FormData>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
@ -149,8 +122,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: initialData?.environmentId || editEnvironmentId || 0, environmentId: initialData?.environmentId || editEnvironmentId || 0,
approvalRequired: false, approvalRequired: false,
approverUserIds: [], approverUserIds: [],
notificationChannelId: initialData?.notificationChannelId,
notificationEnabled: initialData?.notificationEnabled || false,
requireCodeReview: initialData?.requireCodeReview || false, requireCodeReview: initialData?.requireCodeReview || false,
remark: initialData?.remark || '', remark: initialData?.remark || '',
}, },
@ -159,7 +130,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
// 对话框打开时初始化 // 对话框打开时初始化
useEffect(() => { useEffect(() => {
if (open) { if (open) {
loadNotificationChannels();
if (editEnvironmentId) { if (editEnvironmentId) {
// 编辑模式或绑定新环境设置环境ID并加载配置 // 编辑模式或绑定新环境设置环境ID并加载配置
@ -172,8 +142,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: initialData.environmentId, environmentId: initialData.environmentId,
approvalRequired: false, approvalRequired: false,
approverUserIds: [], approverUserIds: [],
notificationChannelId: initialData.notificationChannelId,
notificationEnabled: initialData.notificationEnabled || false,
requireCodeReview: initialData.requireCodeReview || false, requireCodeReview: initialData.requireCodeReview || false,
remark: initialData.remark || '', remark: initialData.remark || '',
}); });
@ -184,8 +152,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: 0, environmentId: 0,
approvalRequired: false, approvalRequired: false,
approverUserIds: [], approverUserIds: [],
notificationChannelId: undefined,
notificationEnabled: false,
requireCodeReview: false, requireCodeReview: false,
remark: '', remark: '',
}); });
@ -206,8 +172,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: config.environmentId, environmentId: config.environmentId,
approvalRequired: config.approvalRequired || false, approvalRequired: config.approvalRequired || false,
approverUserIds: config.approverUserIds || [], approverUserIds: config.approverUserIds || [],
notificationChannelId: config.notificationChannelId,
notificationEnabled: config.notificationEnabled || false,
requireCodeReview: config.requireCodeReview || false, requireCodeReview: config.requireCodeReview || false,
remark: config.remark || '', remark: config.remark || '',
}); });
@ -220,8 +184,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
environmentId: editEnvironmentId, environmentId: editEnvironmentId,
approvalRequired: false, approvalRequired: false,
approverUserIds: [], approverUserIds: [],
notificationChannelId: undefined,
notificationEnabled: false,
requireCodeReview: false, requireCodeReview: false,
remark: '', 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) => { const handleSubmit = async (data: FormData) => {
console.log('表单提交开始', data); console.log('表单提交开始', data);
setSubmitting(true); setSubmitting(true);
@ -263,9 +209,6 @@ export const TeamEnvironmentConfigDialog: React.FC<
approvalRequired: data.approvalRequired, approvalRequired: data.approvalRequired,
// 如果不需要审批,清空审批人列表 // 如果不需要审批,清空审批人列表
approverUserIds: data.approvalRequired ? data.approverUserIds : [], approverUserIds: data.approvalRequired ? data.approverUserIds : [],
// 如果未启用通知,清空通知渠道
notificationChannelId: data.notificationEnabled ? data.notificationChannelId : undefined,
notificationEnabled: data.notificationEnabled,
requireCodeReview: data.requireCodeReview, requireCodeReview: data.requireCodeReview,
remark: data.remark, 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 <FormField
control={form.control} control={form.control}

View File

@ -20,12 +20,13 @@ import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { useToast } from '@/components/ui/use-toast'; 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 { Environment } from '@/pages/Deploy/Environment/List/types';
import type { TeamEnvironmentConfig } from '../types'; import type { TeamEnvironmentConfig } from '../types';
import { getTeamEnvironmentConfigs, deleteTeamEnvironmentConfig } from '../service'; import { getTeamEnvironmentConfigs, deleteTeamEnvironmentConfig } from '../service';
import { TeamEnvironmentConfigDialog } from './TeamEnvironmentConfigDialog'; import { TeamEnvironmentConfigDialog } from './TeamEnvironmentConfigDialog';
import { TeamApplicationManageDialog } from './TeamApplicationManageDialog'; import { TeamApplicationManageDialog } from './TeamApplicationManageDialog';
import { NotificationConfigDialog } from './NotificationConfigDialog';
interface User { interface User {
id: number; id: number;
@ -69,8 +70,13 @@ export const TeamEnvironmentManageDialog: React.FC<
// 应用管理对话框状态 // 应用管理对话框状态
const [appManageDialogOpen, setAppManageDialogOpen] = useState(false); const [appManageDialogOpen, setAppManageDialogOpen] = useState(false);
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState< 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); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@ -123,6 +129,12 @@ export const TeamEnvironmentManageDialog: React.FC<
setAppManageDialogOpen(true); setAppManageDialogOpen(true);
}; };
const handleOpenNotificationDialog = (config: TeamEnvironmentConfig) => {
setNotificationEnvironmentId(config.environmentId);
setNotificationEnvironmentName(config.environmentName || '');
setNotificationDialogOpen(true);
};
const handleOpenDeleteDialog = (config: TeamEnvironmentConfig) => { const handleOpenDeleteDialog = (config: TeamEnvironmentConfig) => {
setDeletingConfig(config); setDeletingConfig(config);
setDeleteDialogOpen(true); setDeleteDialogOpen(true);
@ -202,15 +214,30 @@ export const TeamEnvironmentManageDialog: React.FC<
)} )}
</TableCell> </TableCell>
<TableCell> <TableCell>
{config.notificationEnabled ? ( {config.notificationConfig ? (
<Badge variant="outline"> <div className="flex flex-col gap-1">
{config.notificationChannelName || '已启用'} {config.notificationConfig.deployNotificationEnabled && (
<Badge variant="outline" className="text-xs">
</Badge> </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 className="text-muted-foreground text-sm">
</span> </span>
)} )}
</div>
) : (
<span className="text-muted-foreground text-sm">
</span>
)}
</TableCell> </TableCell>
<TableCell> <TableCell>
<span className="text-sm"> <span className="text-sm">
@ -247,6 +274,14 @@ export const TeamEnvironmentManageDialog: React.FC<
<Settings className="h-4 w-4 mr-1" /> <Settings className="h-4 w-4 mr-1" />
</Button> </Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleOpenNotificationDialog(config)}
>
<Bell className="h-4 w-4 mr-1" />
</Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" 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 <ConfirmDialog
open={deleteDialogOpen} open={deleteDialogOpen}

View File

@ -156,16 +156,7 @@ export const getTeamEnvironmentConfig = (teamId: number, environmentId: number)
/** /**
* *
*/ */
export const createTeamEnvironmentConfig = (data: { export const createTeamEnvironmentConfig = (data: TeamEnvironmentConfigRequest) =>
teamId: number;
environmentId: number;
approvalRequired?: boolean;
approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
}) =>
request.post('/api/v1/team-environment-config', data); request.post('/api/v1/team-environment-config', data);
/** /**
@ -173,16 +164,7 @@ export const createTeamEnvironmentConfig = (data: {
*/ */
export const updateTeamEnvironmentConfig = ( export const updateTeamEnvironmentConfig = (
id: number, id: number,
data: { data: TeamEnvironmentConfigRequest
teamId: number;
environmentId: number;
approvalRequired?: boolean;
approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean;
remark?: string;
}
) => ) =>
request.put(`/api/v1/team-environment-config/${id}`, data); 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; environmentId: number;
approvalRequired?: boolean; approvalRequired?: boolean;
approverUserIds?: number[]; approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean; requireCodeReview?: boolean;
remark?: string; remark?: string;
// 嵌套的通知配置
notificationConfig?: NotificationConfig;
// 关联数据 // 关联数据
environmentName?: string; environmentName?: string;
notificationChannelName?: string;
applicationCount?: number; applicationCount?: number;
} }
@ -71,10 +83,16 @@ export interface TeamEnvironmentConfigRequest {
environmentId: number; environmentId: number;
approvalRequired?: boolean; approvalRequired?: boolean;
approverUserIds?: number[]; approverUserIds?: number[];
notificationChannelId?: number;
notificationEnabled?: boolean;
requireCodeReview?: boolean; requireCodeReview?: boolean;
remark?: string; remark?: string;
// 嵌套的通知配置
notificationConfig?: {
id?: number;
notificationChannelId?: number;
deployNotificationEnabled?: boolean;
buildNotificationEnabled?: boolean;
buildFailureFileEnabled?: boolean;
};
} }
// ==================== 团队应用关联相关 ==================== // ==================== 团队应用关联相关 ====================