三方系统密码加密
This commit is contained in:
parent
a974b6fea4
commit
3f061e996e
@ -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,24 +10,64 @@ 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) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
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(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
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<
|
||||
|
||||
@ -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>
|
||||
|
||||
{/* 部署统计信息 */}
|
||||
|
||||
@ -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,13 +81,7 @@ const formSchema = z.object({
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
interface NotificationChannel {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface TeamEnvironmentConfigDialogProps {
|
||||
interface TeamEnvironmentConfigDialogProps{
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
teamId: number;
|
||||
@ -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}
|
||||
|
||||
@ -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,13 +214,28 @@ export const TeamEnvironmentManageDialog: React.FC<
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{config.notificationEnabled ? (
|
||||
<Badge variant="outline">
|
||||
{config.notificationChannelName || '已启用'}
|
||||
</Badge>
|
||||
{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>
|
||||
@ -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}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 团队应用关联相关 ====================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user