增加图标展示,区分清楚项目类型
This commit is contained in:
parent
456a4bede2
commit
d65e82b0e8
@ -171,6 +171,37 @@ const SelectItem = React.forwardRef<
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
// 支持副标题的选项组件
|
||||
interface SelectItemWithDescriptionProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> {
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const SelectItemWithDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
SelectItemWithDescriptionProps
|
||||
>(({ className, label, description, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none flex-col items-start rounded-sm py-2 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 top-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText className="font-medium">{label}</SelectPrimitive.ItemText>
|
||||
{description && (
|
||||
<span className="text-xs text-muted-foreground mt-0.5">{description}</span>
|
||||
)}
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItemWithDescription.displayName = "SelectItemWithDescription"
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
@ -225,6 +256,7 @@ export {
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectItemWithDescription,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
|
||||
57
frontend/src/pages/Dashboard/utils/languageIcons.tsx
Normal file
57
frontend/src/pages/Dashboard/utils/languageIcons.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import {
|
||||
CodeOutlined,
|
||||
JavaOutlined,
|
||||
NodeIndexOutlined,
|
||||
PythonOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { DevelopmentLanguageTypeEnum } from '@/pages/Deploy/Application/List/types';
|
||||
|
||||
interface LanguageIconConfig {
|
||||
icon: React.ReactNode;
|
||||
color: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 语言图标配置映射(与 /deploy/applications 保持一致)
|
||||
*/
|
||||
export const LANGUAGE_ICONS: Record<DevelopmentLanguageTypeEnum, LanguageIconConfig> = {
|
||||
JAVA: {
|
||||
icon: <JavaOutlined />,
|
||||
color: '#E76F00',
|
||||
label: 'Java'
|
||||
},
|
||||
NODE_JS: {
|
||||
icon: <NodeIndexOutlined />,
|
||||
color: '#339933',
|
||||
label: 'NodeJS'
|
||||
},
|
||||
PYTHON: {
|
||||
icon: <PythonOutlined />,
|
||||
color: '#3776AB',
|
||||
label: 'Python'
|
||||
},
|
||||
GO: {
|
||||
icon: <CodeOutlined />,
|
||||
color: '#00ADD8',
|
||||
label: 'Go'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取语言图标配置
|
||||
*/
|
||||
export const getLanguageIcon = (language?: DevelopmentLanguageTypeEnum): LanguageIconConfig => {
|
||||
if (!language) {
|
||||
return {
|
||||
icon: <CodeOutlined />,
|
||||
color: '#666666',
|
||||
label: '未知'
|
||||
};
|
||||
}
|
||||
return LANGUAGE_ICONS[language] || {
|
||||
icon: <CodeOutlined />,
|
||||
color: '#666666',
|
||||
label: language
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,254 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Search, Check, ChevronDown } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { RepositoryBranchResponse } from '@/pages/Resource/Git/List/types';
|
||||
|
||||
interface GitConfigSelectorProps {
|
||||
label: string; // "源" 或 "目标"
|
||||
// Git系统
|
||||
gitSystems: any[];
|
||||
selectedSystemId: number | null;
|
||||
onSystemChange: (systemId: number) => void;
|
||||
// 仓库项目
|
||||
repoProjects: any[];
|
||||
loadingRepoProjects: boolean;
|
||||
selectedProjectId: number | null;
|
||||
onProjectChange: (projectId: number) => void;
|
||||
// 分支
|
||||
branches: RepositoryBranchResponse[];
|
||||
loadingBranches: boolean;
|
||||
selectedBranch: string;
|
||||
onBranchChange: (branch: string) => void;
|
||||
}
|
||||
|
||||
export const GitConfigSelector: React.FC<GitConfigSelectorProps> = ({
|
||||
label,
|
||||
gitSystems,
|
||||
selectedSystemId,
|
||||
onSystemChange,
|
||||
repoProjects,
|
||||
loadingRepoProjects,
|
||||
selectedProjectId,
|
||||
onProjectChange,
|
||||
branches,
|
||||
loadingBranches,
|
||||
selectedBranch,
|
||||
onBranchChange,
|
||||
}) => {
|
||||
const [projectSearchValue, setProjectSearchValue] = useState('');
|
||||
const [projectPopoverOpen, setProjectPopoverOpen] = useState(false);
|
||||
const [branchSearchValue, setBranchSearchValue] = useState('');
|
||||
const [branchPopoverOpen, setBranchPopoverOpen] = useState(false);
|
||||
|
||||
// 过滤分支
|
||||
const filteredBranches = branches.filter(branch =>
|
||||
branch.name.toLowerCase().includes(branchSearchValue.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-semibold">{label}Git配置</Label>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{/* 代码系统 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-muted-foreground">{label}代码系统</Label>
|
||||
<Select
|
||||
value={selectedSystemId?.toString() || ''}
|
||||
onValueChange={(value) => onSystemChange(Number(value))}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择系统" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{gitSystems.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
暂无可用的Git系统
|
||||
</div>
|
||||
) : (
|
||||
gitSystems.map((system) => (
|
||||
<SelectItem key={system.id} value={system.id.toString()}>
|
||||
{system.name}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 仓库项目 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-muted-foreground">{label}仓库项目</Label>
|
||||
{selectedSystemId ? (
|
||||
<Popover open={projectPopoverOpen} onOpenChange={setProjectPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
disabled={loadingRepoProjects || repoProjects.length === 0}
|
||||
className={cn(
|
||||
'w-full justify-between',
|
||||
!selectedProjectId && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{selectedProjectId
|
||||
? (() => {
|
||||
const selectedProject = repoProjects.find(
|
||||
(p) => p.repoProjectId === selectedProjectId
|
||||
);
|
||||
return selectedProject
|
||||
? (selectedProject.repoGroupName
|
||||
? `${selectedProject.repoGroupName} / ${selectedProject.name}`
|
||||
: selectedProject.name)
|
||||
: '选择项目';
|
||||
})()
|
||||
: loadingRepoProjects
|
||||
? '加载中...'
|
||||
: repoProjects.length === 0
|
||||
? '暂无项目'
|
||||
: '选择项目'}
|
||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||
<div className="flex items-center border-b px-3">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<input
|
||||
placeholder="搜索项目..."
|
||||
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
||||
value={projectSearchValue}
|
||||
onChange={(e) => setProjectSearchValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<ScrollArea className="h-[200px]">
|
||||
<div className="p-1">
|
||||
{repoProjects
|
||||
.filter(
|
||||
(project) =>
|
||||
project.name.toLowerCase().includes(projectSearchValue.toLowerCase()) ||
|
||||
project.repoGroupName?.toLowerCase().includes(projectSearchValue.toLowerCase())
|
||||
)
|
||||
.map((project) => (
|
||||
<div
|
||||
key={project.repoProjectId}
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground',
|
||||
project.repoProjectId === selectedProjectId && 'bg-accent text-accent-foreground'
|
||||
)}
|
||||
onClick={() => {
|
||||
onProjectChange(project.repoProjectId);
|
||||
setProjectSearchValue('');
|
||||
setProjectPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 truncate">
|
||||
{project.repoGroupName && (
|
||||
<span className="text-muted-foreground">{project.repoGroupName} / </span>
|
||||
)}
|
||||
{project.name}
|
||||
</div>
|
||||
{project.repoProjectId === selectedProjectId && <Check className="ml-2 h-4 w-4" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<Input placeholder="请先选择系统" disabled />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 分支 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs text-muted-foreground">{label}分支</Label>
|
||||
{selectedProjectId ? (
|
||||
<Popover open={branchPopoverOpen} onOpenChange={setBranchPopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
disabled={loadingBranches || branches.length === 0}
|
||||
className={cn('w-full justify-between', !selectedBranch && 'text-muted-foreground')}
|
||||
>
|
||||
{selectedBranch
|
||||
? (() => {
|
||||
const selectedBranchObj = branches.find((b) => b.name === selectedBranch);
|
||||
return (
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
{selectedBranchObj?.name}
|
||||
{selectedBranchObj?.isDefaultBranch && (
|
||||
<span className="text-xs text-muted-foreground">(默认)</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})()
|
||||
: loadingBranches
|
||||
? '加载中...'
|
||||
: branches.length === 0
|
||||
? '无分支'
|
||||
: '选择分支'}
|
||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0">
|
||||
<div className="flex items-center border-b px-3">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<input
|
||||
placeholder="搜索分支..."
|
||||
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
||||
value={branchSearchValue}
|
||||
onChange={(e) => setBranchSearchValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<ScrollArea className="h-[200px]">
|
||||
<div className="p-1">
|
||||
{filteredBranches.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">未找到分支</div>
|
||||
) : (
|
||||
filteredBranches.map((branch) => (
|
||||
<div
|
||||
key={branch.id}
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent',
|
||||
branch.name === selectedBranch && 'bg-accent'
|
||||
)}
|
||||
onClick={() => {
|
||||
onBranchChange(branch.name);
|
||||
setBranchSearchValue('');
|
||||
setBranchPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="flex-1 truncate">
|
||||
{branch.name}
|
||||
{branch.isDefaultBranch && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">(默认)</span>
|
||||
)}
|
||||
</span>
|
||||
{branch.name === selectedBranch && <Check className="ml-2 h-4 w-4" />}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<Input placeholder="请先选择项目" disabled />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -32,6 +32,7 @@ import type { RepositoryBranchResponse } from '@/pages/Resource/Git/List/types';
|
||||
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
|
||||
import { getExternalSystemList } from '@/pages/Resource/External/List/service';
|
||||
import { getRepositoryProjectsList, getRepositoryBranchesList } from '@/pages/Resource/Git/List/service';
|
||||
import { GitConfigSelector } from './GitConfigSelector';
|
||||
|
||||
interface TeamApplicationDialogProps {
|
||||
open: boolean;
|
||||
@ -39,6 +40,7 @@ interface TeamApplicationDialogProps {
|
||||
teamId: number;
|
||||
environmentId: number;
|
||||
environmentName: string;
|
||||
enableGitSyncCheck: boolean; // 🆕 团队是否启用Git同步检测
|
||||
application?: TeamApplication; // 编辑时传入
|
||||
applications: Application[]; // 可选择的应用列表
|
||||
jenkinsSystems: any[];
|
||||
@ -55,6 +57,9 @@ interface TeamApplicationDialogProps {
|
||||
workflowDefinitionId: number | null;
|
||||
codeSourceSystemId: number | null;
|
||||
codeSourceProjectId: number | null;
|
||||
targetGitSystemId: number | null; // 🆕 目标Git系统ID
|
||||
targetGitProjectId: number | null; // 🆕 目标Git项目ID
|
||||
targetBranch: string; // 🆕 目标分支
|
||||
}) => Promise<void>;
|
||||
onLoadBranches: (appId: number, app: Application) => Promise<RepositoryBranchResponse[]>;
|
||||
onLoadJenkinsJobs: (systemId: number) => Promise<any[]>;
|
||||
@ -66,6 +71,7 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
teamId,
|
||||
environmentId,
|
||||
environmentName,
|
||||
enableGitSyncCheck,
|
||||
application,
|
||||
applications,
|
||||
jenkinsSystems,
|
||||
@ -86,8 +92,11 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
deploySystemId: null as number | null,
|
||||
deployJob: '',
|
||||
workflowDefinitionId: null as number | null,
|
||||
codeSourceSystemId: null as number | null,
|
||||
codeSourceProjectId: null as number | null,
|
||||
codeSourceSystemId: null as number | null, // 源代码系统ID
|
||||
codeSourceProjectId: null as number | null, // 源仓库项目ID
|
||||
targetGitSystemId: null as number | null, // 🆕 目标Git系统ID
|
||||
targetGitProjectId: null as number | null, // 🆕 目标Git项目ID
|
||||
targetBranch: '', // 🆕 目标分支
|
||||
});
|
||||
|
||||
// 加载状态
|
||||
@ -99,14 +108,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
const [gitSystems, setGitSystems] = useState<any[]>([]);
|
||||
const [repoProjects, setRepoProjects] = useState<any[]>([]);
|
||||
const [loadingRepoProjects, setLoadingRepoProjects] = useState(false);
|
||||
// 🆕 目标Git相关状态
|
||||
const [targetRepoProjects, setTargetRepoProjects] = useState<any[]>([]);
|
||||
const [loadingTargetRepoProjects, setLoadingTargetRepoProjects] = useState(false);
|
||||
const [targetBranches, setTargetBranches] = useState<RepositoryBranchResponse[]>([]);
|
||||
const [loadingTargetBranches, setLoadingTargetBranches] = useState(false);
|
||||
|
||||
// 搜索和弹窗状态
|
||||
const [appSearchValue, setAppSearchValue] = useState('');
|
||||
const [appPopoverOpen, setAppPopoverOpen] = useState(false);
|
||||
const [branchSearchValue, setBranchSearchValue] = useState('');
|
||||
const [branchPopoverOpen, setBranchPopoverOpen] = useState(false);
|
||||
const [projectSearchValue, setProjectSearchValue] = useState('');
|
||||
const [projectPopoverOpen, setProjectPopoverOpen] = useState(false);
|
||||
const [jobSearchValue, setJobSearchValue] = useState('');
|
||||
const [jobPopoverOpen, setJobPopoverOpen] = useState(false);
|
||||
|
||||
@ -124,14 +134,17 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
workflowDefinitionId: application.workflowDefinitionId || null,
|
||||
codeSourceSystemId: application.codeSourceSystemId || null,
|
||||
codeSourceProjectId: application.codeSourceProjectId || null,
|
||||
targetGitSystemId: application.targetGitSystemId || null,
|
||||
targetGitProjectId: application.targetGitProjectId || null,
|
||||
targetBranch: application.targetBranch || '',
|
||||
});
|
||||
|
||||
// 加载仓库项目
|
||||
// 加载源仓库项目
|
||||
if (application.codeSourceSystemId) {
|
||||
loadRepoProjects(application.codeSourceSystemId);
|
||||
}
|
||||
|
||||
// 加载分支(优先使用代码源信息,向后兼容旧数据)
|
||||
// 加载源分支(优先使用代码源信息,向后兼容旧数据)
|
||||
if (application.codeSourceSystemId && application.codeSourceProjectId) {
|
||||
// 使用代码源信息加载分支
|
||||
loadBranchesFromCodeSource(application.codeSourceSystemId, application.codeSourceProjectId);
|
||||
@ -143,6 +156,16 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 加载目标仓库项目(如果有)
|
||||
if (application.targetGitSystemId) {
|
||||
loadTargetRepoProjects(application.targetGitSystemId);
|
||||
}
|
||||
|
||||
// 🆕 加载目标分支(如果有)
|
||||
if (application.targetGitSystemId && application.targetGitProjectId) {
|
||||
loadTargetBranches(application.targetGitSystemId, application.targetGitProjectId);
|
||||
}
|
||||
|
||||
// 加载Jenkins Jobs
|
||||
if (application.deploySystemId) {
|
||||
loadJenkinsJobs(application.deploySystemId);
|
||||
@ -158,6 +181,9 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
workflowDefinitionId: null,
|
||||
codeSourceSystemId: null,
|
||||
codeSourceProjectId: null,
|
||||
targetGitSystemId: null,
|
||||
targetGitProjectId: null,
|
||||
targetBranch: '',
|
||||
});
|
||||
setBranches([]);
|
||||
setJenkinsJobs([]);
|
||||
@ -204,20 +230,34 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 加载仓库项目列表
|
||||
// 加载源仓库项目列表
|
||||
const loadRepoProjects = async (externalSystemId: number) => {
|
||||
setLoadingRepoProjects(true);
|
||||
try {
|
||||
const projects = await getRepositoryProjectsList({ externalSystemId });
|
||||
setRepoProjects(projects || []);
|
||||
} catch (error) {
|
||||
console.error('加载仓库项目失败:', error);
|
||||
console.error('加载源仓库项目失败:', error);
|
||||
setRepoProjects([]);
|
||||
} finally {
|
||||
setLoadingRepoProjects(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 🆕 加载目标仓库项目列表
|
||||
const loadTargetRepoProjects = async (externalSystemId: number) => {
|
||||
setLoadingTargetRepoProjects(true);
|
||||
try {
|
||||
const projects = await getRepositoryProjectsList({ externalSystemId });
|
||||
setTargetRepoProjects(projects || []);
|
||||
} catch (error) {
|
||||
console.error('加载目标仓库项目失败:', error);
|
||||
setTargetRepoProjects([]);
|
||||
} finally {
|
||||
setLoadingTargetRepoProjects(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化加载Git系统列表
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@ -236,6 +276,9 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
workflowDefinitionId: null,
|
||||
codeSourceSystemId: null,
|
||||
codeSourceProjectId: null,
|
||||
targetGitSystemId: null,
|
||||
targetGitProjectId: null,
|
||||
targetBranch: '',
|
||||
});
|
||||
// 清空分支列表(分支现在基于代码源,不基于应用)
|
||||
setBranches([]);
|
||||
@ -263,21 +306,35 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
loadJenkinsJobs(systemId);
|
||||
};
|
||||
|
||||
// 加载基于代码源的分支列表
|
||||
// 加载基于源代码的分支列表
|
||||
const loadBranchesFromCodeSource = async (externalSystemId: number, repoProjectId: number) => {
|
||||
setLoadingBranches(true);
|
||||
try {
|
||||
const branchList = await getRepositoryBranchesList({ externalSystemId, repoProjectId });
|
||||
setBranches(branchList || []);
|
||||
} catch (error) {
|
||||
console.error('加载分支失败:', error);
|
||||
console.error('加载源分支失败:', error);
|
||||
setBranches([]);
|
||||
} finally {
|
||||
setLoadingBranches(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理代码源系统选择
|
||||
// 🆕 加载目标分支列表
|
||||
const loadTargetBranches = async (externalSystemId: number, repoProjectId: number) => {
|
||||
setLoadingTargetBranches(true);
|
||||
try {
|
||||
const branchList = await getRepositoryBranchesList({ externalSystemId, repoProjectId });
|
||||
setTargetBranches(branchList || []);
|
||||
} catch (error) {
|
||||
console.error('加载目标分支失败:', error);
|
||||
setTargetBranches([]);
|
||||
} finally {
|
||||
setLoadingTargetBranches(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理源代码系统选择
|
||||
const handleCodeSourceSystemChange = (systemId: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
@ -289,7 +346,7 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
loadRepoProjects(systemId);
|
||||
};
|
||||
|
||||
// 处理仓库项目选择
|
||||
// 处理源仓库项目选择
|
||||
const handleCodeSourceProjectChange = (projectId: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
@ -302,6 +359,31 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 🆕 处理目标Git系统选择
|
||||
const handleTargetGitSystemChange = (systemId: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetGitSystemId: systemId,
|
||||
targetGitProjectId: null,
|
||||
targetBranch: '', // 清空分支
|
||||
});
|
||||
setTargetBranches([]); // 清空分支列表
|
||||
loadTargetRepoProjects(systemId);
|
||||
};
|
||||
|
||||
// 🆕 处理目标仓库项目选择
|
||||
const handleTargetGitProjectChange = (projectId: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetGitProjectId: projectId,
|
||||
targetBranch: '', // 清空分支
|
||||
});
|
||||
// 加载该项目的分支
|
||||
if (formData.targetGitSystemId) {
|
||||
loadTargetBranches(formData.targetGitSystemId, projectId);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存
|
||||
const handleSave = async () => {
|
||||
// 表单验证
|
||||
@ -325,6 +407,9 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
workflowDefinitionId: formData.workflowDefinitionId,
|
||||
codeSourceSystemId: formData.codeSourceSystemId,
|
||||
codeSourceProjectId: formData.codeSourceProjectId,
|
||||
targetGitSystemId: formData.targetGitSystemId,
|
||||
targetGitProjectId: formData.targetGitProjectId,
|
||||
targetBranch: formData.targetBranch,
|
||||
});
|
||||
|
||||
toast({
|
||||
@ -338,11 +423,6 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 分支过滤
|
||||
const filteredBranches = branches.filter(branch =>
|
||||
branch.name.toLowerCase().includes(branchSearchValue.toLowerCase())
|
||||
);
|
||||
|
||||
// Job 过滤
|
||||
const filteredJobs = jenkinsJobs.filter(job =>
|
||||
job.jobName.toLowerCase().includes(jobSearchValue.toLowerCase())
|
||||
@ -599,215 +679,39 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 代码源选择 */}
|
||||
<div className="space-y-2">
|
||||
<Label>代码源</Label>
|
||||
<Select
|
||||
value={formData.codeSourceSystemId?.toString() || ''}
|
||||
onValueChange={(value) => handleCodeSourceSystemChange(Number(value))}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择代码源" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{gitSystems.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
暂无可用的Git系统
|
||||
</div>
|
||||
) : (
|
||||
gitSystems.map((system) => (
|
||||
<SelectItem key={system.id} value={system.id.toString()}>
|
||||
{system.name}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 仓库项目选择 */}
|
||||
<div className="space-y-2">
|
||||
<Label>仓库项目</Label>
|
||||
{formData.codeSourceSystemId ? (
|
||||
<Popover
|
||||
open={projectPopoverOpen}
|
||||
onOpenChange={setProjectPopoverOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
disabled={loadingRepoProjects || repoProjects.length === 0}
|
||||
className={cn(
|
||||
'w-full justify-between',
|
||||
!formData.codeSourceProjectId && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{formData.codeSourceProjectId
|
||||
? (() => {
|
||||
const selectedProject = repoProjects.find(
|
||||
(p) => p.repoProjectId === formData.codeSourceProjectId
|
||||
);
|
||||
return selectedProject
|
||||
? (selectedProject.repoGroupName
|
||||
? `${selectedProject.repoGroupName} / ${selectedProject.name}`
|
||||
: selectedProject.name)
|
||||
: '选择仓库项目';
|
||||
})()
|
||||
: loadingRepoProjects
|
||||
? '加载中...'
|
||||
: repoProjects.length === 0
|
||||
? '暂无项目'
|
||||
: '选择仓库项目'}
|
||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
|
||||
<div className="flex items-center border-b px-3">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<input
|
||||
placeholder="搜索项目..."
|
||||
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
||||
value={projectSearchValue}
|
||||
onChange={(e) => setProjectSearchValue(e.target.value)}
|
||||
{/* 源Git配置 */}
|
||||
<GitConfigSelector
|
||||
label="源"
|
||||
gitSystems={gitSystems}
|
||||
selectedSystemId={formData.codeSourceSystemId}
|
||||
onSystemChange={handleCodeSourceSystemChange}
|
||||
repoProjects={repoProjects}
|
||||
loadingRepoProjects={loadingRepoProjects}
|
||||
selectedProjectId={formData.codeSourceProjectId}
|
||||
onProjectChange={handleCodeSourceProjectChange}
|
||||
branches={branches}
|
||||
loadingBranches={loadingBranches}
|
||||
selectedBranch={formData.branch}
|
||||
onBranchChange={(branch) => setFormData({ ...formData, branch })}
|
||||
/>
|
||||
</div>
|
||||
<ScrollArea className="h-[200px]">
|
||||
<div className="p-1">
|
||||
{repoProjects
|
||||
.filter((project) =>
|
||||
project.name.toLowerCase().includes(projectSearchValue.toLowerCase()) ||
|
||||
(project.repoGroupName?.toLowerCase().includes(projectSearchValue.toLowerCase()))
|
||||
)
|
||||
.map((project) => (
|
||||
<div
|
||||
key={project.repoProjectId}
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground',
|
||||
project.repoProjectId === formData.codeSourceProjectId &&
|
||||
'bg-accent text-accent-foreground'
|
||||
)}
|
||||
onClick={() => {
|
||||
handleCodeSourceProjectChange(project.repoProjectId);
|
||||
setProjectSearchValue('');
|
||||
setProjectPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex-1 truncate">
|
||||
{project.repoGroupName && (
|
||||
<span className="text-muted-foreground">{project.repoGroupName} / </span>
|
||||
)}
|
||||
{project.name}
|
||||
</div>
|
||||
{project.repoProjectId === formData.codeSourceProjectId && (
|
||||
<Check className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<Input placeholder="请先选择代码源" disabled />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 分支选择 */}
|
||||
<div className="space-y-2">
|
||||
<Label>分支</Label>
|
||||
{formData.codeSourceProjectId ? (
|
||||
<Popover
|
||||
open={branchPopoverOpen}
|
||||
onOpenChange={setBranchPopoverOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
disabled={loadingBranches || branches.length === 0}
|
||||
className={cn(
|
||||
'w-full justify-between',
|
||||
!formData.branch && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{formData.branch
|
||||
? (() => {
|
||||
const selectedBranch = branches.find(
|
||||
(b) => b.name === formData.branch
|
||||
);
|
||||
return (
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
{selectedBranch?.name}
|
||||
{selectedBranch?.isDefaultBranch && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(默认)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})()
|
||||
: loadingBranches
|
||||
? '加载中...'
|
||||
: branches.length === 0
|
||||
? '无分支'
|
||||
: '选择分支'}
|
||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0">
|
||||
<div className="flex items-center border-b px-3">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<input
|
||||
placeholder="搜索分支..."
|
||||
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
||||
value={branchSearchValue}
|
||||
onChange={(e) => setBranchSearchValue(e.target.value)}
|
||||
{/* 🆕 目标Git配置(仅当启用Git同步检测时显示) */}
|
||||
{enableGitSyncCheck && (
|
||||
<GitConfigSelector
|
||||
label="目标"
|
||||
gitSystems={gitSystems}
|
||||
selectedSystemId={formData.targetGitSystemId}
|
||||
onSystemChange={handleTargetGitSystemChange}
|
||||
repoProjects={targetRepoProjects}
|
||||
loadingRepoProjects={loadingTargetRepoProjects}
|
||||
selectedProjectId={formData.targetGitProjectId}
|
||||
onProjectChange={handleTargetGitProjectChange}
|
||||
branches={targetBranches}
|
||||
loadingBranches={loadingTargetBranches}
|
||||
selectedBranch={formData.targetBranch}
|
||||
onBranchChange={(branch) => setFormData({ ...formData, targetBranch: branch })}
|
||||
/>
|
||||
</div>
|
||||
<ScrollArea className="h-[200px]">
|
||||
<div className="p-1">
|
||||
{filteredBranches.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
未找到分支
|
||||
</div>
|
||||
) : (
|
||||
filteredBranches.map((branch) => (
|
||||
<div
|
||||
key={branch.id}
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent',
|
||||
branch.name === formData.branch &&
|
||||
'bg-accent'
|
||||
)}
|
||||
onClick={() => {
|
||||
setFormData({ ...formData, branch: branch.name });
|
||||
setBranchSearchValue('');
|
||||
setBranchPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="flex-1 truncate">
|
||||
{branch.name}
|
||||
{branch.isDefaultBranch && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(默认)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{branch.name === formData.branch && (
|
||||
<Check className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<Input placeholder="请先选择仓库项目" disabled />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 工作流定义 */}
|
||||
<div className="space-y-2">
|
||||
|
||||
@ -10,8 +10,9 @@ import {
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { PaginatedTable, type ColumnDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
|
||||
import { Plus, Edit, Trash2 } from 'lucide-react';
|
||||
import { Plus, Edit, Trash2, GitBranch } from 'lucide-react';
|
||||
import type { Environment } from '@/pages/Deploy/Environment/List/types';
|
||||
import type { TeamApplication, Application } from '../types';
|
||||
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
|
||||
@ -32,6 +33,7 @@ interface TeamApplicationManageDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
teamId: number;
|
||||
enableGitSyncCheck: boolean; // 🆕 团队是否启用Git同步检测
|
||||
environmentId?: number; // 可选:如果指定,则只管理该环境的应用
|
||||
environments: Environment[];
|
||||
onSuccess?: () => void;
|
||||
@ -39,7 +41,7 @@ interface TeamApplicationManageDialogProps {
|
||||
|
||||
export const TeamApplicationManageDialog: React.FC<
|
||||
TeamApplicationManageDialogProps
|
||||
> = ({ open, onOpenChange, teamId, environmentId, environments, onSuccess }) => {
|
||||
> = ({ open, onOpenChange, teamId, enableGitSyncCheck, environmentId, environments, onSuccess }) => {
|
||||
const { toast } = useToast();
|
||||
const tableRef = useRef<PaginatedTableRef<TeamApplication>>(null);
|
||||
const [teamApplications, setTeamApplications] = useState<TeamApplication[]>([]);
|
||||
@ -127,6 +129,9 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
workflowDefinitionId: number | null;
|
||||
codeSourceSystemId: number | null;
|
||||
codeSourceProjectId: number | null;
|
||||
targetGitSystemId: number | null; // 🆕 目标Git系统ID
|
||||
targetGitProjectId: number | null; // 🆕 目标Git项目ID
|
||||
targetBranch: string; // 🆕 目标分支
|
||||
}) => {
|
||||
if (!editingEnvironment) return;
|
||||
|
||||
@ -142,6 +147,10 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
workflowDefinitionId: data.workflowDefinitionId || undefined,
|
||||
codeSourceSystemId: data.codeSourceSystemId || undefined,
|
||||
codeSourceProjectId: data.codeSourceProjectId || undefined,
|
||||
// 🆕 目标Git相关字段
|
||||
targetGitSystemId: data.targetGitSystemId || undefined,
|
||||
targetGitProjectId: data.targetGitProjectId || undefined,
|
||||
targetBranch: data.targetBranch || undefined,
|
||||
};
|
||||
|
||||
if (appDialogMode === 'edit' && data.id) {
|
||||
@ -207,6 +216,84 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
width: '100px',
|
||||
render: (_, app) => getEnvironmentName(app.environmentId),
|
||||
},
|
||||
{
|
||||
key: 'gitConfig',
|
||||
title: 'Git配置',
|
||||
width: '350px',
|
||||
render: (_, app) => {
|
||||
const hasTargetGit = app.targetGitSystemId && app.targetGitProjectId;
|
||||
|
||||
if (!hasTargetGit) {
|
||||
// 未配置目标:单行显示
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="font-medium truncate">
|
||||
{app.codeSourceSystemName || '-'} / {app.codeSourceProjectName || '-'}
|
||||
</span>
|
||||
{app.branch && (
|
||||
<span className="text-xs text-muted-foreground">({app.branch})</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 配置了目标:对称双行显示
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="space-y-0.5 cursor-help">
|
||||
{/* 源Git */}
|
||||
<div className="text-sm">
|
||||
<span className="text-muted-foreground">源:</span>
|
||||
<span className="font-medium">
|
||||
{app.codeSourceSystemName || '-'} / {app.codeSourceProjectName || '-'}
|
||||
</span>
|
||||
{app.branch && (
|
||||
<span className="text-muted-foreground">({app.branch})</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 同步标识 - 居中 */}
|
||||
<div className="flex justify-center text-xs text-blue-500">
|
||||
<span>⇅ 同步</span>
|
||||
</div>
|
||||
{/* 目标Git */}
|
||||
<div className="text-sm">
|
||||
<span className="text-muted-foreground">目:</span>
|
||||
<span className="font-medium text-blue-600">
|
||||
{app.targetGitSystemName || '-'} / {app.targetGitProjectName || '-'}
|
||||
</span>
|
||||
{app.targetBranch && (
|
||||
<span className="text-blue-500">({app.targetBranch})</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<div className="space-y-2 text-xs">
|
||||
<div>
|
||||
<div className="font-semibold mb-1">源Git</div>
|
||||
<div className="text-muted-foreground">
|
||||
<div>系统: {app.codeSourceSystemName}</div>
|
||||
<div>项目: {app.codeSourceProjectName}</div>
|
||||
<div>分支: {app.branch || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t pt-2">
|
||||
<div className="font-semibold mb-1">目标Git</div>
|
||||
<div className="text-muted-foreground">
|
||||
<div>系统: {app.targetGitSystemName}</div>
|
||||
<div>项目: {app.targetGitProjectName}</div>
|
||||
<div>分支: {app.targetBranch || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'buildType',
|
||||
title: '构建类型',
|
||||
@ -214,13 +301,6 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
render: (_, app) =>
|
||||
app.buildType === 'JENKINS' ? 'Jenkins构建' : app.buildType === 'NATIVE' ? '脚本部署' : '-',
|
||||
},
|
||||
{
|
||||
key: 'branch',
|
||||
title: '分支',
|
||||
dataIndex: 'branch',
|
||||
width: '100px',
|
||||
render: (value) => value || '-',
|
||||
},
|
||||
{
|
||||
key: 'deploySystemName',
|
||||
title: 'Jenkins系统',
|
||||
@ -315,6 +395,7 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
open={appDialogOpen}
|
||||
onOpenChange={setAppDialogOpen}
|
||||
mode={appDialogMode}
|
||||
enableGitSyncCheck={enableGitSyncCheck}
|
||||
teamId={teamId}
|
||||
environmentId={editingEnvironment.id}
|
||||
environmentName={editingEnvironment.envName}
|
||||
|
||||
@ -44,6 +44,7 @@ interface TeamEnvironmentManageDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
teamId: number;
|
||||
teamName: string;
|
||||
enableGitSyncCheck: boolean; // 🆕 团队是否启用Git同步检测
|
||||
environments: Environment[];
|
||||
users: User[];
|
||||
onSuccess?: () => void;
|
||||
@ -56,6 +57,7 @@ export const TeamEnvironmentManageDialog: React.FC<
|
||||
onOpenChange,
|
||||
teamId,
|
||||
teamName,
|
||||
enableGitSyncCheck,
|
||||
environments,
|
||||
users,
|
||||
onSuccess,
|
||||
@ -301,6 +303,7 @@ export const TeamEnvironmentManageDialog: React.FC<
|
||||
open={appManageDialogOpen}
|
||||
onOpenChange={setAppManageDialogOpen}
|
||||
teamId={teamId}
|
||||
enableGitSyncCheck={enableGitSyncCheck}
|
||||
environmentId={selectedEnvironmentId}
|
||||
environments={environments}
|
||||
onSuccess={handleConfigSuccess}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { TeamResponse } from '../types';
|
||||
import { DevelopmentModeEnum, DEVELOPMENT_MODE_OPTIONS } from '../types';
|
||||
import { createTeam, updateTeam } from '../service';
|
||||
import { getUserList } from '@/pages/System/User/List/service';
|
||||
import type { UserResponse } from '@/pages/System/User/List/types';
|
||||
@ -25,6 +26,7 @@ import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectItemWithDescription,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
@ -62,9 +64,21 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
ownerName: "",
|
||||
enabled: true,
|
||||
sort: 0,
|
||||
developmentMode: DevelopmentModeEnum.STANDARD,
|
||||
enableGitSyncCheck: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 监听开发模式变化,自动调整Git同步检测选项
|
||||
const developmentMode = form.watch('developmentMode');
|
||||
useEffect(() => {
|
||||
const modeOption = DEVELOPMENT_MODE_OPTIONS.find(opt => opt.value === developmentMode);
|
||||
if (modeOption && !modeOption.enablesGitSyncCheck) {
|
||||
// 如果当前模式不支持Git同步检测,自动禁用
|
||||
form.setValue('enableGitSyncCheck', false);
|
||||
}
|
||||
}, [developmentMode, form]);
|
||||
|
||||
// 加载用户列表
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
@ -89,6 +103,8 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
ownerName: initialValues.ownerName || "",
|
||||
enabled: initialValues.enabled,
|
||||
sort: initialValues.sort,
|
||||
developmentMode: initialValues.developmentMode || DevelopmentModeEnum.STANDARD,
|
||||
enableGitSyncCheck: initialValues.enableGitSyncCheck || false,
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
@ -99,6 +115,8 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
ownerName: "",
|
||||
enabled: true,
|
||||
sort: 0,
|
||||
developmentMode: DevelopmentModeEnum.STANDARD,
|
||||
enableGitSyncCheck: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -236,6 +254,61 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="developmentMode"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>开发模式</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择开发模式" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{DEVELOPMENT_MODE_OPTIONS.map((option) => (
|
||||
<SelectItemWithDescription
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
label={option.label}
|
||||
description={option.description}
|
||||
/>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{developmentMode === 'SYNC_MODE' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enableGitSyncCheck"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Git同步检测</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex items-center gap-2 h-10">
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{field.value ? '启用' : '禁用'}
|
||||
</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { PageContainer } from '@/components/ui/page-container';
|
||||
import { getTeams } from './service';
|
||||
import type { TeamResponse, TeamQuery } from './types';
|
||||
import { DEVELOPMENT_MODE_OPTIONS } from './types';
|
||||
import TeamModal from './components/TeamModal';
|
||||
import DeleteDialog from './components/DeleteDialog';
|
||||
import MemberManageDialog from './components/MemberManageDialog';
|
||||
@ -149,6 +150,28 @@ const TeamList: React.FC = () => {
|
||||
width: '140px',
|
||||
render: (_, record) => record.ownerName || '-',
|
||||
},
|
||||
{
|
||||
key: 'developmentMode',
|
||||
title: '开发模式',
|
||||
width: '160px',
|
||||
render: (_, record) => {
|
||||
const modeOption = DEVELOPMENT_MODE_OPTIONS.find(opt => opt.value === record.developmentMode);
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Badge variant="outline" className="inline-flex">
|
||||
{modeOption?.label || record.developmentMode}
|
||||
</Badge>
|
||||
{record.enableGitSyncCheck && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Git同步检测
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'memberCount',
|
||||
title: '成员数量',
|
||||
@ -332,6 +355,7 @@ const TeamList: React.FC = () => {
|
||||
onOpenChange={setEnvManageDialogOpen}
|
||||
teamId={currentTeam.id}
|
||||
teamName={currentTeam.teamName}
|
||||
enableGitSyncCheck={currentTeam.enableGitSyncCheck}
|
||||
environments={environments}
|
||||
users={users}
|
||||
onSuccess={handleSuccess}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
import { DevelopmentModeEnum } from './types';
|
||||
|
||||
/**
|
||||
* 搜索表单校验
|
||||
@ -20,6 +21,8 @@ export const teamFormSchema = z.object({
|
||||
ownerName: z.string().optional(),
|
||||
enabled: z.boolean().default(true),
|
||||
sort: z.number().min(0).default(0),
|
||||
developmentMode: z.nativeEnum(DevelopmentModeEnum).default(DevelopmentModeEnum.STANDARD),
|
||||
enableGitSyncCheck: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export type SearchFormValues = z.infer<typeof searchFormSchema>;
|
||||
|
||||
@ -5,6 +5,56 @@ import type { Application } from '@/pages/Deploy/Application/List/types';
|
||||
// 导出环境和应用类型供使用
|
||||
export type { Environment, Application };
|
||||
|
||||
/**
|
||||
* 开发模式枚举
|
||||
*/
|
||||
export enum DevelopmentModeEnum {
|
||||
/** 标准模式 - 单仓库直接开发部署 */
|
||||
STANDARD = 'STANDARD',
|
||||
/** 同步模式 - 源码内外网同步 */
|
||||
SYNC_MODE = 'SYNC_MODE',
|
||||
/** 制品交付模式 - 仅交付编译制品,无源码 */
|
||||
ARTIFACT_DELIVERY = 'ARTIFACT_DELIVERY'
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发模式选项配置
|
||||
*/
|
||||
export interface DevelopmentModeOption {
|
||||
value: DevelopmentModeEnum;
|
||||
label: string;
|
||||
description: string;
|
||||
requiresExternalRepo: boolean;
|
||||
enablesGitSyncCheck: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发模式选项列表
|
||||
*/
|
||||
export const DEVELOPMENT_MODE_OPTIONS: DevelopmentModeOption[] = [
|
||||
{
|
||||
value: DevelopmentModeEnum.STANDARD,
|
||||
label: '标准模式',
|
||||
description: '单仓库直接开发部署',
|
||||
requiresExternalRepo: false,
|
||||
enablesGitSyncCheck: false
|
||||
},
|
||||
{
|
||||
value: DevelopmentModeEnum.SYNC_MODE,
|
||||
label: '同步模式',
|
||||
description: '源码内外网同步',
|
||||
requiresExternalRepo: true,
|
||||
enablesGitSyncCheck: true
|
||||
},
|
||||
{
|
||||
value: DevelopmentModeEnum.ARTIFACT_DELIVERY,
|
||||
label: '制品交付模式',
|
||||
description: '仅交付编译制品,无源码',
|
||||
requiresExternalRepo: true,
|
||||
enablesGitSyncCheck: false
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 团队查询参数
|
||||
*/
|
||||
@ -25,6 +75,8 @@ export interface TeamResponse extends BaseResponse {
|
||||
ownerName?: string;
|
||||
enabled: boolean;
|
||||
sort: number;
|
||||
developmentMode: DevelopmentModeEnum; // 开发模式
|
||||
enableGitSyncCheck: boolean; // 是否启用Git同步检测
|
||||
memberCount?: number;
|
||||
environmentCount?: number;
|
||||
applicationCount?: number;
|
||||
@ -41,6 +93,8 @@ export interface TeamRequest {
|
||||
ownerName?: string;
|
||||
enabled?: boolean;
|
||||
sort?: number;
|
||||
developmentMode?: DevelopmentModeEnum; // 开发模式
|
||||
enableGitSyncCheck?: boolean; // 是否启用Git同步检测
|
||||
}
|
||||
|
||||
// ==================== 团队环境配置相关 ====================
|
||||
@ -130,16 +184,21 @@ export interface TeamApplication extends BaseResponse {
|
||||
deploySystemId?: number;
|
||||
deployJob?: string;
|
||||
workflowDefinitionId?: number;
|
||||
codeSourceSystemId?: number; // 代码源系统ID
|
||||
codeSourceProjectId?: number; // 代码源项目ID
|
||||
codeSourceSystemId?: number; // 源代码系统ID
|
||||
codeSourceProjectId?: number; // 源仓库项目ID
|
||||
targetGitSystemId?: number; // 目标Git系统ID(仅SYNC_MODE)
|
||||
targetGitProjectId?: number; // 目标Git项目ID
|
||||
targetBranch?: string; // 目标分支名称
|
||||
teamName?: string;
|
||||
applicationName?: string;
|
||||
applicationCode?: string;
|
||||
environmentName?: string;
|
||||
deploySystemName?: string;
|
||||
workflowDefinitionName?: string;
|
||||
codeSourceSystemName?: string; // 代码源系统名称
|
||||
codeSourceProjectName?: string; // 代码源项目名称
|
||||
codeSourceSystemName?: string; // 源代码系统名称
|
||||
codeSourceProjectName?: string; // 源仓库项目名称
|
||||
targetGitSystemName?: string; // 目标Git系统名称
|
||||
targetGitProjectName?: string; // 目标Git项目名称
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +213,10 @@ export interface TeamApplicationRequest {
|
||||
deploySystemId?: number;
|
||||
deployJob?: string;
|
||||
workflowDefinitionId?: number;
|
||||
codeSourceSystemId?: number; // 代码源系统ID
|
||||
codeSourceProjectId?: number; // 代码源项目ID
|
||||
codeSourceSystemId?: number; // 源代码系统ID
|
||||
codeSourceProjectId?: number; // 源仓库项目ID
|
||||
targetGitSystemId?: number; // 目标Git系统ID(仅SYNC_MODE)
|
||||
targetGitProjectId?: number; // 目标Git项目ID
|
||||
targetBranch?: string; // 目标分支名称
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user