deploy-ease-platform/frontend/src/pages/Dashboard/index.tsx
2025-11-02 16:13:40 +08:00

320 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { cn } from "@/lib/utils";
import {
Package,
Shield,
Loader2,
Rocket,
GitBranch,
Users,
Server,
CheckCircle2
} from "lucide-react";
import { useToast } from '@/components/ui/use-toast';
import { getDeployEnvironments, startDeployment } from './service';
import type { DeployTeam, ApplicationConfig } from './types';
const LoadingState = () => (
<div className="flex-1 p-8">
<div className="flex flex-col items-center justify-center min-h-[400px]">
<Loader2 className="h-8 w-8 animate-spin text-primary mb-4"/>
<p className="text-sm text-muted-foreground">...</p>
</div>
</div>
);
const Dashboard: React.FC = () => {
const { toast } = useToast();
const [loading, setLoading] = useState(true);
const [teams, setTeams] = useState<DeployTeam[]>([]);
const [currentTeamId, setCurrentTeamId] = useState<number | null>(null);
const [currentEnvId, setCurrentEnvId] = useState<number | null>(null);
const [deploying, setDeploying] = useState<Set<number>>(new Set());
// 加载部署环境数据
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const response = await getDeployEnvironments();
if (response && response.teams) {
setTeams(response.teams);
// 默认选中第一个团队和第一个环境
if (response.teams.length > 0) {
setCurrentTeamId(response.teams[0].teamId);
if (response.teams[0].environments.length > 0) {
setCurrentEnvId(response.teams[0].environments[0].environmentId);
}
}
}
} catch (error) {
console.error('加载数据失败:', error);
toast({
variant: 'destructive',
title: '加载失败',
description: '无法加载部署环境数据,请稍后重试',
});
} finally {
setLoading(false);
}
};
loadData();
}, [toast]);
// 切换团队时,自动选中第一个环境
const handleTeamChange = (teamId: string) => {
const newTeamId = Number(teamId);
setCurrentTeamId(newTeamId);
const team = teams.find(t => t.teamId === newTeamId);
if (team && team.environments.length > 0) {
setCurrentEnvId(team.environments[0].environmentId);
} else {
setCurrentEnvId(null);
}
};
// 处理部署
const handleDeploy = async (app: ApplicationConfig, requiresApproval: boolean) => {
try {
setDeploying((prev) => new Set(prev).add(app.teamApplicationId));
await startDeployment(app.teamApplicationId);
toast({
title: requiresApproval ? '部署申请已提交' : '部署任务已创建',
description: requiresApproval
? '您的部署申请已提交,等待审批人审核'
: '部署任务已成功创建并开始执行',
});
} catch (error: any) {
toast({
variant: 'destructive',
title: '操作失败',
description: error.response?.data?.message || '部署失败,请稍后重试',
});
} finally {
setDeploying((prev) => {
const newSet = new Set(prev);
newSet.delete(app.teamApplicationId);
return newSet;
});
}
};
// 获取当前团队和环境
const currentTeam = teams.find(t => t.teamId === currentTeamId);
const currentEnv = currentTeam?.environments.find(e => e.environmentId === currentEnvId);
const currentApps = currentEnv?.applications || [];
if (loading) {
return <LoadingState/>;
}
if (teams.length === 0) {
return (
<div className="flex-1 p-8">
<h2 className="text-3xl font-bold tracking-tight mb-6"></h2>
<Card>
<CardContent className="flex flex-col items-center justify-center py-12">
<Package className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-sm text-muted-foreground"></p>
</CardContent>
</Card>
</div>
);
}
return (
<div className="flex-1 p-8 space-y-6">
{/* 页面标题和团队选择器 */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold tracking-tight"></h2>
<p className="text-muted-foreground mt-1">
</p>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">:</span>
</div>
<Select value={currentTeamId?.toString()} onValueChange={handleTeamChange}>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="选择团队" />
</SelectTrigger>
<SelectContent>
{teams.map((team) => (
<SelectItem key={team.teamId} value={team.teamId.toString()}>
{team.teamName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* 当前团队信息 */}
{currentTeam && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span className="font-medium text-foreground">{currentTeam.teamName}</span>
{currentTeam.teamRole && (
<>
<span>·</span>
<span>{currentTeam.teamRole}</span>
</>
)}
{currentTeam.description && (
<>
<span>·</span>
<span>{currentTeam.description}</span>
</>
)}
</div>
)}
{/* 环境切换和应用列表 */}
{currentTeam && (
currentTeam.environments.length === 0 ? (
<Card>
<CardContent className="flex flex-col items-center justify-center py-16">
<Server className="h-16 w-16 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2"></h3>
<p className="text-sm text-muted-foreground">
{currentTeam.teamName}
</p>
</CardContent>
</Card>
) : (
<Card>
<CardHeader className="border-b">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<CardTitle></CardTitle>
<Select value={currentEnvId?.toString()} onValueChange={(value) => setCurrentEnvId(Number(value))}>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="选择环境" />
</SelectTrigger>
<SelectContent>
{currentTeam.environments.map((env) => (
<SelectItem key={env.environmentId} value={env.environmentId.toString()}>
<div className="flex items-center gap-2">
{env.requiresApproval ? (
<Shield className="h-3 w-3 text-amber-600" />
) : (
<CheckCircle2 className="h-3 w-3 text-green-600" />
)}
{env.environmentName}
<span className="text-xs text-muted-foreground">({env.applications.length})</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground min-h-[20px]">
{currentEnv && currentEnv.requiresApproval && currentEnv.approvers.length > 0 ? (
<>
<Users className="h-4 w-4" />
<span>: {currentEnv.approvers.map((a) => a.realName).join('、')}</span>
</>
) : (
<span className="opacity-0"></span>
)}
</div>
</div>
</CardHeader>
<CardContent className="pt-6">
{/* 应用列表 */}
{currentApps.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16">
<Package className="h-16 w-16 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2"></h3>
<p className="text-sm text-muted-foreground">
{currentEnv?.environmentName}
</p>
</div>
) : (
<div className="grid grid-cols-6 gap-3">
{currentApps.map((app) => {
const isDeploying = deploying.has(app.teamApplicationId);
return (
<div
key={app.teamApplicationId}
className="flex flex-col p-3 rounded-lg border hover:bg-accent/50 transition-colors"
>
<div className="flex items-start gap-2 mb-3">
<Package className="h-4 w-4 text-muted-foreground shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-sm mb-1 truncate">{app.applicationName}</h4>
<code className="text-xs text-muted-foreground truncate block">
{app.applicationCode}
</code>
</div>
</div>
<div className="space-y-1.5 mb-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1.5">
<GitBranch className="h-3 w-3 shrink-0" />
<code className="truncate font-mono">{app.branch}</code>
</div>
<div className="flex items-center gap-1.5">
<Rocket className="h-3 w-3 shrink-0" />
<span className="truncate">{app.workflowDefinitionName || '未配置'}</span>
</div>
{app.deploySystemName && (
<div className="flex items-center gap-1.5">
<Server className="h-3 w-3 shrink-0" />
<span className="truncate">{app.deploySystemName}</span>
</div>
)}
</div>
<Button
onClick={() => handleDeploy(app, currentEnv?.requiresApproval || false)}
disabled={!currentEnv?.enabled || isDeploying}
size="sm"
className={cn(
'w-full text-xs h-8',
currentEnv?.requiresApproval && 'bg-amber-600 hover:bg-amber-700'
)}
>
{isDeploying ? (
<>
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
</>
) : (
<>
<Rocket className="h-3 w-3 mr-1" />
{currentEnv?.requiresApproval ? '申请部署' : '立即部署'}
</>
)}
</Button>
</div>
);
})}
</div>
)}
</CardContent>
</Card>
)
)}
</div>
);
};
export default Dashboard;