diff --git a/frontend/src/pages/Deploy/External/index.tsx b/frontend/src/pages/Deploy/External/index.tsx index cf8c3dd1..9aacac42 100644 --- a/frontend/src/pages/Deploy/External/index.tsx +++ b/frontend/src/pages/Deploy/External/index.tsx @@ -22,7 +22,7 @@ const ExternalPage: React.FC = () => { refresh } = useTableData({ service: { - list: service.getExternalSystems, + list: service.getExternalSystemsPage, create: service.createExternalSystem, update: service.updateExternalSystem, delete: service.deleteExternalSystem diff --git a/frontend/src/pages/Deploy/External/service.ts b/frontend/src/pages/Deploy/External/service.ts index 22788093..63d721ab 100644 --- a/frontend/src/pages/Deploy/External/service.ts +++ b/frontend/src/pages/Deploy/External/service.ts @@ -5,7 +5,10 @@ import type { ExternalSystemResponse, ExternalSystemRequest, ExternalSystemQuery const BASE_URL = '/api/v1/external-system'; // 获取三方系统列表 -export const getExternalSystems = (params: ExternalSystemQuery) => +export const getExternalSystemsPage = (params: ExternalSystemQuery) => + request.get>(`${BASE_URL}/page`, { params }); + +export const getExternalSystems = (params: ExternalSystemQuery) => request.get>(`${BASE_URL}/page`, { params }); // 创建三方系统 diff --git a/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx b/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx index 447a2c0a..050110ce 100644 --- a/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx +++ b/frontend/src/pages/Deploy/JenkinsManager/List/index.tsx @@ -1,22 +1,11 @@ import React, { useState, useEffect } from 'react'; import { PageContainer } from '@/components/ui/page-container'; -import { Plus, Pencil, Trash2, Loader2 } from 'lucide-react'; -import { - Table, - TableHeader, - TableBody, - TableHead, - TableRow, - TableCell, -} from "@/components/ui/table"; +import { RefreshCw, ChevronRight } from 'lucide-react'; import { Card, CardContent, - CardHeader, - CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { Select, SelectContent, @@ -26,71 +15,54 @@ import { } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { useToast } from "@/components/ui/use-toast"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { searchFormSchema, type SearchFormValues } from "./schema"; -import { DataTablePagination } from "@/components/ui/pagination"; -import type { JenkinsManagerResponse, JenkinsManagerQuery } from './types'; -import { getJenkinsManagerPage, deleteJenkinsManager } from './service'; -import JenkinsManagerModal from './components/JenkinsManagerModal'; - -interface Column { - accessorKey?: keyof JenkinsManagerResponse; - id?: string; - header: string; - size: number; - cell?: (props: { row: { original: JenkinsManagerResponse } }) => React.ReactNode; -} +import { Separator } from "@/components/ui/separator"; +import type { JenkinsInstance, JenkinsView, SyncType } from './types'; +import { getJenkinsInstances, getJenkinsViews, syncJenkinsData } from './service'; +import { SyncStatus } from '@/pages/Deploy/External/types'; const JenkinsManagerList: React.FC = () => { - const [modalVisible, setModalVisible] = useState(false); - const [currentRecord, setCurrentRecord] = useState(); - const [list, setList] = useState([]); + const [jenkinsList, setJenkinsList] = useState([]); + const [currentJenkinsId, setCurrentJenkinsId] = useState(); + const [currentJenkins, setCurrentJenkins] = useState(); + const [views, setViews] = useState([]); const [loading, setLoading] = useState(false); - const [pagination, setPagination] = useState({ - pageNum: 1, - pageSize: 10, - totalElements: 0, + const [syncing, setSyncing] = useState>({ + views: false, + jobs: false, + builds: false }); const { toast } = useToast(); - const form = useForm({ - resolver: zodResolver(searchFormSchema), - defaultValues: { - name: "", - enabled: undefined, - } - }); - - const loadData = async (params?: JenkinsManagerQuery) => { - setLoading(true); + // 获取 Jenkins 实例列表 + const loadJenkinsList = async () => { try { - const queryParams: JenkinsManagerQuery = { - ...params, - pageNum: pagination.pageNum, - pageSize: pagination.pageSize, - }; - const data = await getJenkinsManagerPage(queryParams); - setList(data.content || []); - setPagination({ - ...pagination, - totalElements: data.totalElements, - }); + 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: "获取列表失败", + title: "获取 Jenkins 实例列表失败", + duration: 3000, + }); + } + }; + + // 获取视图列表 + const loadViews = async () => { + if (!currentJenkinsId) return; + setLoading(true); + try { + const data = await getJenkinsViews(currentJenkinsId); + setViews(data); + } catch (error) { + toast({ + variant: "destructive", + title: "获取视图列表失败", duration: 3000, }); } finally { @@ -98,261 +70,199 @@ const JenkinsManagerList: React.FC = () => { } }; - const handlePageChange = (page: number) => { - setPagination({ - ...pagination, - pageNum: page, - }); + // 切换 Jenkins 实例 + const handleJenkinsChange = (id: string) => { + setCurrentJenkinsId(id); + const jenkins = jenkinsList.find(j => String(j.id) === id); + setCurrentJenkins(jenkins); + setViews([]); }; - useEffect(() => { - loadData(form.getValues()); - }, [pagination.pageNum, pagination.pageSize]); - - const handleDelete = async (id: number) => { + // 同步数据 + const handleSync = async (type: SyncType) => { + if (!currentJenkinsId) return; + setSyncing(prev => ({ ...prev, [type]: true })); try { - await deleteJenkinsManager(id); + await syncJenkinsData(currentJenkinsId, type); + await loadJenkinsList(); + if (type === 'views') { + await loadViews(); + } toast({ - title: "删除成功", + title: "同步成功", duration: 3000, }); - loadData(form.getValues()); } catch (error) { toast({ variant: "destructive", - title: "删除失败", + title: "同步失败", duration: 3000, }); + } finally { + setSyncing(prev => ({ ...prev, [type]: false })); } }; - const handleAdd = () => { - setCurrentRecord(undefined); - setModalVisible(true); + useEffect(() => { + loadJenkinsList(); + }, []); + + useEffect(() => { + if (currentJenkinsId) { + loadViews(); + } + }, [currentJenkinsId]); + + const formatTime = (time: string | null) => { + if (!time) return 'Never'; + return time; }; - const handleEdit = (record: JenkinsManagerResponse) => { - setCurrentRecord(record); - setModalVisible(true); + // 模拟统计数据 + const mockStats = { + views: 5, + jobs: 20, + builds: 100 }; - const handleModalClose = () => { - setModalVisible(false); - setCurrentRecord(undefined); - }; - - const handleSuccess = () => { - setModalVisible(false); - setCurrentRecord(undefined); - loadData(form.getValues()); - }; - - const handleReset = () => { - form.reset({ - name: "", - enabled: undefined, - }); - loadData(); - }; - - const columns: Column[] = [ - { - accessorKey: 'name', - header: '名称', - size: 180, - }, - { - accessorKey: 'description', - header: '描述', - size: 200, - }, - { - accessorKey: 'enabled', - header: '状态', - size: 100, - cell: ({row}) => ( - - {row.original.enabled ? '启用' : '禁用'} - - ), - }, - { - accessorKey: 'sort', - header: '排序', - size: 80, - }, - { - accessorKey: 'createTime', - header: '创建时间', - size: 180, - }, - { - id: 'actions', - header: '操作', - size: 180, - cell: ({row}) => ( -
- - - - - - - - 确定要删除吗? - - 删除后将无法恢复,请谨慎操作 - - - - 取消 - handleDelete(row.original.id)} - > - 确定 - - - - -
- ), - }, - ]; - return ( -
-

Jenkins管理

-
- -
+
+

Jenkins Management

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

{currentJenkins.name}

+

{currentJenkins.url}

+
+ +
+
+
+ Views + {mockStats.views} +
+
+ Last sync: {formatTime(currentJenkins.lastSyncTime)} + +
+
+ +
+
+ Jobs + {mockStats.jobs} +
+
+ Last sync: {formatTime(currentJenkins.lastSyncTime)} + +
+
+ +
+
+ Builds + {mockStats.builds} +
+
+ Last sync: {formatTime(currentJenkins.lastSyncTime)} + +
+
+
+
+
+
+ )} - - 列表 - - -
- - - - {columns.map((column) => ( - - {column.header} - - ))} - - - - {loading ? ( - - -
- - 加载中... -
-
-
- ) : list.length === 0 ? ( - - - 暂无数据 - - - ) : ( - list.map((item) => ( - - {columns.map((column) => ( - - {column.cell - ? column.cell({row: {original: item}}) - : item[column.accessorKey!]} - + +

Views

+
+ {loading ? ( +
+ +
+ ) : views.map((view, index) => ( +
+ {index > 0 && } +
+
+

{view.name}

+ +
+ {view.jobs.length > 0 && ( +
+ {view.jobs.map(job => ( +
+ {job.name} + {job.lastBuild && ( +
+ + #{job.lastBuild.number} - {job.lastBuild.result} + + {job.lastBuild.timestamp} +
+ )} +
))} - - )) - )} - -
-
- -
+
+ )} +
+ + ))} - - {modalVisible && ( - - )}
); }; diff --git a/frontend/src/pages/Deploy/JenkinsManager/List/service.ts b/frontend/src/pages/Deploy/JenkinsManager/List/service.ts index 3a3f4bfb..860deb41 100644 --- a/frontend/src/pages/Deploy/JenkinsManager/List/service.ts +++ b/frontend/src/pages/Deploy/JenkinsManager/List/service.ts @@ -1,33 +1,66 @@ import request from '@/utils/request'; -import type { Page } from '@/types/base'; -import type { JenkinsManagerResponse, JenkinsManagerQuery, JenkinsManagerRequest } from './types'; +import type { JenkinsInstance, JenkinsView, SyncType } from './types'; +import { getExternalSystems } from '@/pages/Deploy/External/service'; +import { SystemType } from '@/pages/Deploy/External/types'; const BASE_URL = '/api/v1/jenkins-manager'; -// 创建 -export const createJenkinsManager = (data: JenkinsManagerRequest) => - request.post(BASE_URL, data); +// 获取 Jenkins 实例列表 +export const getJenkinsInstances = () => + getExternalSystems({ + type: SystemType.JENKINS, + enabled: true + }).then(response => response.content); -// 更新 -export const updateJenkinsManager = (id: number, data: JenkinsManagerRequest) => - request.put(`${BASE_URL}/${id}`, data); +// 获取 Jenkins 视图列表 +export const getJenkinsViews = (jenkinsId: string) => + // 模拟数据 + Promise.resolve([ + { + id: '1', + name: 'All', + url: 'https://jenkins.prod.example.com/view/all', + jobs: [] + }, + { + id: '2', + name: 'Frontend', + url: 'https://jenkins.prod.example.com/view/frontend', + jobs: [ + { + id: '1', + name: 'Build Frontend', + url: 'https://jenkins.prod.example.com/job/build-frontend', + lastBuild: { + id: '42', + number: 42, + result: 'SUCCESS', + timestamp: '2024/12/28 20:28:43', + url: 'https://jenkins.prod.example.com/job/build-frontend/42' + } + }, + { + id: '2', + name: 'Test Backend', + url: 'https://jenkins.prod.example.com/job/test-backend', + lastBuild: { + id: '41', + number: 41, + result: 'FAILURE', + timestamp: '2024/12/28 19:28:43', + url: 'https://jenkins.prod.example.com/job/test-backend/41' + } + } + ] + }, + { + id: '3', + name: 'Backend', + url: 'https://jenkins.prod.example.com/view/backend', + jobs: [] + } + ]); -// 删除 -export const deleteJenkinsManager = (id: number) => - request.delete(`${BASE_URL}/${id}`); - -// 获取详情 -export const getJenkinsManager = (id: number) => - request.get(`${BASE_URL}/${id}`); - -// 分页查询列表 -export const getJenkinsManagerPage = (params?: JenkinsManagerQuery) => - request.get>(`${BASE_URL}/page`, { params }); - -// 获取所有列表 -export const getJenkinsManagerList = () => - request.get(BASE_URL); - -// 条件查询列表 -export const getJenkinsManagerListByCondition = (params?: JenkinsManagerQuery) => - request.get(`${BASE_URL}/list`, { params }); +// 同步 Jenkins 数据 +export const syncJenkinsData = (jenkinsId: string, type: SyncType) => + request.post(`${BASE_URL}/${jenkinsId}/sync/${type}`); diff --git a/frontend/src/pages/Deploy/JenkinsManager/List/types.ts b/frontend/src/pages/Deploy/JenkinsManager/List/types.ts index 71349259..da3c4e2a 100644 --- a/frontend/src/pages/Deploy/JenkinsManager/List/types.ts +++ b/frontend/src/pages/Deploy/JenkinsManager/List/types.ts @@ -1,23 +1,33 @@ -import type { BaseResponse, BaseQuery } from '@/types/base'; +import type { BaseResponse } from '@/types/base'; +import type { ExternalSystemResponse } from '@/pages/Deploy/External/types'; -export interface JenkinsManagerResponse extends BaseResponse { - name: string; - description?: string; - enabled: boolean; - sort: number; - // TODO: 添加其他字段 +// 使用外部系统响应作为 Jenkins 实例 +export type JenkinsInstance = ExternalSystemResponse; + +// Jenkins 视图类型 +export interface JenkinsView { + id: string; + name: string; + url: string; + jobs: JenkinsJob[]; } -export interface JenkinsManagerQuery extends BaseQuery { - name?: string; - enabled?: boolean; - // TODO: 添加其他查询字段 +// Jenkins Job 类型 +export interface JenkinsJob { + id: string; + name: string; + url: string; + lastBuild?: JenkinsBuild; } -export interface JenkinsManagerRequest { - name: string; - description?: string; - enabled: boolean; - sort: number; - // TODO: 添加其他请求字段 +// Jenkins 构建类型 +export interface JenkinsBuild { + id: string; + number: number; + result: 'SUCCESS' | 'FAILURE' | 'RUNNING' | 'ABORTED'; + timestamp: string; + url: string; } + +// 同步类型 +export type SyncType = 'views' | 'jobs' | 'builds';