This commit is contained in:
asp_ly 2024-12-29 19:43:18 +08:00
parent 43670b8142
commit 426297ebbc

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { PageContainer } from '@/components/ui/page-container'; import { PageContainer } from '@/components/ui/page-container';
import { RefreshCw, ChevronRight } from 'lucide-react'; import { RefreshCw, ChevronLeft, ChevronRight } from 'lucide-react';
import { import {
Card, Card,
CardContent, CardContent,
@ -15,7 +15,12 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { Separator } from "@/components/ui/separator"; import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
import type { JenkinsInstance, JenkinsInstanceDTO } from './types'; import type { JenkinsInstance, JenkinsInstanceDTO } from './types';
import { getJenkinsInstances, getJenkinsInstance, syncViews, syncJobs, syncBuilds } from './service'; import { getJenkinsInstances, getJenkinsInstance, syncViews, syncJobs, syncBuilds } from './service';
@ -25,12 +30,13 @@ const JenkinsManagerList: React.FC = () => {
const [currentJenkins, setCurrentJenkins] = useState<JenkinsInstance>(); const [currentJenkins, setCurrentJenkins] = useState<JenkinsInstance>();
const [instanceDetails, setInstanceDetails] = useState<JenkinsInstanceDTO>(); const [instanceDetails, setInstanceDetails] = useState<JenkinsInstanceDTO>();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [currentView, setCurrentView] = useState<string>();
const [syncing, setSyncing] = useState<Record<string, boolean>>({ const [syncing, setSyncing] = useState<Record<string, boolean>>({
views: false, views: false,
jobs: false, jobs: false,
builds: false builds: false
}); });
const [expandedViews, setExpandedViews] = useState<Record<number, boolean>>({}); const tabsRef = useRef<HTMLDivElement>(null);
const { toast } = useToast(); const { toast } = useToast();
// 获取 Jenkins 实例列表 // 获取 Jenkins 实例列表
@ -110,13 +116,12 @@ const JenkinsManagerList: React.FC = () => {
} }
}; };
// 添加切换展开/收起的处理函数 // 在获取实例详情后设置默认视图
const toggleView = (viewId: number) => { useEffect(() => {
setExpandedViews(prev => ({ if (instanceDetails?.jenkinsViewList.length > 0) {
...prev, setCurrentView(String(instanceDetails.jenkinsViewList[0].id));
[viewId]: !prev[viewId] }
})); }, [instanceDetails]);
};
useEffect(() => { useEffect(() => {
loadJenkinsList(); loadJenkinsList();
@ -133,6 +138,19 @@ const JenkinsManagerList: React.FC = () => {
return time; 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'
});
}
};
return ( return (
<PageContainer> <PageContainer>
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
@ -226,67 +244,97 @@ const JenkinsManagerList: React.FC = () => {
</Card> </Card>
)} )}
<Card> {loading ? (
<CardContent className="pt-6"> <Card>
<h3 className="text-lg font-semibold mb-4">Views</h3> <CardContent className="flex items-center justify-center py-8">
<div className="space-y-4"> <RefreshCw className="h-6 w-6 animate-spin" />
{loading ? ( </CardContent>
<div className="flex items-center justify-center py-8"> </Card>
<RefreshCw className="h-6 w-6 animate-spin" /> ) : instanceDetails && (
</div> <Tabs value={currentView} onValueChange={setCurrentView}>
) : instanceDetails?.jenkinsViewList.map((view, index) => ( <div className="relative mb-6">
<div key={view.id}> <div className="absolute left-0 top-0 bottom-0 flex items-center">
{index > 0 && <Separator className="my-4" />} <Button
<div className="space-y-4"> variant="ghost"
<div size="icon"
className="flex items-center justify-between cursor-pointer" className="h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm"
onClick={() => toggleView(view.id)} onClick={() => handleScroll('left')}
>
<ChevronLeft className="h-4 w-4" />
</Button>
</div>
<div className="overflow-x-auto scrollbar-hide mx-10" ref={tabsRef}>
<TabsList className="w-max">
{instanceDetails.jenkinsViewList.map(view => (
<TabsTrigger
key={view.id}
value={String(view.id)}
className="min-w-[120px] max-w-[200px]"
title={`${view.viewName}${view.description ? ` - ${view.description}` : ''}`}
> >
<div> <div className="truncate">
<h4 className="text-base font-medium">{view.viewName}</h4> <span className="font-medium">{view.viewName}</span>
{view.description && ( {view.description && (
<p className="text-sm text-muted-foreground">{view.description}</p> <span className="ml-1 text-xs text-muted-foreground">
({view.description})
</span>
)} )}
</div> </div>
<ChevronRight </TabsTrigger>
className={`h-4 w-4 transition-transform ${expandedViews[view.id] ? 'rotate-90' : ''}`} ))}
/> </TabsList>
</div> </div>
{expandedViews[view.id] && (
<div className="pl-4 space-y-2 border-l"> <div className="absolute right-0 top-0 bottom-0 flex items-center">
{instanceDetails.jenkinsJobList <Button
.filter(job => job.viewId === view.id) variant="ghost"
.map(job => ( size="icon"
<div key={job.id} className="flex items-center justify-between text-sm py-2"> className="h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm"
<div> onClick={() => handleScroll('right')}
<a >
href={job.jobUrl} <ChevronRight className="h-4 w-4" />
target="_blank" </Button>
rel="noopener noreferrer" </div>
className="hover:underline"
>
{job.jobName}
</a>
{job.description && (
<p className="text-xs text-muted-foreground">{job.description}</p>
)}
</div>
<div className="flex items-center gap-2">
<Badge variant={job.lastBuildStatus === 'SUCCESS' ? 'outline' : 'destructive'}>
#{job.lastBuildNumber} - {job.lastBuildStatus}
</Badge>
<span className="text-muted-foreground">{formatTime(job.lastBuildTime)}</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
</div> </div>
</CardContent>
</Card> {instanceDetails.jenkinsViewList.map(view => (
<TabsContent key={view.id} value={String(view.id)}>
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
{instanceDetails.jenkinsJobList
.filter(job => job.viewId === view.id)
.map(job => (
<div key={job.id} className="flex items-center justify-between text-sm py-2">
<div>
<a
href={job.jobUrl}
target="_blank"
rel="noopener noreferrer"
className="hover:underline"
>
{job.jobName}
</a>
{job.description && (
<p className="text-xs text-muted-foreground">{job.description}</p>
)}
</div>
<div className="flex items-center gap-2">
<Badge variant={job.lastBuildStatus === 'SUCCESS' ? 'outline' : 'destructive'}>
#{job.lastBuildNumber} - {job.lastBuildStatus}
</Badge>
<span className="text-muted-foreground">{formatTime(job.lastBuildTime)}</span>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
))}
</Tabs>
)}
</PageContainer> </PageContainer>
); );
}; };