1
This commit is contained in:
parent
43670b8142
commit
426297ebbc
@ -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,34 +244,65 @@ const JenkinsManagerList: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardContent className="pt-6">
|
|
||||||
<h3 className="text-lg font-semibold mb-4">Views</h3>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex items-center justify-center py-8">
|
<Card>
|
||||||
|
<CardContent className="flex items-center justify-center py-8">
|
||||||
<RefreshCw className="h-6 w-6 animate-spin" />
|
<RefreshCw className="h-6 w-6 animate-spin" />
|
||||||
</div>
|
</CardContent>
|
||||||
) : instanceDetails?.jenkinsViewList.map((view, index) => (
|
</Card>
|
||||||
<div key={view.id}>
|
) : instanceDetails && (
|
||||||
{index > 0 && <Separator className="my-4" />}
|
<Tabs value={currentView} onValueChange={setCurrentView}>
|
||||||
<div className="space-y-4">
|
<div className="relative mb-6">
|
||||||
<div
|
<div className="absolute left-0 top-0 bottom-0 flex items-center">
|
||||||
className="flex items-center justify-between cursor-pointer"
|
<Button
|
||||||
onClick={() => toggleView(view.id)}
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm"
|
||||||
|
onClick={() => handleScroll('left')}
|
||||||
>
|
>
|
||||||
<div>
|
<ChevronLeft className="h-4 w-4" />
|
||||||
<h4 className="text-base font-medium">{view.viewName}</h4>
|
</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 className="truncate">
|
||||||
|
<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">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm"
|
||||||
|
onClick={() => handleScroll('right')}
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{instanceDetails.jenkinsViewList.map(view => (
|
||||||
|
<TabsContent key={view.id} value={String(view.id)}>
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="space-y-4">
|
||||||
{instanceDetails.jenkinsJobList
|
{instanceDetails.jenkinsJobList
|
||||||
.filter(job => job.viewId === view.id)
|
.filter(job => job.viewId === view.id)
|
||||||
.map(job => (
|
.map(job => (
|
||||||
@ -280,13 +329,12 @@ const JenkinsManagerList: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user