diff --git a/frontend/package.json b/frontend/package.json
index 42fd007a..abdeeed9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -21,6 +21,7 @@
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 225bc91f..e9840e51 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -41,6 +41,9 @@ importers:
'@radix-ui/react-checkbox':
specifier: ^1.3.3
version: 1.3.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-collapsible':
+ specifier: ^1.1.12
+ version: 1.1.12(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dialog':
specifier: ^1.1.15
version: 1.1.15(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
diff --git a/frontend/src/components/ui/collapsible.tsx b/frontend/src/components/ui/collapsible.tsx
new file mode 100644
index 00000000..c69ecb0d
--- /dev/null
+++ b/frontend/src/components/ui/collapsible.tsx
@@ -0,0 +1,11 @@
+import * as React from "react"
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
+
diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx
index eafd2d8a..a25bb8fc 100644
--- a/frontend/src/pages/Dashboard/index.tsx
+++ b/frontend/src/pages/Dashboard/index.tsx
@@ -1,67 +1,21 @@
-import React, {useState, useEffect} from 'react';
-import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
-import {Badge} from "@/components/ui/badge";
-import {Progress} from "@/components/ui/progress";
-import {Button} from "@/components/ui/button";
-import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
-import {Input} from "@/components/ui/input";
+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 {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import {cn} from "@/lib/utils";
-import {
- Clock,
- Search,
- Edit,
Package,
- ChevronRight,
- CheckCircle,
- AlertTriangle,
- XCircle,
+ Shield,
Loader2,
- ServerCrash,
- Server
+ Rocket,
+ GitBranch,
+ Users,
+ Server,
+ CheckCircle2
} from "lucide-react";
-import {getEnvironmentList} from '@/pages/Deploy/Environment/List/service';
-import {getDevelopmentLanguages} from '@/pages/Deploy/Application/List/service';
-import {getDeploymentConfigPage} from '@/pages/Deploy/Deployment/List/service';
-import type {Environment} from '@/pages/Deploy/Environment/List/types';
-import type {DevelopmentLanguageType} from '@/pages/Deploy/Application/List/types';
-import type {DeploymentConfig} from '@/pages/Deploy/Deployment/List/types';
-import type {Page} from '@/types/base';
-import type { JsonNode } from '@/types/common';
-import DeploymentFormModal from './components/DeploymentFormModal';
-import {message} from "antd";
-
-type EnvironmentStatus = 'success' | 'warning' | 'error';
-
-// 扩展环境类型,添加监控数据
-interface EnhancedEnvironment extends Environment {
- projectCount: number;
- status: EnvironmentStatus;
- lastDeployment: string;
- cpu: number;
- memory: number;
- storage: number;
-}
-
-const EmptyState: React.FC<{ title: string; description: string; icon: React.ReactNode }> = ({
- title,
- description,
- icon
- }) => (
-
-
- {icon}
-
-
{title}
-
{description}
-
-);
+import { useToast } from '@/components/ui/use-toast';
+import { getDeployEnvironments, startDeployment } from './service';
+import type { DeployTeam, ApplicationConfig } from './types';
const LoadingState = () => (
@@ -73,404 +27,291 @@ const LoadingState = () => (
);
const Dashboard: React.FC = () => {
- const [projectType, setProjectType] = useState("ALL");
- const [status, setStatus] = useState("");
- const [environments, setEnvironments] = useState
([]);
+ const { toast } = useToast();
const [loading, setLoading] = useState(true);
- const [languages, setLanguages] = useState([]);
- const [deployConfigs, setDeployConfigs] = useState([]);
- const [searchText, setSearchText] = useState("");
- const [currentEnvId, setCurrentEnvId] = useState();
- const [deployModalOpen, setDeployModalOpen] = useState(false);
- const [selectedConfig, setSelectedConfig] = useState(null);
+ const [teams, setTeams] = useState([]);
+ const [currentTeamId, setCurrentTeamId] = useState(null);
+ const [currentEnvId, setCurrentEnvId] = useState(null);
+ const [deploying, setDeploying] = useState>(new Set());
- // 获取环境和语言数据
+ // 加载部署环境数据
useEffect(() => {
- const fetchData = async () => {
+ const loadData = async () => {
try {
setLoading(true);
- const [envResponse, langResponse] = await Promise.all([
- getEnvironmentList(),
- getDevelopmentLanguages()
- ]);
-
- if (envResponse) {
- const enrichedEnvironments = envResponse.map(env => {
- const randomStatus: EnvironmentStatus = Math.random() > 0.7 ? 'warning' : 'success';
- return {
- ...env,
- projectCount: 0, // 初始化为0,后续更新
- status: randomStatus,
- lastDeployment: '暂无部署',
- cpu: Math.floor(Math.random() * 40) + 40,
- memory: Math.floor(Math.random() * 30) + 50,
- storage: Math.floor(Math.random() * 40) + 30,
- };
- });
- setEnvironments(enrichedEnvironments);
- if (enrichedEnvironments.length > 0) {
- setCurrentEnvId(enrichedEnvironments[0].id);
+ 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);
+ }
}
}
-
- if (langResponse) {
- setLanguages(langResponse);
- }
} catch (error) {
- console.error('Failed to fetch data:', error);
+ console.error('加载数据失败:', error);
+ toast({
+ variant: 'destructive',
+ title: '加载失败',
+ description: '无法加载部署环境数据,请稍后重试',
+ });
} finally {
setLoading(false);
}
};
- fetchData();
- }, []);
+ loadData();
+ }, [toast]);
- // 获取部署配置数据
- useEffect(() => {
- const fetchDeployConfigs = async () => {
- if (!currentEnvId) return;
-
- try {
- const response = await getDeploymentConfigPage({
- pageSize: 100,
- pageNum: 1,
- environmentId: currentEnvId,
- workflowDefinitionId: 0,
- enabled: undefined
- });
-
- if (response) {
- setDeployConfigs(response.content);
- // 更新环境的项目数量
- setEnvironments(prevEnvs =>
- prevEnvs.map(env =>
- env.id === currentEnvId
- ? {...env, projectCount: response.totalElements}
- : env
- )
- );
- }
- } catch (error) {
- console.error('Failed to fetch deployment configs:', error);
- }
- };
-
- fetchDeployConfigs();
- }, [currentEnvId]);
-
- const handleSearch = async () => {
- if (!currentEnvId) return;
-
- try {
- const params: any = {
- pageSize: 100,
- pageNum: 1,
- environmentId: currentEnvId,
- enabled: status === 'active' ? true : status === 'paused' ? false : undefined
- };
-
- // 如果选择了具体语言(不是"所有"),添加到查询参数
- if (projectType !== 'ALL') {
- params.languageType = projectType;
- }
-
- const response = await getDeploymentConfigPage(params);
-
- if (response) {
- setDeployConfigs(response.content);
- }
- } catch (error) {
- console.error('Failed to search deployment configs:', error);
+ // 切换团队时,自动选中第一个环境
+ 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 handleReset = () => {
- setSearchText("");
- setProjectType("ALL");
- setStatus("");
- if (currentEnvId) {
- getDeploymentConfigPage({
- pageSize: 100,
- pageNum: 1,
- environmentId: currentEnvId,
- workflowDefinitionId: 0,
- enabled: undefined
- }).then(response => {
- if (response) {
- setDeployConfigs(response.content);
- }
+ // 处理部署
+ 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 handleTabChange = (envCode: string) => {
- const env = environments.find(e => e.envCode === envCode);
- if (env) {
- setCurrentEnvId(env.id);
- }
- };
-
- const getStatusIcon = (status: string) => {
- switch (status) {
- case 'success':
- return ;
- case 'warning':
- return ;
- case 'error':
- return ;
- default:
- return ;
- }
- };
-
- const getBuildStatusBadge = (enabled: boolean) => {
- const config = enabled
- ? {className: "bg-green-100 text-green-800", text: "活跃"}
- : {className: "bg-red-100 text-red-800", text: "暂停"};
-
- return (
-
- {config.text}
-
- );
- };
-
- const handleDeploy = (config: DeploymentConfig) => {
- if (!config?.formVariablesSchema) {
- message.error('工作流配置有误,请检查工作流定义');
- return;
- }
- setSelectedConfig(config);
- setDeployModalOpen(true);
- };
+ // 获取当前团队和环境
+ const currentTeam = teams.find(t => t.teamId === currentTeamId);
+ const currentEnv = currentTeam?.environments.find(e => e.environmentId === currentEnvId);
+ const currentApps = currentEnv?.applications || [];
if (loading) {
return ;
}
- const hasEnvironments = environments.length > 0;
- const hasProjects = deployConfigs.length > 0;
-
- return (
-
-
部署环境概览
-
- {!hasEnvironments ? (
+ if (teams.length === 0) {
+ return (
+
+
部署管理
-
- }
- title="暂无环境数据"
- description="当前系统中还没有配置任何环境。请先添加部署环境,然后开始使用部署功能。"
- />
+
+
+ 您还没有加入任何团队
- ) : (
- <>
+
+ );
+ }
-
- {environments.map((env) => (
-
-
- {env.envName}
- {getStatusIcon(env.status)}
-
-
- {env.projectCount}
- 个项目
-
-
-
- 最近部署: {env.lastDeployment}
-
-
-
- CPU
- {env.cpu}%
-
-
-
-
-
- 内存
- {env.memory}%
-
-
-
-
-
- 存储
- {env.storage}%
-
-
-
-
-
-
- ))}
+ return (
+
+ {/* 页面标题和团队选择器 */}
+
+
+
部署管理
+
+ 管理和执行应用部署任务
+
+
+
+
+
+
+ 当前团队:
-
-
-
-
-
- {environments.map((env) => (
-
- {env.envName}
-
- ))}
-
-
-
- {environments.map((env) => (
-
-
-
-
setSearchText(e.target.value)}
- />
-
-
-
-
-
-
-
-
-
- {!hasProjects ? (
-
-
- }
- title="暂无项目数据"
- description="当前环境中还没有配置任何项目。请先创建项目,然后开始部署。"
- />
-
-
- ) : (
-
- {deployConfigs.map((config) => (
-
-
-
-
-
{config.application.appName}
-
{config.application.appCode}
-
- {getBuildStatusBadge(config.enabled)}
-
-
-
-
构建类型
-
{config.buildType}
-
-
-
开发语言
-
{config.languageType}
-
-
-
工作流
-
{config.publishedWorkflowDefinition?.name || '未配置'}
-
-
-
最后部署时间
-
{config.lastBuildStartTime ? new Date(config.lastBuildStartTime).toLocaleString() : '暂无部署'}
-
-
-
-
-
-
构建状态
-
- {config.lastBuildStatus === 'COMPLETED' ? '构建成功' :
- config.lastBuildStatus === 'FAILED' ? '构建失败' :
- config.lastBuildStatus === 'RUNNING' ? '构建中' : '未构建'}
-
-
-
-
构建时长
-
- {config.lastBuildStartTime && config.lastBuildEndTime ? (
- `${Math.round((new Date(config.lastBuildEndTime).getTime() - new Date(config.lastBuildStartTime).getTime()) / 1000 / 60)} 分钟`
- ) : '暂无数据'}
-
-
-
-
-
-
-
-
-
-
- ))}
-
- )}
-
+
-
- >
+
+
+
+
+ {/* 当前团队信息 */}
+ {currentTeam && (
+
+ {currentTeam.teamName}
+ {currentTeam.teamRole && (
+ <>
+ ·
+ {currentTeam.teamRole}
+ >
+ )}
+ {currentTeam.description && (
+ <>
+ ·
+ {currentTeam.description}
+ >
+ )}
+
)}
- {selectedConfig && selectedConfig.formVariablesSchema && (
-
{
- setDeployModalOpen(false);
- setSelectedConfig(null);
- }}
- formSchema={selectedConfig.formVariablesSchema}
- deployConfig={selectedConfig}
- />
+
+ {/* 环境切换和应用列表 */}
+ {currentTeam && (
+ currentTeam.environments.length === 0 ? (
+
+
+
+ 暂无部署环境
+
+ 当前团队「{currentTeam.teamName}」还没有配置任何部署环境
+
+
+
+ ) : (
+
+
+
+
+
选择部署环境
+
+
+
+ {currentEnv && currentEnv.requiresApproval && currentEnv.approvers.length > 0 ? (
+ <>
+
+ 审批人: {currentEnv.approvers.map((a) => a.realName).join('、')}
+ >
+ ) : (
+ 占位
+ )}
+
+
+
+
+
+ {/* 应用列表 */}
+ {currentApps.length === 0 ? (
+
+
+
暂无可部署应用
+
+ 环境「{currentEnv?.environmentName}」暂未配置任何应用
+
+
+ ) : (
+
+ {currentApps.map((app) => {
+ const isDeploying = deploying.has(app.teamApplicationId);
+
+ return (
+
+
+
+
+
{app.applicationName}
+
+ {app.applicationCode}
+
+
+
+
+
+
+
+ {app.branch}
+
+
+
+
+ {app.workflowDefinitionName || '未配置'}
+
+
+ {app.deploySystemName && (
+
+
+ {app.deploySystemName}
+
+ )}
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ )
)}
);
diff --git a/frontend/src/pages/Dashboard/service.ts b/frontend/src/pages/Dashboard/service.ts
index 5cd934d3..6f9aeb7b 100644
--- a/frontend/src/pages/Dashboard/service.ts
+++ b/frontend/src/pages/Dashboard/service.ts
@@ -1,9 +1,16 @@
-import { DeployAppBuildDTO } from './types';
-import request from "@/utils/request.ts";
+import request from '@/utils/request';
+import type { DeployEnvironmentsResponse, StartDeploymentResponse } from './types';
+
+const DEPLOY_URL = '/api/v1/deploy';
/**
- * 部署应用
- * @param data 部署参数
+ * 获取用户的部署环境列表
*/
-export const deployApp = (data: DeployAppBuildDTO) =>
- request.post
('/api/v1/deploy-app-config/deploy', data);
\ No newline at end of file
+export const getDeployEnvironments = () =>
+ request.get(`${DEPLOY_URL}/environments`);
+
+/**
+ * 发起部署
+ */
+export const startDeployment = (teamApplicationId: number) =>
+ request.post(`${DEPLOY_URL}/execute`, { teamApplicationId });
diff --git a/frontend/src/pages/Dashboard/types.ts b/frontend/src/pages/Dashboard/types.ts
index bd51acf8..ca977ef8 100644
--- a/frontend/src/pages/Dashboard/types.ts
+++ b/frontend/src/pages/Dashboard/types.ts
@@ -1,38 +1,64 @@
-import { JsonNode } from '@/types/common';
+import type { BaseResponse } from '@/types/base';
-export interface DeployAppBuildDTO {
- /**
- * 构建类型
- */
- buildType: string;
+export interface Approver {
+ userId: number;
+ username: string;
+ realName: string;
+}
- /**
- * 应用语言
- */
- languageType: string;
+export interface ApplicationConfig {
+ teamApplicationId: number;
+ applicationId: number;
+ applicationCode: string;
+ applicationName: string;
+ applicationDesc?: string;
+ branch: string;
+ deploySystemId?: number;
+ deploySystemName?: string;
+ deployJob?: string;
+ workflowDefinitionId?: number;
+ workflowDefinitionName?: string;
+ workflowDefinitionKey?: string;
+}
- /**
- * 表单配置
- */
- formVariables?: JsonNode;
+export interface DeployEnvironment {
+ environmentId: number;
+ environmentCode: string;
+ environmentName: string;
+ environmentDesc?: string;
+ enabled: boolean;
+ sort: number;
+ requiresApproval: boolean;
+ approvers: Approver[];
+ applications: ApplicationConfig[];
+}
- /**
- * 构建配置
- */
- buildVariables: JsonNode;
+export interface DeployTeam {
+ teamId: number;
+ teamCode: string;
+ teamName: string;
+ teamRole: string;
+ description?: string;
+ environments: DeployEnvironment[];
+}
- /**
- * 环境ID
- */
- environmentId: number;
+export interface DeployEnvironmentsResponse {
+ userId: number;
+ username: string;
+ realName: string;
+ teams: DeployTeam[];
+}
- /**
- * 应用ID
- */
- applicationId: number;
+export interface StartDeploymentRequest {
+ teamApplicationId: number;
+ workflowDefinitionKey: string;
+ formData?: Record;
+}
- /**
- * 已发布的流程定义ID
- */
- workflowDefinitionId: number;
-}
\ No newline at end of file
+export interface StartDeploymentResponse {
+ instanceId: string;
+ processDefinitionId: string;
+ businessKey: string;
+ processKey: string;
+ startTime?: string;
+}
diff --git a/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx b/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx
index d3969aa2..8905f14c 100644
--- a/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx
+++ b/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx
@@ -97,7 +97,7 @@ const ApplicationModal: React.FC = ({
}
};
- // 加载外部系统列表
+ // 加载Gitlab列表
const loadExternalSystems = async () => {
try {
const response = await getExternalSystems({
@@ -110,7 +110,7 @@ const ApplicationModal: React.FC = ({
} catch (error) {
toast({
variant: "destructive",
- title: "加载外部系统失败",
+ title: "加载Gitlab失败",
description: error instanceof Error ? error.message : undefined,
duration: 3000,
});
@@ -137,14 +137,14 @@ const ApplicationModal: React.FC = ({
repoProjectId: initialValues.repoProjectId
});
- // 如果有外部系统ID,加载仓库项目
+ // 如果有Gitlab ID,加载仓库项目
if (initialValues.externalSystemId) {
fetchRepositoryProjects(initialValues.externalSystemId);
}
}
}, [initialValues]);
- // 当选择外部系统时,获取对应的仓库项目列表
+ // 当选择Gitlab时,获取对应的仓库项目列表
const handleExternalSystemChange = (externalSystemId: number | undefined) => {
form.setValue('repoProjectId', undefined);
setRepositoryProjects([]);
@@ -162,8 +162,8 @@ const ApplicationModal: React.FC = ({
const handleSubmit = async (values: ApplicationFormValues) => {
console.log('Form submitted with values:', values);
try {
- // 去掉 externalSystemId 字段,不传给后端
- const { externalSystemId, ...submitData } = values;
+ // 保留 externalSystemId 字段,传给后端
+ const submitData = values;
if (isEdit) {
await updateApplication({
@@ -311,7 +311,7 @@ const ApplicationModal: React.FC = ({
name="externalSystemId"
render={({field}) => (
- 外部系统
+ Gitlab