diff --git a/frontend/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx index 3fd47ada..013cf88c 100644 --- a/frontend/src/components/ui/progress.tsx +++ b/frontend/src/components/ui/progress.tsx @@ -3,10 +3,14 @@ import * as ProgressPrimitive from "@radix-ui/react-progress" import { cn } from "@/lib/utils" +export interface ProgressProps extends React.ComponentPropsWithoutRef { + indicatorClassName?: string; +} + const Progress = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( + ProgressProps +>(({ className, value, indicatorClassName, ...props }, ref) => ( diff --git a/frontend/src/pages/Deploy/GitManager/List/index.tsx b/frontend/src/pages/Deploy/GitManager/List/index.tsx index dd5fb0e8..632f0079 100644 --- a/frontend/src/pages/Deploy/GitManager/List/index.tsx +++ b/frontend/src/pages/Deploy/GitManager/List/index.tsx @@ -1,13 +1,12 @@ -import React, { useState, useEffect } from 'react'; -import { PageContainer } from '@ant-design/pro-components'; +import React, { useState, useEffect, useMemo } from 'react'; import { Card, CardContent, - CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Table, TableBody, @@ -25,16 +24,21 @@ import { SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { useToast } from "@/components/ui/use-toast"; import { GitBranch, GitFork, RefreshCw, - Link2, - FolderGit2 + FolderGit2, + Globe, + Lock, + Users, + Copy, + ExternalLink, + Search, + Loader2 } from "lucide-react"; -import { message } from 'antd'; -import type { GitInstance, GitInstanceInfo, RepositoryGroup, RepositoryProject } from './types'; +import type { GitInstance, GitInstanceInfo } from './types'; import { getGitInstances, getGitInstanceInfo, @@ -43,12 +47,42 @@ import { syncGitProjects, syncGitBranches } from './service'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +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' }, +}; const GitManager: React.FC = () => { + const { toast } = useToast(); const [loading, setLoading] = useState(false); const [instances, setInstances] = useState([]); const [selectedInstance, setSelectedInstance] = useState(); const [instanceInfo, setInstanceInfo] = useState(); + + // 同步状态 + const [syncing, setSyncing] = useState({ + all: false, + groups: false, + projects: false, + 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'); // 加载Git实例列表 const loadInstances = async () => { @@ -61,7 +95,11 @@ const GitManager: React.FC = () => { } } } catch (error) { - message.error('加载Git实例失败'); + toast({ + variant: "destructive", + title: "加载失败", + description: "加载 Git 实例列表失败", + }); } }; @@ -73,7 +111,11 @@ const GitManager: React.FC = () => { const data = await getGitInstanceInfo(selectedInstance); setInstanceInfo(data); } catch (error) { - message.error('加载实例信息失败'); + toast({ + variant: "destructive", + title: "加载失败", + description: "加载实例信息失败", + }); } finally { setLoading(false); } @@ -82,51 +124,135 @@ const GitManager: React.FC = () => { // 同步所有数据 const handleSyncAll = async () => { if (!selectedInstance) return; + setSyncing(prev => ({ ...prev, all: true })); try { await syncAllGitData(selectedInstance); - message.success('同步任务已启动'); + toast({ + title: "同步成功", + description: "同步任务已启动", + }); loadInstanceInfo(); } catch (error) { - message.error('同步失败'); + toast({ + variant: "destructive", + title: "同步失败", + description: "同步任务启动失败", + }); + } finally { + setSyncing(prev => ({ ...prev, all: false })); } }; // 同步仓库组 const handleSyncGroups = async () => { if (!selectedInstance) return; + setSyncing(prev => ({ ...prev, groups: true })); try { await syncGitGroups(selectedInstance); - message.success('仓库组同步任务已启动'); + toast({ + title: "同步成功", + description: "仓库组同步任务已启动", + }); loadInstanceInfo(); } catch (error) { - message.error('同步失败'); + toast({ + variant: "destructive", + title: "同步失败", + description: "仓库组同步任务启动失败", + }); + } finally { + setSyncing(prev => ({ ...prev, groups: false })); } }; // 同步项目 const handleSyncProjects = async () => { if (!selectedInstance) return; + setSyncing(prev => ({ ...prev, projects: true })); try { await syncGitProjects(selectedInstance); - message.success('项目同步任务已启动'); + toast({ + title: "同步成功", + description: "项目同步任务已启动", + }); loadInstanceInfo(); } catch (error) { - message.error('同步失败'); + toast({ + variant: "destructive", + title: "同步失败", + description: "项目同步任务启动失败", + }); + } finally { + setSyncing(prev => ({ ...prev, projects: false })); } }; // 同步分支 const handleSyncBranches = async () => { if (!selectedInstance) return; + setSyncing(prev => ({ ...prev, branches: true })); try { await syncGitBranches(selectedInstance); - message.success('分支同步任务已启动'); + toast({ + title: "同步成功", + description: "分支同步任务已启动", + }); loadInstanceInfo(); } catch (error) { - message.error('同步失败'); + toast({ + variant: "destructive", + title: "同步失败", + description: "分支同步任务启动失败", + }); + } finally { + setSyncing(prev => ({ ...prev, branches: false })); } }; + // 复制路径 + const handleCopyPath = (path: string) => { + navigator.clipboard.writeText(path); + toast({ + title: "复制成功", + description: `已复制路径: ${path}`, + }); + }; + + // 格式化时间 + const formatTime = (time?: string) => { + if (!time) return 'Never'; + const diff = dayjs().diff(dayjs(time), 'day'); + if (diff > 7) { + return dayjs(time).format('YYYY-MM-DD HH:mm'); + } + return dayjs(time).fromNow(); + }; + + // 过滤仓库组 + const filteredGroups = useMemo(() => { + if (!instanceInfo?.repositoryGroupList) return []; + return instanceInfo.repositoryGroupList.filter(group => { + const matchSearch = !groupSearch || + 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]); + + // 过滤项目 + const filteredProjects = useMemo(() => { + if (!instanceInfo?.repositoryProjectList) return []; + return instanceInfo.repositoryProjectList.filter(project => { + const matchSearch = !projectSearch || + 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]); + useEffect(() => { loadInstances(); }, []); @@ -137,22 +263,41 @@ const GitManager: React.FC = () => { } }, [selectedInstance]); + // 渲染可见性 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'); + }; + return ( - - - 同步所有数据 - , +
+ {/* 页面标题栏 */} +
+

Git 仓库管理

+
- ], - }} - > - {selectedInstance && instances.find(i => i.id === selectedInstance) && ( - - - {instances.find(i => i.id === selectedInstance)?.name} - - {instances.find(i => i.id === selectedInstance)?.url} - - - - )} + +
+
-
- - - -
- - {instanceInfo?.totalGroups || 0} -
-
仓库组
+ {/* 统计卡片 */} +
+ {/* 仓库组卡片 */} + +
+ + + 仓库组 - +
+ + +
-

- Last sync: {instanceInfo?.lastSyncGroupsTime ? new Date(instanceInfo.lastSyncGroupsTime).toLocaleString() : 'Never'} +

{instanceInfo?.totalGroups || 0}
+

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

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

- Last sync: {instanceInfo?.lastSyncProjectsTime ? new Date(instanceInfo.lastSyncProjectsTime).toLocaleString() : 'Never'} +

{instanceInfo?.totalProjects || 0}
+

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

- - - -
- - {instanceInfo?.totalBranches || 0} -
-
分支
+ {/* 分支卡片 */} + +
+ + + 分支 - +
+ + +
-

- Last sync: {instanceInfo?.lastSyncBranchesTime ? new Date(instanceInfo.lastSyncBranchesTime).toLocaleString() : 'Never'} +

{instanceInfo?.totalBranches || 0}
+

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

- + {/* Tabs 区域 */} + - 仓库组 - 项目 + 仓库组 ({filteredGroups.length}) + 项目 ({filteredProjects.length}) + {/* 仓库组 Tab */} + + +
+
+ + setGroupSearch(e.target.value)} + className="pl-10" + /> +
+ + +
+
+
+ - - - - - 名称 - 路径 - 可见性 - 描述 - 操作 - - - - {instanceInfo?.repositoryGroupList?.map((group) => ( - - {group.name} - {group.path} - - - {group.visibility} - - - {group.description} - - - + + {loading ? ( +
+ +
+ ) : filteredGroups.length === 0 ? ( +
+ +

暂无仓库组

+

请同步 Git 实例数据

+
+ ) : ( +
+ + + 名称 + 路径 + 可见性 + 描述 + 操作 - ))} - -
+ + + {filteredGroups.map((group) => ( + + + + + +
+ + {group.path} + + +
+
+ + {renderVisibilityBadge(group.visibility)} + + + {group.description || '-'} + + +
+ + {group.webUrl && ( + + + + )} +
+
+
+ ))} +
+ + )}
+ {/* 项目 Tab */} + + +
+
+ + setProjectSearch(e.target.value)} + className="pl-10" + /> +
+ + + +
+
+
+ - - - - - 名称 - 路径 - 默认分支 - 最后活动时间 - 操作 - - - - {instanceInfo?.repositoryProjectList?.map((project) => ( - - {project.name} - {project.path} - {project.isDefaultBranch} - {new Date(project.lastActivityAt).toLocaleString()} - -
- - -
-
+ + {loading ? ( +
+ +
+ ) : filteredProjects.length === 0 ? ( +
+ +

暂无项目

+

请同步 Git 实例数据

+
+ ) : ( +
+ + + 名称 + 路径 + 可见性 + 默认分支 + 最后活动时间 + 操作 - ))} - -
+ + + {filteredProjects.map((project) => ( + + {project.name} + +
+ + {project.path} + + +
+
+ + {renderVisibilityBadge(project.visibility)} + + + + + {project.isDefaultBranch} + + + + {formatTime(project.lastActivityAt)} + + +
+ + {project.webUrl && ( + + + + )} +
+
+
+ ))} +
+ + )}
- +
); }; diff --git a/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx b/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx index 3381d48b..668c4bcf 100644 --- a/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx +++ b/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx @@ -1,342 +1,560 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { PageContainer } from '@/components/ui/page-container'; -import { RefreshCw, ChevronLeft, ChevronRight } from 'lucide-react'; +import { RefreshCw, Layers, Box, Activity, Search, CheckCircle, XCircle, AlertTriangle, Loader2, ExternalLink } from 'lucide-react'; import { - Card, - CardContent, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; + Card, + CardContent, + CardHeader, + CardTitle, +} from '@/components/ui/card'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Badge } from "@/components/ui/badge"; -import { useToast } from "@/components/ui/use-toast"; + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { useToast } from '@/components/ui/use-toast'; +import { Progress } from '@/components/ui/progress'; import type { JenkinsInstance, JenkinsInstanceDTO } from './types'; import { getJenkinsInstances, getJenkinsInstance, syncViews, syncJobs, syncBuilds } from './service'; const JenkinsManagerList: React.FC = () => { - const [jenkinsList, setJenkinsList] = useState([]); - const [currentJenkinsId, setCurrentJenkinsId] = useState(); - const [currentJenkins, setCurrentJenkins] = useState(); - const [instanceDetails, setInstanceDetails] = useState(); - const [loading, setLoading] = useState(false); - const [currentView, setCurrentView] = useState(); - const [syncing, setSyncing] = useState>({ - views: false, - jobs: false, - builds: false - }); - const tabsRef = useRef(null); - const { toast } = useToast(); + const [jenkinsList, setJenkinsList] = useState([]); + const [currentJenkinsId, setCurrentJenkinsId] = useState(); + const [instanceDetails, setInstanceDetails] = useState(); + const [loading, setLoading] = useState(false); + const [currentView, setCurrentView] = useState(); + const [syncing, setSyncing] = useState>({ + views: false, + jobs: false, + builds: false, + all: false, + }); + + // 搜索和过滤状态 + const [searchKeyword, setSearchKeyword] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); - // 获取 Jenkins 实例列表 - const loadJenkinsList = async () => { - try { - const data = await getJenkinsInstances(); - setJenkinsList(data); - // 如果没有选中的实例,默认选择第一个 - if (!currentJenkinsId && data.length > 0) { - setCurrentJenkinsId(String(data[0].id)); - setCurrentJenkins(data[0]); - } - } catch (error) { - toast({ - variant: "destructive", - title: "获取 Jenkins 实例列表失败", - duration: 3000, - }); - } + const { toast } = useToast(); + + // 获取 Jenkins 实例列表 + const loadJenkinsList = async () => { + try { + const data = await getJenkinsInstances(); + setJenkinsList(data); + if (!currentJenkinsId && data.length > 0) { + setCurrentJenkinsId(String(data[0].id)); + } + } catch (error) { + toast({ + variant: 'destructive', + title: '获取 Jenkins 实例列表失败', + duration: 3000, + }); + } + }; + + // 获取 Jenkins 实例详情 + const loadInstanceDetails = async () => { + if (!currentJenkinsId) return; + setLoading(true); + try { + const data = await getJenkinsInstance(currentJenkinsId); + setInstanceDetails(data); + // 设置默认视图 + if (data.jenkinsViewList.length > 0 && !currentView) { + setCurrentView(data.jenkinsViewList[0].id); + } + } catch (error) { + toast({ + variant: 'destructive', + title: '获取实例详情失败', + duration: 3000, + }); + } finally { + setLoading(false); + } + }; + + // 切换 Jenkins 实例 + const handleJenkinsChange = (id: string) => { + setCurrentJenkinsId(id); + setInstanceDetails(undefined); + setCurrentView(undefined); + setSearchKeyword(''); + setStatusFilter('all'); + }; + + // 单个同步 + const handleSync = async (type: 'views' | 'jobs' | 'builds') => { + if (!currentJenkinsId) return; + setSyncing((prev) => ({ ...prev, [type]: true })); + try { + switch (type) { + case 'views': + await syncViews(currentJenkinsId); + break; + case 'jobs': + await syncJobs(currentJenkinsId); + break; + case 'builds': + await syncBuilds(currentJenkinsId); + break; + } + await loadInstanceDetails(); + toast({ + title: '同步成功', + duration: 2000, + }); + } catch (error) { + toast({ + variant: 'destructive', + title: '同步失败', + duration: 3000, + }); + } finally { + setSyncing((prev) => ({ ...prev, [type]: false })); + } + }; + + // 全部同步 + const handleSyncAll = async () => { + if (!currentJenkinsId) return; + setSyncing((prev) => ({ ...prev, all: true })); + try { + await Promise.all([ + syncViews(currentJenkinsId), + syncJobs(currentJenkinsId), + syncBuilds(currentJenkinsId), + ]); + await loadInstanceDetails(); + toast({ + title: '全部同步成功', + duration: 2000, + }); + } catch (error) { + toast({ + variant: 'destructive', + title: '同步失败', + description: '部分数据同步失败,请重试', + duration: 3000, + }); + } finally { + setSyncing((prev) => ({ ...prev, all: false })); + } + }; + + useEffect(() => { + loadJenkinsList(); + }, []); + + useEffect(() => { + if (currentJenkinsId) { + loadInstanceDetails(); + } + }, [currentJenkinsId]); + + const formatTime = (time: string | null | undefined) => { + if (!time) return 'Never'; + return time; + }; + + // 获取构建状态信息 + const getBuildStatusInfo = (status: string) => { + const statusMap: Record = { + SUCCESS: { + variant: 'default', + icon: , + label: '成功', + }, + FAILURE: { + variant: 'destructive', + icon: , + label: '失败', + }, + UNSTABLE: { + variant: 'secondary', + icon: , + label: '不稳定', + }, + ABORTED: { + variant: 'outline', + icon: , + label: '已中止', + }, + RUNNING: { + variant: 'outline', + icon: , + label: '构建中', + }, }; - - // 获取 Jenkins 实例详情 - const loadInstanceDetails = async () => { - if (!currentJenkinsId) return; - setLoading(true); - try { - const data = await getJenkinsInstance(currentJenkinsId); - setInstanceDetails(data); - } catch (error) { - toast({ - variant: "destructive", - title: "获取实例详情失败", - duration: 3000, - }); - } finally { - setLoading(false); - } + return statusMap[status] || { + variant: 'outline' as const, + icon: , + label: status || '未知', }; + }; - // 切换 Jenkins 实例 - const handleJenkinsChange = (id: string) => { - setCurrentJenkinsId(id); - const jenkins = jenkinsList.find(j => String(j.id) === id); - setCurrentJenkins(jenkins); - setInstanceDetails(undefined); - }; + // 获取健康度颜色 + const getHealthColor = (score: number) => { + if (score >= 80) return 'bg-green-500'; + if (score >= 60) return 'bg-green-400'; + if (score >= 40) return 'bg-yellow-400'; + if (score >= 20) return 'bg-orange-400'; + return 'bg-red-500'; + }; - // 同步数据 - const handleSync = async (type: 'views' | 'jobs' | 'builds') => { - if (!currentJenkinsId) return; - setSyncing(prev => ({ ...prev, [type]: true })); - try { - switch (type) { - case 'views': - await syncViews(currentJenkinsId); - break; - case 'jobs': - await syncJobs(currentJenkinsId); - break; - case 'builds': - await syncBuilds(currentJenkinsId); - break; - } - await loadInstanceDetails(); // 重新加载实例详情 - toast({ - title: "同步成功", - duration: 3000, - }); - } catch (error) { - toast({ - variant: "destructive", - title: "同步失败", - duration: 3000, - }); - } finally { - setSyncing(prev => ({ ...prev, [type]: false })); - } - }; - - // 在获取实例详情后设置默认视图 - useEffect(() => { - if (instanceDetails?.jenkinsViewList.length > 0) { - setCurrentView(String(instanceDetails.jenkinsViewList[0].id)); - } - }, [instanceDetails]); - - useEffect(() => { - loadJenkinsList(); - }, []); - - useEffect(() => { - if (currentJenkinsId) { - loadInstanceDetails(); - } - }, [currentJenkinsId]); - - const formatTime = (time: string | null | undefined) => { - if (!time) return 'Never'; - return time; - }; - - const handleScroll = (direction: 'left' | 'right') => { - if (tabsRef.current) { - const scrollAmount = 200; // 每次滚动的距离 - const newScrollLeft = direction === 'left' - ? tabsRef.current.scrollLeft - scrollAmount - : tabsRef.current.scrollLeft + scrollAmount; - tabsRef.current.scrollTo({ - left: newScrollLeft, - behavior: 'smooth' - }); - } - }; + // 过滤和搜索 Jobs + const filteredJobs = useMemo(() => { + if (!instanceDetails) return []; + + let jobs = instanceDetails.jenkinsJobList; + + // 按 View 过滤 + if (currentView) { + jobs = jobs.filter((job) => job.viewId === currentView); + } + + // 按搜索关键词过滤 + if (searchKeyword) { + jobs = jobs.filter((job) => + job.jobName.toLowerCase().includes(searchKeyword.toLowerCase()) || + job.description?.toLowerCase().includes(searchKeyword.toLowerCase()) + ); + } + + // 按状态过滤 + if (statusFilter !== 'all') { + jobs = jobs.filter((job) => job.lastBuildStatus === statusFilter); + } + + return jobs; + }, [instanceDetails, currentView, searchKeyword, statusFilter]); + // 空状态:没有实例 + if (jenkinsList.length === 0) { return ( - -
-

Jenkins 三方管理

- -
- - {currentJenkins && ( - - -
-
-

{currentJenkins.name}

-

{currentJenkins.url}

-
- -
-
-
- Views - {instanceDetails?.totalViews || 0} -
-
- Last sync: {formatTime(instanceDetails?.lastSyncViewsTime)} - -
-
- -
-
- Jobs - {instanceDetails?.totalJobs || 0} -
-
- Last sync: {formatTime(instanceDetails?.lastSyncJobsTime)} - -
-
- -
-
- Builds - {instanceDetails?.totalBuilds || 0} -
-
- Last sync: {formatTime(instanceDetails?.lastSyncBuildsTime)} - -
-
-
-
-
-
- )} - - {loading ? ( - - - - - - ) : instanceDetails && ( - -
-
- -
- -
- - {instanceDetails.jenkinsViewList.map(view => ( - -
- {view.viewName} - {view.description && ( - - ({view.description}) - - )} -
-
- ))} -
-
- -
- -
-
- - {instanceDetails.jenkinsViewList.map(view => ( - - - -
- {instanceDetails.jenkinsJobList - .filter(job => job.viewId === view.id) - .map(job => ( -
-
- - {job.jobName} - - {job.description && ( -

{job.description}

- )} -
-
- - #{job.lastBuildNumber} - {job.lastBuildStatus} - - {formatTime(job.lastBuildTime)} -
-
- ))} -
-
-
-
- ))} -
- )} -
+ +
+

Jenkins 三方管理

+
+ + + +

暂无 Jenkins 实例

+

+ 请先在外部服务管理中添加 Jenkins 系统 +

+ +
+
+
); + } + + return ( + + {/* 页面标题 */} +
+

Jenkins 三方管理

+
+ + +
+
+ + {/* 统计卡片 */} +
+ + + Views +
+ + +
+
+ +
{instanceDetails?.totalViews || 0}
+

+ 最后同步: {formatTime(instanceDetails?.lastSyncViewsTime)} +

+
+
+ + + + Jobs +
+ + +
+
+ +
{instanceDetails?.totalJobs || 0}
+

+ 最后同步: {formatTime(instanceDetails?.lastSyncJobsTime)} +

+
+
+ + + + Builds +
+ + +
+
+ +
{instanceDetails?.totalBuilds || 0}
+

+ 最后同步: {formatTime(instanceDetails?.lastSyncBuildsTime)} +

+
+
+
+ + {/* View 切换 */} + {instanceDetails && instanceDetails.jenkinsViewList.length > 0 && ( + + +
+ {instanceDetails.jenkinsViewList.map((view) => ( + + ))} +
+
+
+ )} + + {/* 搜索和过滤 */} + {instanceDetails && instanceDetails.jenkinsViewList.length > 0 && ( + +
+
+
+ + setSearchKeyword(e.target.value)} + className="pl-9" + /> +
+ + +
+
+
+ )} + + {/* Jobs 表格 */} + {loading ? ( + + + + + + ) : instanceDetails && instanceDetails.jenkinsViewList.length > 0 ? ( + + + + {instanceDetails.jenkinsViewList.find((v) => v.id === currentView)?.viewName || 'Jobs'} 列表 + + + +
+ + + + Job 名称 + 描述 + 最后构建 + 状态 + 健康度 + 构建时间 + 操作 + + + + {filteredJobs.length === 0 ? ( + + +
+ +

暂无任务

+
+
+
+ ) : ( + filteredJobs.map((job) => { + const statusInfo = getBuildStatusInfo(job.lastBuildStatus); + return ( + + +
+ {job.jobName} +
+
+ + {job.description || '-'} + + + #{job.lastBuildNumber} + + + + {statusInfo.icon} + {statusInfo.label} + + + +
+ + {job.healthReportScore}% +
+
+ + {formatTime(job.lastBuildTime)} + + + + +
+ ); + }) + )} +
+
+
+
+
+ ) : ( + + + +

暂无视图数据

+

+ 请点击上方的同步按钮获取 Jenkins 数据 +

+ +
+
+ )} +
+ ); }; export default JenkinsManagerList;