From 714ced899f9192a205f335abc280df0a51d1fcfa Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 29 Oct 2025 13:54:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=A2=E9=98=9F=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/Deploy/GitManager/List/index.tsx | 1031 ++++++++++------- .../pages/Deploy/GitManager/List/service.ts | 139 ++- .../src/pages/Deploy/GitManager/List/types.ts | 199 ++-- 3 files changed, 829 insertions(+), 540 deletions(-) diff --git a/frontend/src/pages/Deploy/GitManager/List/index.tsx b/frontend/src/pages/Deploy/GitManager/List/index.tsx index 7dfcb0bd..bb2a691c 100644 --- a/frontend/src/pages/Deploy/GitManager/List/index.tsx +++ b/frontend/src/pages/Deploy/GitManager/List/index.tsx @@ -3,50 +3,56 @@ import { Card, CardContent, CardHeader, - CardTitle -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { Badge } from "@/components/ui/badge"; + CardTitle, +} from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useToast } from "@/components/ui/use-toast"; +} from '@/components/ui/select'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { useToast } from '@/components/ui/use-toast'; import { GitBranch, - GitFork, - RefreshCw, FolderGit2, + RefreshCw, Globe, Lock, Users, - Copy, - ExternalLink, Search, - Loader2 -} from "lucide-react"; -import type { GitInstance, GitInstanceInfo } from './types'; + Loader2, + ChevronRight, + ChevronDown, + ExternalLink, + Shield, + Code2, + Calendar, + User, + GitCommit, +} from 'lucide-react'; +import type { + RepositoryGroupResponse, + RepositoryProjectResponse, + RepositoryBranchResponse, + GitStatistics, +} from './types'; import { getGitInstances, - getGitInstanceInfo, + getRepositoryGroupTree, + getRepositoryProjects, + getRepositoryBranches, syncAllGitData, - syncGitGroups, - syncGitProjects, - syncGitBranches + syncRepositoryGroups, + syncRepositoryProjects, + syncRepositoryBranches, + getGitStatistics, } from './service'; +import type { ExternalSystemResponse } from '@/pages/Deploy/External/types'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import 'dayjs/locale/zh-cn'; @@ -54,19 +60,38 @@ import 'dayjs/locale/zh-cn'; dayjs.extend(relativeTime); dayjs.locale('zh-cn'); -// 常量定义 +// 可见性配置 const VISIBILITY_CONFIG = { public: { label: '公开', variant: 'default' as const, icon: Globe, color: 'text-green-600' }, - private: { label: '私有', variant: 'destructive' as const, icon: Lock, color: 'text-red-600' }, - internal: { label: '内部', variant: 'secondary' as const, icon: Users, color: 'text-yellow-600' }, + private: { label: '私有', variant: 'secondary' as const, icon: Lock, color: 'text-red-600' }, + internal: { label: '内部', variant: 'outline' as const, icon: Users, color: 'text-yellow-600' }, }; const GitManager: React.FC = () => { const { toast } = useToast(); - const [loading, setLoading] = useState(false); - const [instances, setInstances] = useState([]); - const [selectedInstance, setSelectedInstance] = useState(); - const [instanceInfo, setInstanceInfo] = useState(); + + // Git 实例选择 + const [instances, setInstances] = useState([]); + const [selectedInstanceId, setSelectedInstanceId] = useState(); + + // 数据状态 + const [groupTree, setGroupTree] = useState([]); + const [projects, setProjects] = useState([]); + const [branches, setBranches] = useState([]); + const [statistics, setStatistics] = useState(); + + // 选中状态 + const [selectedGroup, setSelectedGroup] = useState(); + const [selectedProject, setSelectedProject] = useState(); + const [expandedGroupIds, setExpandedGroupIds] = useState>(new Set()); + + // 加载状态 + const [loading, setLoading] = useState({ + groups: false, + projects: false, + branches: false, + statistics: false, + }); // 同步状态 const [syncing, setSyncing] = useState({ @@ -76,151 +101,244 @@ const GitManager: React.FC = () => { branches: false, }); - // 搜索和过滤状态 + // 搜索过滤 const [groupSearch, setGroupSearch] = useState(''); - const [groupVisibility, setGroupVisibility] = useState('all'); const [projectSearch, setProjectSearch] = useState(''); - const [projectVisibility, setProjectVisibility] = useState('all'); - const [projectGroup, setProjectGroup] = useState('all'); - const [activeTab, setActiveTab] = useState('groups'); + const [branchSearch, setBranchSearch] = useState(''); - // 加载Git实例列表 + // 加载 Git 实例列表 const loadInstances = async () => { try { - const response = await getGitInstances(); - if (response && Array.isArray(response)) { - setInstances(response); - if (response.length > 0) { - setSelectedInstance(response[0].id); + const data = await getGitInstances(); + if (data && Array.isArray(data)) { + setInstances(data); + if (data.length > 0 && !selectedInstanceId) { + setSelectedInstanceId(data[0].id); } } } catch (error) { toast({ - variant: "destructive", - title: "加载失败", - description: "加载 Git 实例列表失败", + variant: 'destructive', + title: '加载失败', + description: '加载 Git 实例列表失败', }); } }; - // 加载实例信息 - const loadInstanceInfo = async () => { - if (!selectedInstance) return; - setLoading(true); + // 加载仓库组树 + const loadGroupTree = async () => { + if (!selectedInstanceId) return; + + setLoading((prev) => ({ ...prev, groups: true })); try { - const data = await getGitInstanceInfo(selectedInstance); - setInstanceInfo(data); + const data = await getRepositoryGroupTree(selectedInstanceId); + setGroupTree(data || []); } catch (error) { toast({ - variant: "destructive", - title: "加载失败", - description: "加载实例信息失败", + variant: 'destructive', + title: '加载失败', + description: '加载仓库组失败', }); + setGroupTree([]); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, groups: false })); + } + }; + + // 加载项目列表 + const loadProjects = async (repoGroupId?: number) => { + if (!selectedInstanceId) return; + + setLoading((prev) => ({ ...prev, projects: true })); + try { + const data = await getRepositoryProjects({ + externalSystemId: selectedInstanceId, + repoGroupId, + }); + setProjects(data || []); + } catch (error) { + toast({ + variant: 'destructive', + title: '加载失败', + description: '加载项目列表失败', + }); + setProjects([]); + } finally { + setLoading((prev) => ({ ...prev, projects: false })); + } + }; + + // 加载分支列表 + const loadBranches = async (repoProjectId: number) => { + setLoading((prev) => ({ ...prev, branches: true })); + try { + const data = await getRepositoryBranches({ + repoProjectId, + }); + setBranches(data || []); + } catch (error) { + toast({ + variant: 'destructive', + title: '加载失败', + description: '加载分支列表失败', + }); + setBranches([]); + } finally { + setLoading((prev) => ({ ...prev, branches: false })); + } + }; + + // 加载统计信息 + const loadStatistics = async () => { + if (!selectedInstanceId) return; + + setLoading((prev) => ({ ...prev, statistics: true })); + try { + const data = await getGitStatistics(selectedInstanceId); + setStatistics(data); + } catch (error) { + // 静默失败 + } finally { + setLoading((prev) => ({ ...prev, statistics: false })); } }; // 同步所有数据 const handleSyncAll = async () => { - if (!selectedInstance) return; - setSyncing(prev => ({ ...prev, all: true })); + if (!selectedInstanceId) return; + + setSyncing((prev) => ({ ...prev, all: true })); try { - await syncAllGitData(selectedInstance); + await syncAllGitData(selectedInstanceId); toast({ - title: "同步成功", - description: "同步任务已启动", + title: '同步成功', + description: '已开始同步所有数据', }); - loadInstanceInfo(); + // 重新加载数据 + await Promise.all([ + loadGroupTree(), + loadProjects(selectedGroup?.repoGroupId || selectedGroup?.id), + loadStatistics(), + ]); } catch (error) { toast({ - variant: "destructive", - title: "同步失败", - description: "同步任务启动失败", + variant: 'destructive', + title: '同步失败', + description: '同步任务启动失败', }); } finally { - setSyncing(prev => ({ ...prev, all: false })); + setSyncing((prev) => ({ ...prev, all: false })); } }; // 同步仓库组 const handleSyncGroups = async () => { - if (!selectedInstance) return; - setSyncing(prev => ({ ...prev, groups: true })); + if (!selectedInstanceId) return; + + setSyncing((prev) => ({ ...prev, groups: true })); try { - await syncGitGroups(selectedInstance); + await syncRepositoryGroups(selectedInstanceId); toast({ - title: "同步成功", - description: "仓库组同步任务已启动", + title: '同步成功', + description: '已开始同步仓库组', }); - loadInstanceInfo(); + await loadGroupTree(); } catch (error) { toast({ - variant: "destructive", - title: "同步失败", - description: "仓库组同步任务启动失败", + variant: 'destructive', + title: '同步失败', + description: '仓库组同步失败', }); } finally { - setSyncing(prev => ({ ...prev, groups: false })); + setSyncing((prev) => ({ ...prev, groups: false })); } }; // 同步项目 const handleSyncProjects = async () => { - if (!selectedInstance) return; - setSyncing(prev => ({ ...prev, projects: true })); + if (!selectedInstanceId || !selectedGroup) return; + + setSyncing((prev) => ({ ...prev, projects: true })); try { - await syncGitProjects(selectedInstance); + // 传递 repoGroupId (Git系统中的ID) 和 externalSystemId + await syncRepositoryProjects( + selectedInstanceId, + selectedGroup.repoGroupId + ); toast({ - title: "同步成功", - description: "项目同步任务已启动", + title: '同步成功', + description: `已开始同步仓库组"${selectedGroup.name}"的项目`, }); - loadInstanceInfo(); + await loadProjects(selectedGroup.repoGroupId || selectedGroup.id); } catch (error) { toast({ - variant: "destructive", - title: "同步失败", - description: "项目同步任务启动失败", + variant: 'destructive', + title: '同步失败', + description: '项目同步失败', }); } finally { - setSyncing(prev => ({ ...prev, projects: false })); + setSyncing((prev) => ({ ...prev, projects: false })); } }; // 同步分支 const handleSyncBranches = async () => { - if (!selectedInstance) return; - setSyncing(prev => ({ ...prev, branches: true })); + if (!selectedInstanceId || !selectedProject || !selectedGroup) return; + + setSyncing((prev) => ({ ...prev, branches: true })); try { - await syncGitBranches(selectedInstance); + // 传递 repoProjectId, repoGroupId (Git系统中的ID) 和 externalSystemId + await syncRepositoryBranches( + selectedInstanceId, + selectedProject.repoProjectId, + selectedGroup.repoGroupId + ); toast({ - title: "同步成功", - description: "分支同步任务已启动", + title: '同步成功', + description: `已开始同步项目"${selectedProject.name}"的分支`, }); - loadInstanceInfo(); + await loadBranches(selectedProject.repoProjectId || selectedProject.id); } catch (error) { toast({ - variant: "destructive", - title: "同步失败", - description: "分支同步任务启动失败", + variant: 'destructive', + title: '同步失败', + description: '分支同步失败', }); } finally { - setSyncing(prev => ({ ...prev, branches: false })); + setSyncing((prev) => ({ ...prev, branches: false })); } }; - // 复制路径 - const handleCopyPath = (path: string) => { - navigator.clipboard.writeText(path); - toast({ - title: "复制成功", - description: `已复制路径: ${path}`, + // 切换仓库组展开/折叠 + const toggleGroupExpand = (groupId: number) => { + setExpandedGroupIds((prev) => { + const next = new Set(prev); + if (next.has(groupId)) { + next.delete(groupId); + } else { + next.add(groupId); + } + return next; }); }; + // 选中仓库组 + const handleSelectGroup = (group: RepositoryGroupResponse) => { + setSelectedGroup(group); + setSelectedProject(undefined); + setBranches([]); + loadProjects(group.repoGroupId || group.id); + }; + + // 选中项目 + const handleSelectProject = (project: RepositoryProjectResponse) => { + setSelectedProject(project); + loadBranches(project.repoProjectId || project.id); + }; + // 格式化时间 const formatTime = (time?: string) => { - if (!time) return 'Never'; + if (!time) return '-'; const diff = dayjs().diff(dayjs(time), 'day'); if (diff > 7) { return dayjs(time).format('YYYY-MM-DD HH:mm'); @@ -228,86 +346,156 @@ const GitManager: React.FC = () => { return dayjs(time).fromNow(); }; - // 过滤仓库组 - const filteredGroups = useMemo(() => { - if (!instanceInfo?.repositoryGroupList) return []; - return instanceInfo.repositoryGroupList.filter(group => { - const matchSearch = !groupSearch || + // 渲染可见性图标 + const renderVisibilityIcon = (visibility: string) => { + const config = + VISIBILITY_CONFIG[visibility.toLowerCase() as keyof typeof VISIBILITY_CONFIG] || + VISIBILITY_CONFIG.public; + const Icon = config.icon; + return ; + }; + + // 过滤仓库组树 + const filterGroupTree = (groups: RepositoryGroupResponse[]): RepositoryGroupResponse[] => { + if (!groupSearch) return groups; + + return groups.filter((group) => { + const matchesSearch = group.name.toLowerCase().includes(groupSearch.toLowerCase()) || - group.path.toLowerCase().includes(groupSearch.toLowerCase()); - const matchVisibility = groupVisibility === 'all' || group.visibility === groupVisibility; - return matchSearch && matchVisibility; - }); - }, [instanceInfo?.repositoryGroupList, groupSearch, groupVisibility]); + (group.path && group.path.toLowerCase().includes(groupSearch.toLowerCase())); + + const filteredChildren = group.children + ? filterGroupTree(group.children) + : []; + + return matchesSearch || filteredChildren.length > 0; + }).map((group) => ({ + ...group, + children: group.children ? filterGroupTree(group.children) : [], + })); + }; - // 过滤项目 + // 渲染仓库组树节点 + const renderGroupTreeNode = ( + group: RepositoryGroupResponse, + level: number = 0 + ): React.ReactNode => { + const hasChildren = group.children && group.children.length > 0; + const isExpanded = expandedGroupIds.has(group.id); + + return ( + +
handleSelectGroup(group)} + > + {hasChildren ? ( + + ) : ( + + )} + + + {renderVisibilityIcon(group.visibility)} + + + {group.name} + +
+ + {hasChildren && isExpanded && ( +
+ {group.children!.map((child) => + renderGroupTreeNode(child, level + 1) + )} +
+ )} +
+ ); + }; + + // 过滤项目列表 const filteredProjects = useMemo(() => { - if (!instanceInfo?.repositoryProjectList) return []; - return instanceInfo.repositoryProjectList.filter(project => { - const matchSearch = !projectSearch || + if (!projectSearch) return projects; + return projects.filter( + (project) => project.name.toLowerCase().includes(projectSearch.toLowerCase()) || - project.path.toLowerCase().includes(projectSearch.toLowerCase()); - const matchVisibility = projectVisibility === 'all' || project.visibility === projectVisibility; - const matchGroup = projectGroup === 'all' || (project.groupId || project.id).toString() === projectGroup; - return matchSearch && matchVisibility && matchGroup; - }); - }, [instanceInfo?.repositoryProjectList, projectSearch, projectVisibility, projectGroup]); + (project.path && project.path.toLowerCase().includes(projectSearch.toLowerCase())) + ); + }, [projects, projectSearch]); + // 过滤分支列表 + const filteredBranches = useMemo(() => { + if (!branchSearch) return branches; + return branches.filter((branch) => + branch.name.toLowerCase().includes(branchSearch.toLowerCase()) + ); + }, [branches, branchSearch]); + + // 初始化加载 useEffect(() => { loadInstances(); }, []); useEffect(() => { - if (selectedInstance) { - loadInstanceInfo(); + if (selectedInstanceId) { + loadGroupTree(); + loadStatistics(); + setSelectedGroup(undefined); + setSelectedProject(undefined); + setProjects([]); + setBranches([]); } - }, [selectedInstance]); + }, [selectedInstanceId]); - // 渲染可见性 Badge - const renderVisibilityBadge = (visibility: string) => { - const config = VISIBILITY_CONFIG[visibility.toLowerCase() as keyof typeof VISIBILITY_CONFIG] || VISIBILITY_CONFIG.public; - const Icon = config.icon; - return ( - - - {config.label} - - ); - }; - - // 点击仓库组,跳转到项目列表 - const handleGroupClick = (groupId: number) => { - // 清空项目搜索和可见性过滤 - setProjectSearch(''); - setProjectVisibility('all'); - // 设置仓库组过滤 - setProjectGroup((groupId || '').toString()); - // 切换到项目 Tab - setActiveTab('projects'); - }; + const filteredGroupTree = useMemo(() => filterGroupTree(groupTree), [groupTree, groupSearch]); return ( -
- {/* 页面标题栏 */} +
+ {/* 顶部标题栏 */}
+

Git 仓库管理

+

+ 管理和浏览 Git 仓库、项目和分支 +

+
-
{/* 统计卡片 */} -
- {/* 仓库组卡片 */} - -
+ {statistics && ( +
+ - - 仓库组 - -
+ 仓库组 - -
-
{instanceInfo?.totalGroups || 0}
-

- 最后同步: {formatTime(instanceInfo?.lastSyncGroupsTime)} -

+
{statistics.totalGroups}
- - {/* 项目卡片 */} - -
+ - - 项目 - -
- - -
+ 项目 +
-
{instanceInfo?.totalProjects || 0}
-

- 最后同步: {formatTime(instanceInfo?.lastSyncProjectsTime)} -

+
{statistics.totalProjects}
- - {/* 分支卡片 */} - -
+ - - 分支 - -
+ 分支 + + +
{statistics.totalBranches}
+
+ +
+ )} + + {/* 三栏布局 */} +
+ {/* 左栏:仓库组树 */} + + +
+ 仓库组
-
- -
{instanceInfo?.totalBranches || 0}
-

- 最后同步: {formatTime(instanceInfo?.lastSyncBranchesTime)} -

-
-
-
- - {/* Tabs 区域 */} - - - 仓库组 ({filteredGroups.length}) - 项目 ({filteredProjects.length}) - - - {/* 仓库组 Tab */} - - - -
-
- +
+ setGroupSearch(e.target.value)} - className="pl-10" + className="pl-8 h-8" />
- - -
- - - - - - {loading ? ( -
- + + +
+ {loading.groups ? ( +
+
- ) : filteredGroups.length === 0 ? ( -
- -

暂无仓库组

-

请同步 Git 实例数据

+ ) : filteredGroupTree.length === 0 ? ( +
+ +

暂无仓库组

) : ( - - - - 名称 - 路径 - 可见性 - 描述 - 操作 - - - - {filteredGroups.map((group) => ( - - - - - -
- - {group.path} - + filteredGroupTree.map((group) => renderGroupTreeNode(group)) + )} +
+ + + + {/* 中栏:项目列表 */} + + +
+ + 项目 {selectedGroup && `(${filteredProjects.length})`} +
-
- - {renderVisibilityBadge(group.visibility)} - - - {group.description || '-'} - - -
- - {group.webUrl && ( - - - - )} -
-
-
- ))} -
-
- )} - - - - - {/* 项目 Tab */} - - - -
-
- +
+ setProjectSearch(e.target.value)} - className="pl-10" + className="pl-8 h-8" />
- - - + +
+ {loading.projects ? ( +
+
- - - - - - {loading ? ( -
- + ) : !selectedGroup ? ( +
+ +

请先选择仓库组

) : filteredProjects.length === 0 ? ( -
- -

暂无项目

-

请同步 Git 实例数据

+
+ +

暂无项目

) : ( - - - - 名称 - 路径 - 可见性 - 默认分支 - 最后活动时间 - 操作 - - - +
{filteredProjects.map((project) => ( - - {project.name} - -
- - {project.path} - - +
handleSelectProject(project)} + > +
+
+
+ + {renderVisibilityIcon(project.visibility)} + + {project.name} +
- - - {renderVisibilityBadge(project.visibility)} - - - + {project.description && ( +

+ {project.description} +

+ )} +
+ {project.isDefaultBranch && ( + {project.isDefaultBranch} - - - + + )} + {project.lastActivityAt && ( + + {formatTime(project.lastActivityAt)} - - -
- + + )} +
+
{project.webUrl && ( e.stopPropagation()} + className="flex-shrink-0" > - )}
- - - ))} - -
- )} - +
+ ))} +
+ )} +
+ + + {/* 右栏:分支列表 */} + + +
+ + 分支 {selectedProject && `(${filteredBranches.length})`} + + +
+
+ + setBranchSearch(e.target.value)} + className="pl-8 h-8" + /> +
+
+
+ {loading.branches ? ( +
+ +
+ ) : !selectedProject ? ( +
+ +

请先选择项目

+
+ ) : filteredBranches.length === 0 ? ( +
+ +

暂无分支

+
+ ) : ( +
+ {filteredBranches.map((branch) => ( +
+
+ +
+
+ + {branch.name} + + {branch.isDefaultBranch && ( + + 默认 + + )} + {branch.isProtected && ( + + + 受保护 + + )} +
+ {branch.lastCommitMessage && ( +

+ + {branch.lastCommitMessage} +

+ )} + {(branch.commitAuthor || branch.commitDate) && ( +
+ {branch.commitAuthor && ( + + + {branch.commitAuthor} + + )} + {branch.commitDate && ( + + + {formatTime(branch.commitDate)} + + )} +
+ )} +
+
+ {branch.webUrl && ( + + + 在 Git 中查看 + + )} +
+ ))} +
+ )} +
- - +
); }; diff --git a/frontend/src/pages/Deploy/GitManager/List/service.ts b/frontend/src/pages/Deploy/GitManager/List/service.ts index 83e9fff2..4038f372 100644 --- a/frontend/src/pages/Deploy/GitManager/List/service.ts +++ b/frontend/src/pages/Deploy/GitManager/List/service.ts @@ -1,33 +1,126 @@ import request from '@/utils/request'; -import type { GitInstanceInfo } from './types'; +import type { + RepositoryGroupResponse, + RepositoryGroupQuery, + RepositoryProjectResponse, + RepositoryProjectQuery, + RepositoryBranchResponse, + RepositoryBranchQuery, + GitStatistics, +} from './types'; import { getExternalSystems } from '@/pages/Deploy/External/service'; import { SystemType } from '@/pages/Deploy/External/types'; -const BASE_URL = '/api/v1/repository-manager'; +// API 基础路径 +const GROUP_URL = '/api/v1/repository-group'; +const PROJECT_URL = '/api/v1/repository-project'; +const BRANCH_URL = '/api/v1/repository-branch'; -// 获取 Git 实例列表 -export const getGitInstances = () => +// ==================== Git 实例 ==================== + +/** + * 获取 Git 实例列表 + */ +export const getGitInstances = () => getExternalSystems({ type: SystemType.GIT, - enabled: true - }).then(response => response.content); + enabled: true, + }).then((response) => response.content); -// 获取 Git 实例信息 -export const getGitInstanceInfo = (externalSystemId: number) => - request.get(`${BASE_URL}/${externalSystemId}/instance`); +// ==================== 仓库组 ==================== -// 同步所有 Git 数据 +/** + * 获取仓库组列表 + */ +export const getRepositoryGroups = (params?: RepositoryGroupQuery) => + request.get(`${GROUP_URL}/list`, { params }); + +/** + * 获取仓库组树形结构 + */ +export const getRepositoryGroupTree = (externalSystemId: number) => + request.get(`${GROUP_URL}/tree`, { + params: { externalSystemId }, + }); + +/** + * 同步仓库组数据 + */ +export const syncRepositoryGroups = (externalSystemId: number) => + request.post(`${GROUP_URL}/sync`, null, { + params: { externalSystemId }, + }); + +// ==================== 仓库项目 ==================== + +/** + * 获取仓库项目列表 + */ +export const getRepositoryProjects = (params?: RepositoryProjectQuery) => + request.get(`${PROJECT_URL}/list`, { params }); + +/** + * 根据仓库组ID获取项目列表 + */ +export const getProjectsByGroupId = (repoGroupId: number) => + request.get(`${PROJECT_URL}/list`, { + params: { repoGroupId }, + }); + +/** + * 同步仓库项目数据 + */ +export const syncRepositoryProjects = (externalSystemId: number, repoGroupId?: number) => + request.post(`${PROJECT_URL}/sync`, null, { + params: { externalSystemId, repoGroupId }, + }); + +// ==================== 仓库分支 ==================== + +/** + * 获取仓库分支列表 + */ +export const getRepositoryBranches = (params?: RepositoryBranchQuery) => + request.get(`${BRANCH_URL}/list`, { params }); + +/** + * 根据项目ID获取分支列表 + */ +export const getBranchesByProjectId = (repoProjectId: number) => + request.get(`${BRANCH_URL}/list`, { + params: { repoProjectId }, + }); + +/** + * 同步仓库分支数据 + */ +export const syncRepositoryBranches = ( + externalSystemId: number, + repoProjectId?: number, + repoGroupId?: number +) => + request.post(`${BRANCH_URL}/sync`, null, { + params: { externalSystemId, repoProjectId, repoGroupId }, + }); + +// ==================== 统计信息 ==================== + +/** + * 获取Git仓库统计信息 + */ +export const getGitStatistics = (externalSystemId: number) => + request.get(`${GROUP_URL}/statistics`, { + params: { externalSystemId }, + }); + +// ==================== 批量操作 ==================== + +/** + * 同步所有数据(全量同步,不指定仓库组和项目) + */ export const syncAllGitData = (externalSystemId: number) => - request.post(`${BASE_URL}/${externalSystemId}/sync-all`); - -// 同步 Git 仓库组 -export const syncGitGroups = (externalSystemId: number) => - request.post(`${BASE_URL}/${externalSystemId}/sync-groups`); - -// 同步 Git 项目 -export const syncGitProjects = (externalSystemId: number) => - request.post(`${BASE_URL}/${externalSystemId}/groups/sync-projects`); - -// 同步 Git 分支 -export const syncGitBranches = (externalSystemId: number) => - request.post(`${BASE_URL}/${externalSystemId}/projects/sync-branches`); + Promise.all([ + syncRepositoryGroups(externalSystemId), + syncRepositoryProjects(externalSystemId), + syncRepositoryBranches(externalSystemId), + ]); diff --git a/frontend/src/pages/Deploy/GitManager/List/types.ts b/frontend/src/pages/Deploy/GitManager/List/types.ts index 1cda8bc8..5785889c 100644 --- a/frontend/src/pages/Deploy/GitManager/List/types.ts +++ b/frontend/src/pages/Deploy/GitManager/List/types.ts @@ -1,88 +1,151 @@ import type { BaseResponse } from '@/types/base'; -import type { ExternalSystemResponse } from '@/pages/Deploy/External/types'; -// Git仓库组 -export interface RepositoryGroup { - id: number; - groupId: number; +/** + * 仓库组响应类型 + */ +export interface RepositoryGroupResponse extends BaseResponse { + /** 仓库组名 */ name: string; + /** 仓库组描述 */ + description?: string; + /** 父级仓库组ID */ + parentId?: number; + /** 仓库组路径 */ path: string; - description: string; + /** 头像URL */ + avatarUrl?: string; + /** 完整名称(包含层级关系) */ + fullName?: string; + /** 完整路径 */ + fullPath?: string; + /** 网页URL */ + webUrl?: string; + /** 可见性 */ visibility: string; - parentId: number | null; - webUrl: string; - avatarUrl: string; + /** 排序号 */ + sort?: number; + /** 外部系统中的组ID */ + repoGroupId?: number; + /** 外部系统ID */ + externalSystemId: number; + /** 子仓库组列表(用于树形结构) */ + children?: RepositoryGroupResponse[]; } -// Git项目 -export interface RepositoryProject { - id: number; - projectId: number; +/** + * 仓库项目响应类型 + */ +export interface RepositoryProjectResponse extends BaseResponse { + /** 项目名称 */ name: string; + /** 项目路径 */ path: string; - description: string; + /** 项目描述 */ + description?: string; + /** 可见性 */ visibility: string; - groupId: number; - isDefaultBranch: string; - webUrl: string; - sshUrl: string; - httpUrl: string; - lastActivityAt: string; + /** 默认分支名称 */ + isDefaultBranch?: string; + /** 网页URL */ + webUrl?: string; + /** SSH URL */ + sshUrl?: string; + /** HTTP URL */ + httpUrl?: string; + /** 最后活动时间 */ + lastActivityAt?: string; + /** 外部系统ID */ + externalSystemId: number; + /** 所属仓库组ID */ + repoGroupId?: number; + /** 外部系统中的项目ID */ + repoProjectId?: number; } -// Git实例信息 -export interface GitInstanceInfo { - totalGroups: number; - lastSyncGroupsTime: string; - totalProjects: number; - lastSyncProjectsTime: string; - totalBranches: number; - lastSyncBranchesTime: string; - repositoryGroupList: RepositoryGroup[]; - repositoryProjectList: RepositoryProject[]; -} - -// 使用外部系统响应作为 Git 实例 -export type GitInstance = ExternalSystemResponse; - -// Git 仓库详情 -export interface GitRepositoryDTO extends BaseResponse { - repoName: string; - repoUrl: string; - description: string; - defaultBranch: string; - visibility: 'public' | 'private'; - lastCommitTime?: string; +/** + * 仓库分支响应类型 + */ +export interface RepositoryBranchResponse extends BaseResponse { + /** 分支名称 */ + name: string; + /** 是否为默认分支 */ + isDefaultBranch?: boolean; + /** 是否受保护 */ + isProtected?: boolean; + /** 是否可推送 */ + canPush?: boolean; + /** 开发者是否可推送 */ + developersCanPush?: boolean; + /** 开发者是否可合并 */ + developersCanMerge?: boolean; + /** 最后提交ID */ + lastCommitId?: string; + /** 最后提交信息 */ lastCommitMessage?: string; - lastCommitAuthor?: string; - totalBranches: number; - totalTags: number; - totalCommits: number; + /** 提交作者 */ + commitAuthor?: string; + /** 提交日期 */ + commitDate?: string; + /** 网页URL */ + webUrl?: string; + /** 所属项目ID */ + repoProjectId: number; + /** 外部系统ID */ externalSystemId: number; } -// Git 分支信息 -export interface GitBranchDTO extends BaseResponse { - branchName: string; - isDefault: boolean; - isProtected: boolean; - lastCommitSha: string; - lastCommitMessage: string; - lastCommitAuthor: string; - lastCommitTime: string; - repositoryId: number; +/** + * 仓库组查询参数 + */ +export interface RepositoryGroupQuery { + /** 仓库组名称 */ + name?: string; + /** 父级ID */ + parentId?: number; + /** 外部系统ID */ + externalSystemId?: number; + /** 可见性 */ + visibility?: string; } -// Git 标签信息 -export interface GitTagDTO extends BaseResponse { - tagName: string; - tagMessage: string; - taggerName: string; - taggerEmail: string; - taggedTime: string; - commitSha: string; - repositoryId: number; +/** + * 仓库项目查询参数 + */ +export interface RepositoryProjectQuery { + /** 项目名称 */ + name?: string; + /** 仓库组ID */ + repoGroupId?: number; + /** 外部系统ID */ + externalSystemId?: number; + /** 可见性 */ + visibility?: string; } -// 同步类型 -export type SyncType = 'repositories' | 'branches' | 'tags'; +/** + * 仓库分支查询参数 + */ +export interface RepositoryBranchQuery { + /** 分支名称 */ + name?: string; + /** 项目ID */ + repoProjectId?: number; + /** 外部系统ID */ + externalSystemId?: number; + /** 是否为默认分支 */ + isDefaultBranch?: boolean; +} + +/** + * 统计信息 + */ +export interface GitStatistics { + /** 总仓库组数 */ + totalGroups: number; + /** 总项目数 */ + totalProjects: number; + /** 总分支数 */ + totalBranches: number; + /** 最后同步时间 */ + lastSyncTime?: string; +}