增加团队管理页面
This commit is contained in:
parent
945d827eb8
commit
3ad79299c3
@ -622,7 +622,7 @@ const GitManager: React.FC = () => {
|
||||
className={`h-4 w-4 ${syncing.groups ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-2">
|
||||
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
@ -638,13 +638,13 @@ const GitManager: React.FC = () => {
|
||||
|
||||
</CardHeader>
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-2 space-y-1">
|
||||
<div className="p-2 space-y-1 min-h-[calc(100vh-280px)]">
|
||||
{loading.groups ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="flex items-center justify-center h-full min-h-[400px]">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : filteredGroupTree.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||
<div className="flex flex-col items-center justify-center h-full min-h-[400px] text-muted-foreground">
|
||||
<FolderGit2 className="h-12 w-12 mb-2 opacity-20" />
|
||||
<p className="text-sm">暂无仓库组</p>
|
||||
</div>
|
||||
@ -654,7 +654,7 @@ const GitManager: React.FC = () => {
|
||||
</div>
|
||||
|
||||
</ScrollArea>
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
|
||||
{/* 中栏:项目列表 */}
|
||||
@ -663,9 +663,9 @@ const GitManager: React.FC = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base">
|
||||
项目 {selectedGroup && `(${filteredProjects.length})`}
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
@ -675,8 +675,8 @@ const GitManager: React.FC = () => {
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 ${syncing.projects ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-2">
|
||||
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
@ -760,7 +760,7 @@ const GitManager: React.FC = () => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{project.webUrl && (
|
||||
|
||||
<Tooltip>
|
||||
@ -772,11 +772,11 @@ const GitManager: React.FC = () => {
|
||||
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Button>
|
||||
</a>
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Button>
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>在 GitLab 中查看</p>
|
||||
@ -788,8 +788,8 @@ const GitManager: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 右栏:分支列表 */}
|
||||
<Card className="col-span-4 flex flex-col min-h-0">
|
||||
@ -798,8 +798,8 @@ const GitManager: React.FC = () => {
|
||||
<CardTitle className="text-base">
|
||||
分支 {selectedProject && `(${filteredBranches.length})`}
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={handleSyncBranches}
|
||||
@ -808,8 +808,8 @@ const GitManager: React.FC = () => {
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 ${syncing.branches ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative mt-2">
|
||||
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
@ -864,7 +864,7 @@ const GitManager: React.FC = () => {
|
||||
<Badge variant="secondary" className="text-xs flex items-center gap-1 cursor-help">
|
||||
<Shield className="h-3 w-3" />
|
||||
受保护
|
||||
</Badge>
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>受保护的分支,限制直接推送</p>
|
||||
@ -900,10 +900,10 @@ const GitManager: React.FC = () => {
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href={branch.webUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
@ -3,6 +3,7 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import {
|
||||
Activity,
|
||||
CheckCircle2,
|
||||
@ -119,7 +120,11 @@ const Dashboard: React.FC = () => {
|
||||
<Card className="border-l-4 border-l-orange-500">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium">正在执行</CardTitle>
|
||||
<Loader2 className="h-4 w-4 text-muted-foreground animate-spin" />
|
||||
{data?.summary.running && data.summary.running > 0 ? (
|
||||
<Loader2 className="h-4 w-4 text-orange-500 animate-spin" />
|
||||
) : (
|
||||
<Activity className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{data?.summary.running || 0}</div>
|
||||
@ -277,114 +282,150 @@ const Dashboard: React.FC = () => {
|
||||
|
||||
{/* 状态分组视图 */}
|
||||
<TabsContent value="status">
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-4 md:grid-cols-4 h-[calc(100vh-280px)]">
|
||||
{/* 运行中 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Card className="border-l-4 border-l-orange-500 flex flex-col">
|
||||
<CardHeader className="pb-3 flex-shrink-0">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<div className="h-2 w-2 rounded-full bg-orange-500 animate-pulse" />
|
||||
运行中 ({groupedJobs.running.length})
|
||||
运行中
|
||||
</CardTitle>
|
||||
<div className="text-2xl font-bold">{groupedJobs.running.length}</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{groupedJobs.running.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">无正在运行的任务</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{groupedJobs.running.map((job) => {
|
||||
const runningJob = data?.runningJobs.find(r => r.jobId === job.id);
|
||||
return (
|
||||
<div key={job.id} className="flex items-center gap-3 p-2 border rounded">
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">{job.jobName}</p>
|
||||
<CardContent className="flex-1 overflow-hidden">
|
||||
<ScrollArea className="h-full pr-3">
|
||||
{groupedJobs.running.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground text-sm">
|
||||
<Loader2 className="h-8 w-8 mx-auto mb-2 opacity-20" />
|
||||
<p>无正在运行的任务</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{groupedJobs.running.map((job) => {
|
||||
const runningJob = data?.runningJobs.find(r => r.jobId === job.id);
|
||||
return (
|
||||
<div key={job.id} className="p-3 border rounded-lg bg-card hover:bg-accent transition-colors">
|
||||
<p className="font-medium text-sm mb-2 truncate" title={job.jobName}>
|
||||
{job.jobName}
|
||||
</p>
|
||||
{runningJob && (
|
||||
<Progress value={runningJob.progress} className="h-2 mt-2" />
|
||||
<>
|
||||
<Progress value={runningJob.progress} className="h-2 mb-2" />
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>{runningJob.message || '执行中...'}</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{runningJob.progress}%
|
||||
</Badge>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{runningJob && (
|
||||
<Badge variant="outline">{runningJob.progress}%</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 已暂停 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Card className="border-l-4 border-l-yellow-500 flex flex-col">
|
||||
<CardHeader className="pb-3 flex-shrink-0">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<Pause className="h-4 w-4 text-yellow-600" />
|
||||
已暂停 ({groupedJobs.paused.length})
|
||||
已暂停
|
||||
</CardTitle>
|
||||
<div className="text-2xl font-bold">{groupedJobs.paused.length}</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{groupedJobs.paused.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">无暂停的任务</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{groupedJobs.paused.map((job) => (
|
||||
<div key={job.id} className="flex items-center justify-between p-2 border rounded">
|
||||
<p className="font-medium">{job.jobName}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
上次: {job.lastExecuteTime ? dayjs(job.lastExecuteTime).fromNow() : '-'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="flex-1 overflow-hidden">
|
||||
<ScrollArea className="h-full pr-3">
|
||||
{groupedJobs.paused.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground text-sm">
|
||||
<Pause className="h-8 w-8 mx-auto mb-2 opacity-20" />
|
||||
<p>无暂停的任务</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{groupedJobs.paused.map((job) => (
|
||||
<div key={job.id} className="p-3 border rounded-lg bg-card hover:bg-accent transition-colors">
|
||||
<p className="font-medium text-sm mb-1 truncate" title={job.jobName}>
|
||||
{job.jobName}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
上次: {job.lastExecuteTime ? dayjs(job.lastExecuteTime).fromNow() : '-'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 空闲中 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Card className="border-l-4 border-l-green-500 flex flex-col">
|
||||
<CardHeader className="pb-3 flex-shrink-0">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
||||
空闲中 ({groupedJobs.idle.length})
|
||||
空闲中
|
||||
</CardTitle>
|
||||
<div className="text-2xl font-bold">{groupedJobs.idle.length}</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{groupedJobs.idle.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">无空闲任务</p>
|
||||
) : (
|
||||
<div className="grid gap-2 md:grid-cols-2">
|
||||
{groupedJobs.idle.map((job) => (
|
||||
<div key={job.id} className="p-2 border rounded">
|
||||
<p className="font-medium truncate">{job.jobName}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
下次: {job.nextExecuteTime ? dayjs(job.nextExecuteTime).format('MM-DD HH:mm') : '-'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="flex-1 overflow-hidden">
|
||||
<ScrollArea className="h-full pr-3">
|
||||
{groupedJobs.idle.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground text-sm">
|
||||
<CheckCircle2 className="h-8 w-8 mx-auto mb-2 opacity-20" />
|
||||
<p>无空闲任务</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{groupedJobs.idle.map((job) => (
|
||||
<div key={job.id} className="p-3 border rounded-lg bg-card hover:bg-accent transition-colors">
|
||||
<p className="font-medium text-sm mb-1 truncate" title={job.jobName}>
|
||||
{job.jobName}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
下次: {job.nextExecuteTime ? dayjs(job.nextExecuteTime).format('MM-DD HH:mm') : '-'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 已禁用 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Card className="border-l-4 border-l-gray-400 flex flex-col">
|
||||
<CardHeader className="pb-3 flex-shrink-0">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<XCircle className="h-4 w-4 text-gray-500" />
|
||||
已禁用 ({groupedJobs.disabled.length})
|
||||
已禁用
|
||||
</CardTitle>
|
||||
<div className="text-2xl font-bold">{groupedJobs.disabled.length}</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{groupedJobs.disabled.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">无禁用的任务</p>
|
||||
) : (
|
||||
<div className="grid gap-2 md:grid-cols-2">
|
||||
{groupedJobs.disabled.map((job) => (
|
||||
<div key={job.id} className="p-2 border rounded opacity-60">
|
||||
<p className="font-medium truncate">{job.jobName}</p>
|
||||
<p className="text-sm text-muted-foreground">已停用</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="flex-1 overflow-hidden">
|
||||
<ScrollArea className="h-full pr-3">
|
||||
{groupedJobs.disabled.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground text-sm">
|
||||
<XCircle className="h-8 w-8 mx-auto mb-2 opacity-20" />
|
||||
<p>无禁用的任务</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{groupedJobs.disabled.map((job) => (
|
||||
<div key={job.id} className="p-3 border rounded-lg bg-card hover:bg-accent transition-colors opacity-60">
|
||||
<p className="font-medium text-sm mb-1 truncate" title={job.jobName}>
|
||||
{job.jobName}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">已停用</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user