增加代码编辑器表单组件
This commit is contained in:
parent
87b8023e1c
commit
a0b189854d
@ -21,6 +21,7 @@
|
|||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
|
|||||||
@ -41,6 +41,9 @@ importers:
|
|||||||
'@radix-ui/react-checkbox':
|
'@radix-ui/react-checkbox':
|
||||||
specifier: ^1.3.3
|
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)
|
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':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.1.15
|
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)
|
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)
|
||||||
|
|||||||
11
frontend/src/components/ui/collapsible.tsx
Normal file
11
frontend/src/components/ui/collapsible.tsx
Normal file
@ -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 }
|
||||||
|
|
||||||
@ -1,67 +1,21 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
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 { Button } from "@/components/ui/button";
|
||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import {Input} from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Clock,
|
|
||||||
Search,
|
|
||||||
Edit,
|
|
||||||
Package,
|
Package,
|
||||||
ChevronRight,
|
Shield,
|
||||||
CheckCircle,
|
|
||||||
AlertTriangle,
|
|
||||||
XCircle,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
ServerCrash,
|
Rocket,
|
||||||
Server
|
GitBranch,
|
||||||
|
Users,
|
||||||
|
Server,
|
||||||
|
CheckCircle2
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {getEnvironmentList} from '@/pages/Deploy/Environment/List/service';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import {getDevelopmentLanguages} from '@/pages/Deploy/Application/List/service';
|
import { getDeployEnvironments, startDeployment } from './service';
|
||||||
import {getDeploymentConfigPage} from '@/pages/Deploy/Deployment/List/service';
|
import type { DeployTeam, ApplicationConfig } from './types';
|
||||||
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
|
|
||||||
}) => (
|
|
||||||
<div className="flex flex-col items-center justify-center p-8 text-center">
|
|
||||||
<div className="rounded-full bg-muted p-3 mb-4">
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-semibold mb-2">{title}</h3>
|
|
||||||
<p className="text-sm text-muted-foreground max-w-sm">{description}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const LoadingState = () => (
|
const LoadingState = () => (
|
||||||
<div className="flex-1 p-8">
|
<div className="flex-1 p-8">
|
||||||
@ -73,404 +27,291 @@ const LoadingState = () => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const [projectType, setProjectType] = useState("ALL");
|
const { toast } = useToast();
|
||||||
const [status, setStatus] = useState("");
|
|
||||||
const [environments, setEnvironments] = useState<EnhancedEnvironment[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [languages, setLanguages] = useState<DevelopmentLanguageType[]>([]);
|
const [teams, setTeams] = useState<DeployTeam[]>([]);
|
||||||
const [deployConfigs, setDeployConfigs] = useState<DeploymentConfig[]>([]);
|
const [currentTeamId, setCurrentTeamId] = useState<number | null>(null);
|
||||||
const [searchText, setSearchText] = useState("");
|
const [currentEnvId, setCurrentEnvId] = useState<number | null>(null);
|
||||||
const [currentEnvId, setCurrentEnvId] = useState<number>();
|
const [deploying, setDeploying] = useState<Set<number>>(new Set());
|
||||||
const [deployModalOpen, setDeployModalOpen] = useState(false);
|
|
||||||
const [selectedConfig, setSelectedConfig] = useState<DeploymentConfig | null>(null);
|
|
||||||
|
|
||||||
// 获取环境和语言数据
|
// 加载部署环境数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const [envResponse, langResponse] = await Promise.all([
|
const response = await getDeployEnvironments();
|
||||||
getEnvironmentList(),
|
|
||||||
getDevelopmentLanguages()
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (envResponse) {
|
if (response && response.teams) {
|
||||||
const enrichedEnvironments = envResponse.map(env => {
|
setTeams(response.teams);
|
||||||
const randomStatus: EnvironmentStatus = Math.random() > 0.7 ? 'warning' : 'success';
|
|
||||||
return {
|
// 默认选中第一个团队和第一个环境
|
||||||
...env,
|
if (response.teams.length > 0) {
|
||||||
projectCount: 0, // 初始化为0,后续更新
|
setCurrentTeamId(response.teams[0].teamId);
|
||||||
status: randomStatus,
|
|
||||||
lastDeployment: '暂无部署',
|
if (response.teams[0].environments.length > 0) {
|
||||||
cpu: Math.floor(Math.random() * 40) + 40,
|
setCurrentEnvId(response.teams[0].environments[0].environmentId);
|
||||||
memory: Math.floor(Math.random() * 30) + 50,
|
|
||||||
storage: Math.floor(Math.random() * 40) + 30,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setEnvironments(enrichedEnvironments);
|
|
||||||
if (enrichedEnvironments.length > 0) {
|
|
||||||
setCurrentEnvId(enrichedEnvironments[0].id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (langResponse) {
|
|
||||||
setLanguages(langResponse);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch data:', error);
|
console.error('加载数据失败:', error);
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: '加载失败',
|
||||||
|
description: '无法加载部署环境数据,请稍后重试',
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
loadData();
|
||||||
}, []);
|
}, [toast]);
|
||||||
|
|
||||||
// 获取部署配置数据
|
// 切换团队时,自动选中第一个环境
|
||||||
useEffect(() => {
|
const handleTeamChange = (teamId: string) => {
|
||||||
const fetchDeployConfigs = async () => {
|
const newTeamId = Number(teamId);
|
||||||
if (!currentEnvId) return;
|
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 {
|
try {
|
||||||
const response = await getDeploymentConfigPage({
|
setDeploying((prev) => new Set(prev).add(app.teamApplicationId));
|
||||||
pageSize: 100,
|
|
||||||
pageNum: 1,
|
await startDeployment(app.teamApplicationId);
|
||||||
environmentId: currentEnvId,
|
|
||||||
workflowDefinitionId: 0,
|
toast({
|
||||||
enabled: undefined
|
title: requiresApproval ? '部署申请已提交' : '部署任务已创建',
|
||||||
|
description: requiresApproval
|
||||||
|
? '您的部署申请已提交,等待审批人审核'
|
||||||
|
: '部署任务已成功创建并开始执行',
|
||||||
});
|
});
|
||||||
|
} catch (error: any) {
|
||||||
if (response) {
|
toast({
|
||||||
setDeployConfigs(response.content);
|
variant: 'destructive',
|
||||||
// 更新环境的项目数量
|
title: '操作失败',
|
||||||
setEnvironments(prevEnvs =>
|
description: error.response?.data?.message || '部署失败,请稍后重试',
|
||||||
prevEnvs.map(env =>
|
});
|
||||||
env.id === currentEnvId
|
} finally {
|
||||||
? {...env, projectCount: response.totalElements}
|
setDeploying((prev) => {
|
||||||
: env
|
const newSet = new Set(prev);
|
||||||
)
|
newSet.delete(app.teamApplicationId);
|
||||||
);
|
return newSet;
|
||||||
}
|
|
||||||
} 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 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 handleTabChange = (envCode: string) => {
|
// 获取当前团队和环境
|
||||||
const env = environments.find(e => e.envCode === envCode);
|
const currentTeam = teams.find(t => t.teamId === currentTeamId);
|
||||||
if (env) {
|
const currentEnv = currentTeam?.environments.find(e => e.environmentId === currentEnvId);
|
||||||
setCurrentEnvId(env.id);
|
const currentApps = currentEnv?.applications || [];
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusIcon = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'success':
|
|
||||||
return <CheckCircle className="h-5 w-5 text-green-500"/>;
|
|
||||||
case 'warning':
|
|
||||||
return <AlertTriangle className="h-5 w-5 text-yellow-500"/>;
|
|
||||||
case 'error':
|
|
||||||
return <XCircle className="h-5 w-5 text-red-500"/>;
|
|
||||||
default:
|
|
||||||
return <Clock className="h-5 w-5 text-blue-500"/>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBuildStatusBadge = (enabled: boolean) => {
|
|
||||||
const config = enabled
|
|
||||||
? {className: "bg-green-100 text-green-800", text: "活跃"}
|
|
||||||
: {className: "bg-red-100 text-red-800", text: "暂停"};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge className={cn("rounded-full", config.className)}>
|
|
||||||
{config.text}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeploy = (config: DeploymentConfig) => {
|
|
||||||
if (!config?.formVariablesSchema) {
|
|
||||||
message.error('工作流配置有误,请检查工作流定义');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSelectedConfig(config);
|
|
||||||
setDeployModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingState/>;
|
return <LoadingState/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasEnvironments = environments.length > 0;
|
if (teams.length === 0) {
|
||||||
const hasProjects = deployConfigs.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 p-8">
|
<div className="flex-1 p-8">
|
||||||
<h2 className="text-2xl font-semibold mb-6">部署环境概览</h2>
|
<h2 className="text-3xl font-bold tracking-tight mb-6">部署管理</h2>
|
||||||
|
|
||||||
{!hasEnvironments ? (
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||||
<EmptyState
|
<Package className="h-12 w-12 text-muted-foreground mb-4" />
|
||||||
icon={<ServerCrash className="h-8 w-8 text-muted-foreground"/>}
|
<p className="text-sm text-muted-foreground">您还没有加入任何团队</p>
|
||||||
title="暂无环境数据"
|
|
||||||
description="当前系统中还没有配置任何环境。请先添加部署环境,然后开始使用部署功能。"
|
|
||||||
/>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
</div>
|
||||||
<>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
return (
|
||||||
{environments.map((env) => (
|
<div className="flex-1 p-8 space-y-6">
|
||||||
<Card key={env.id} className="hover:shadow-lg transition-shadow duration-300">
|
{/* 页面标题和团队选择器 */}
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-sm font-medium">{env.envName}</CardTitle>
|
<div>
|
||||||
{getStatusIcon(env.status)}
|
<h2 className="text-3xl font-bold tracking-tight">部署管理</h2>
|
||||||
</CardHeader>
|
<p className="text-muted-foreground mt-1">
|
||||||
<CardContent>
|
管理和执行应用部署任务
|
||||||
<div className="text-2xl font-bold">{env.projectCount}</div>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">个项目</p>
|
|
||||||
<div className="mt-4 space-y-2">
|
|
||||||
<div className="flex items-center text-sm">
|
|
||||||
<Clock className="mr-2 h-4 w-4 text-muted-foreground"/>
|
|
||||||
最近部署: {env.lastDeployment}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex justify-between text-xs">
|
|
||||||
<span>CPU</span>
|
|
||||||
<span>{env.cpu}%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={env.cpu} className="h-1"/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex justify-between text-xs">
|
|
||||||
<span>内存</span>
|
|
||||||
<span>{env.memory}%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={env.memory} className="h-1"/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex justify-between text-xs">
|
|
||||||
<span>存储</span>
|
|
||||||
<span>{env.storage}%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={env.storage} className="h-1"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow">
|
<div className="flex items-center gap-3">
|
||||||
<Tabs
|
<div className="flex items-center gap-2">
|
||||||
defaultValue={environments[0]?.envCode}
|
<Users className="h-4 w-4 text-muted-foreground" />
|
||||||
className="w-full"
|
<span className="text-sm text-muted-foreground">当前团队:</span>
|
||||||
onValueChange={handleTabChange}
|
|
||||||
>
|
|
||||||
<div className="border-b px-6">
|
|
||||||
<TabsList className="h-16">
|
|
||||||
{environments.map((env) => (
|
|
||||||
<TabsTrigger
|
|
||||||
key={env.id}
|
|
||||||
value={env.envCode}
|
|
||||||
className="px-6 py-3 data-[state=active]:border-b-2 data-[state=active]:border-blue-600"
|
|
||||||
>
|
|
||||||
{env.envName}
|
|
||||||
</TabsTrigger>
|
|
||||||
))}
|
|
||||||
</TabsList>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Select value={currentTeamId?.toString()} onValueChange={handleTeamChange}>
|
||||||
{environments.map((env) => (
|
<SelectTrigger className="w-[200px]">
|
||||||
<TabsContent key={env.id} value={env.envCode} className="p-6">
|
<SelectValue placeholder="选择团队" />
|
||||||
<div className="mb-6">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
||||||
<Input
|
|
||||||
placeholder="项目名称或代码"
|
|
||||||
value={searchText}
|
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Select value={projectType} onValueChange={setProjectType}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="开发语言"/>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="ALL">所有</SelectItem>
|
{teams.map((team) => (
|
||||||
{languages.map(lang => (
|
<SelectItem key={team.teamId} value={team.teamId.toString()}>
|
||||||
<SelectItem key={lang.code} value={lang.code}>
|
{team.teamName}
|
||||||
{lang.name}
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select value={status} onValueChange={setStatus}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="项目状态"/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="active">活跃</SelectItem>
|
|
||||||
<SelectItem value="paused">暂停</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<Button className="flex-1" onClick={handleSearch}>
|
|
||||||
<Search className="w-4 h-4 mr-2"/>
|
|
||||||
搜索
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="flex-1" onClick={handleReset}>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!hasProjects ? (
|
{/* 当前团队信息 */}
|
||||||
|
{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>
|
<Card>
|
||||||
<CardContent>
|
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||||
<EmptyState
|
<Server className="h-16 w-16 text-muted-foreground mb-4" />
|
||||||
icon={<Server className="h-8 w-8 text-muted-foreground"/>}
|
<h3 className="text-lg font-semibold mb-2">暂无部署环境</h3>
|
||||||
title="暂无项目数据"
|
<p className="text-sm text-muted-foreground">
|
||||||
description="当前环境中还没有配置任何项目。请先创建项目,然后开始部署。"
|
当前团队「{currentTeam.teamName}」还没有配置任何部署环境
|
||||||
/>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<Card>
|
||||||
{deployConfigs.map((config) => (
|
<CardHeader className="border-b">
|
||||||
<Card key={config.id} className="hover:shadow-md transition-shadow duration-300">
|
<div className="flex items-center justify-between">
|
||||||
<CardContent className="p-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex justify-between items-start mb-2">
|
<CardTitle>选择部署环境</CardTitle>
|
||||||
<div>
|
<Select value={currentEnvId?.toString()} onValueChange={(value) => setCurrentEnvId(Number(value))}>
|
||||||
<h3 className="text-lg font-semibold">{config.application.appName}</h3>
|
<SelectTrigger className="w-[200px]">
|
||||||
<p className="text-sm text-gray-500">{config.application.appCode}</p>
|
<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>
|
</div>
|
||||||
{getBuildStatusBadge(config.enabled)}
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-2 text-sm mb-4">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground min-h-[20px]">
|
||||||
<div>
|
{currentEnv && currentEnv.requiresApproval && currentEnv.approvers.length > 0 ? (
|
||||||
<p className="text-gray-500">构建类型</p>
|
<>
|
||||||
<p className="font-medium">{config.buildType}</p>
|
<Users className="h-4 w-4" />
|
||||||
</div>
|
<span>审批人: {currentEnv.approvers.map((a) => a.realName).join('、')}</span>
|
||||||
<div>
|
</>
|
||||||
<p className="text-gray-500">开发语言</p>
|
) : (
|
||||||
<p className="font-medium">{config.languageType}</p>
|
<span className="opacity-0">占位</span>
|
||||||
</div>
|
)}
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">工作流</p>
|
|
||||||
<p className="font-medium">{config.publishedWorkflowDefinition?.name || '未配置'}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">最后部署时间</p>
|
|
||||||
<p className="font-medium">{config.lastBuildStartTime ? new Date(config.lastBuildStartTime).toLocaleString() : '暂无部署'}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t pt-2 mt-2">
|
</CardHeader>
|
||||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
<CardContent className="pt-6">
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">构建状态</p>
|
{/* 应用列表 */}
|
||||||
<p className={cn(
|
{currentApps.length === 0 ? (
|
||||||
"font-medium",
|
<div className="flex flex-col items-center justify-center py-16">
|
||||||
config.lastBuildStatus === 'COMPLETED' ? 'text-green-600' :
|
<Package className="h-16 w-16 text-muted-foreground mb-4" />
|
||||||
config.lastBuildStatus === 'FAILED' ? 'text-red-600' :
|
<h3 className="text-lg font-semibold mb-2">暂无可部署应用</h3>
|
||||||
config.lastBuildStatus === 'RUNNING' ? 'text-blue-600' : 'text-gray-600'
|
<p className="text-sm text-muted-foreground">
|
||||||
)}>
|
环境「{currentEnv?.environmentName}」暂未配置任何应用
|
||||||
{config.lastBuildStatus === 'COMPLETED' ? '构建成功' :
|
|
||||||
config.lastBuildStatus === 'FAILED' ? '构建失败' :
|
|
||||||
config.lastBuildStatus === 'RUNNING' ? '构建中' : '未构建'}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
) : (
|
||||||
<p className="text-gray-500">构建时长</p>
|
<div className="grid grid-cols-6 gap-3">
|
||||||
<p className="font-medium">
|
{currentApps.map((app) => {
|
||||||
{config.lastBuildStartTime && config.lastBuildEndTime ? (
|
const isDeploying = deploying.has(app.teamApplicationId);
|
||||||
`${Math.round((new Date(config.lastBuildEndTime).getTime() - new Date(config.lastBuildStartTime).getTime()) / 1000 / 60)} 分钟`
|
|
||||||
) : '暂无数据'}
|
return (
|
||||||
</p>
|
<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>
|
</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>
|
||||||
<div className="flex justify-end space-x-2 mt-4">
|
|
||||||
<Button variant="outline" size="sm" onClick={() => handleDeploy(config)}>
|
<div className="flex items-center gap-1.5">
|
||||||
<Package className="h-4 w-4 mr-1"/>
|
<Rocket className="h-3 w-3 shrink-0" />
|
||||||
部署
|
<span className="truncate">{app.workflowDefinitionName || '未配置'}</span>
|
||||||
</Button>
|
</div>
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<ChevronRight className="h-4 w-4"/>
|
{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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
)
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
))}
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
|
|
||||||
)}
|
|
||||||
{selectedConfig && selectedConfig.formVariablesSchema && (
|
|
||||||
<DeploymentFormModal
|
|
||||||
open={deployModalOpen}
|
|
||||||
onClose={() => {
|
|
||||||
setDeployModalOpen(false);
|
|
||||||
setSelectedConfig(null);
|
|
||||||
}}
|
|
||||||
formSchema={selectedConfig.formVariablesSchema}
|
|
||||||
deployConfig={selectedConfig}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import { DeployAppBuildDTO } from './types';
|
import request from '@/utils/request';
|
||||||
import request from "@/utils/request.ts";
|
import type { DeployEnvironmentsResponse, StartDeploymentResponse } from './types';
|
||||||
|
|
||||||
|
const DEPLOY_URL = '/api/v1/deploy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部署应用
|
* 获取用户的部署环境列表
|
||||||
* @param data 部署参数
|
|
||||||
*/
|
*/
|
||||||
export const deployApp = (data: DeployAppBuildDTO) =>
|
export const getDeployEnvironments = () =>
|
||||||
request.post<void>('/api/v1/deploy-app-config/deploy', data);
|
request.get<DeployEnvironmentsResponse>(`${DEPLOY_URL}/environments`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起部署
|
||||||
|
*/
|
||||||
|
export const startDeployment = (teamApplicationId: number) =>
|
||||||
|
request.post<StartDeploymentResponse>(`${DEPLOY_URL}/execute`, { teamApplicationId });
|
||||||
|
|||||||
@ -1,38 +1,64 @@
|
|||||||
import { JsonNode } from '@/types/common';
|
import type { BaseResponse } from '@/types/base';
|
||||||
|
|
||||||
export interface DeployAppBuildDTO {
|
export interface Approver {
|
||||||
/**
|
userId: number;
|
||||||
* 构建类型
|
username: string;
|
||||||
*/
|
realName: string;
|
||||||
buildType: string;
|
}
|
||||||
|
|
||||||
/**
|
export interface ApplicationConfig {
|
||||||
* 应用语言
|
teamApplicationId: number;
|
||||||
*/
|
applicationId: number;
|
||||||
languageType: string;
|
applicationCode: string;
|
||||||
|
applicationName: string;
|
||||||
/**
|
applicationDesc?: string;
|
||||||
* 表单配置
|
branch: string;
|
||||||
*/
|
deploySystemId?: number;
|
||||||
formVariables?: JsonNode;
|
deploySystemName?: string;
|
||||||
|
deployJob?: string;
|
||||||
/**
|
workflowDefinitionId?: number;
|
||||||
* 构建配置
|
workflowDefinitionName?: string;
|
||||||
*/
|
workflowDefinitionKey?: string;
|
||||||
buildVariables: JsonNode;
|
}
|
||||||
|
|
||||||
/**
|
export interface DeployEnvironment {
|
||||||
* 环境ID
|
environmentId: number;
|
||||||
*/
|
environmentCode: string;
|
||||||
environmentId: number;
|
environmentName: string;
|
||||||
|
environmentDesc?: string;
|
||||||
/**
|
enabled: boolean;
|
||||||
* 应用ID
|
sort: number;
|
||||||
*/
|
requiresApproval: boolean;
|
||||||
applicationId: number;
|
approvers: Approver[];
|
||||||
|
applications: ApplicationConfig[];
|
||||||
/**
|
}
|
||||||
* 已发布的流程定义ID
|
|
||||||
*/
|
export interface DeployTeam {
|
||||||
workflowDefinitionId: number;
|
teamId: number;
|
||||||
|
teamCode: string;
|
||||||
|
teamName: string;
|
||||||
|
teamRole: string;
|
||||||
|
description?: string;
|
||||||
|
environments: DeployEnvironment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeployEnvironmentsResponse {
|
||||||
|
userId: number;
|
||||||
|
username: string;
|
||||||
|
realName: string;
|
||||||
|
teams: DeployTeam[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartDeploymentRequest {
|
||||||
|
teamApplicationId: number;
|
||||||
|
workflowDefinitionKey: string;
|
||||||
|
formData?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartDeploymentResponse {
|
||||||
|
instanceId: string;
|
||||||
|
processDefinitionId: string;
|
||||||
|
businessKey: string;
|
||||||
|
processKey: string;
|
||||||
|
startTime?: string;
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载外部系统列表
|
// 加载Gitlab列表
|
||||||
const loadExternalSystems = async () => {
|
const loadExternalSystems = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getExternalSystems({
|
const response = await getExternalSystems({
|
||||||
@ -110,7 +110,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "加载外部系统失败",
|
title: "加载Gitlab失败",
|
||||||
description: error instanceof Error ? error.message : undefined,
|
description: error instanceof Error ? error.message : undefined,
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
@ -137,14 +137,14 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
repoProjectId: initialValues.repoProjectId
|
repoProjectId: initialValues.repoProjectId
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果有外部系统ID,加载仓库项目
|
// 如果有Gitlab ID,加载仓库项目
|
||||||
if (initialValues.externalSystemId) {
|
if (initialValues.externalSystemId) {
|
||||||
fetchRepositoryProjects(initialValues.externalSystemId);
|
fetchRepositoryProjects(initialValues.externalSystemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
// 当选择外部系统时,获取对应的仓库项目列表
|
// 当选择Gitlab时,获取对应的仓库项目列表
|
||||||
const handleExternalSystemChange = (externalSystemId: number | undefined) => {
|
const handleExternalSystemChange = (externalSystemId: number | undefined) => {
|
||||||
form.setValue('repoProjectId', undefined);
|
form.setValue('repoProjectId', undefined);
|
||||||
setRepositoryProjects([]);
|
setRepositoryProjects([]);
|
||||||
@ -162,8 +162,8 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
const handleSubmit = async (values: ApplicationFormValues) => {
|
const handleSubmit = async (values: ApplicationFormValues) => {
|
||||||
console.log('Form submitted with values:', values);
|
console.log('Form submitted with values:', values);
|
||||||
try {
|
try {
|
||||||
// 去掉 externalSystemId 字段,不传给后端
|
// 保留 externalSystemId 字段,传给后端
|
||||||
const { externalSystemId, ...submitData } = values;
|
const submitData = values;
|
||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
await updateApplication({
|
await updateApplication({
|
||||||
@ -311,7 +311,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
name="externalSystemId"
|
name="externalSystemId"
|
||||||
render={({field}) => (
|
render={({field}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>外部系统</FormLabel>
|
<FormLabel>Gitlab</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
field.onChange(Number(value));
|
field.onChange(Number(value));
|
||||||
@ -321,7 +321,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择外部系统"/>
|
<SelectValue placeholder="请选择Gitlab"/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -370,7 +370,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
(project) => project.repoProjectId === field.value
|
(project) => project.repoProjectId === field.value
|
||||||
)?.name
|
)?.name
|
||||||
: !form.watch('externalSystemId')
|
: !form.watch('externalSystemId')
|
||||||
? "请先选择外部系统"
|
? "请先选择Gitlab"
|
||||||
: "请选择项目"}
|
: "请选择项目"}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -238,7 +238,7 @@ const ApplicationList: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'externalSystem',
|
id: 'externalSystem',
|
||||||
header: '外部系统',
|
header: 'Gitlab',
|
||||||
size: 150,
|
size: 150,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export const getRepositoryProjects = (repoGroupId: number) =>
|
|||||||
params: { repoGroupId: repoGroupId },
|
params: { repoGroupId: repoGroupId },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取项目列表(根据外部系统)
|
// 获取项目列表(根据Gitlab)
|
||||||
export const getRepositoryProjectsBySystem = (externalSystemId: number) =>
|
export const getRepositoryProjectsBySystem = (externalSystemId: number) =>
|
||||||
request.get<RepositoryProject[]>(`${REPOSITORY_PROJECT_URL}/list`, {
|
request.get<RepositoryProject[]>(`${REPOSITORY_PROJECT_URL}/list`, {
|
||||||
params: { externalSystemId },
|
params: { externalSystemId },
|
||||||
|
|||||||
@ -19,14 +19,9 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import {
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
Table,
|
import { Search, Check, ChevronDown } from 'lucide-react';
|
||||||
TableBody,
|
import { cn } from '@/lib/utils';
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { Plus, Trash2, Loader2, AlertCircle } from 'lucide-react';
|
import { Plus, Trash2, Loader2, AlertCircle } from 'lucide-react';
|
||||||
@ -39,6 +34,8 @@ import type {
|
|||||||
Application,
|
Application,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import type { UserResponse } from '@/pages/System/User/List/types';
|
import type { UserResponse } from '@/pages/System/User/List/types';
|
||||||
|
import type { RepositoryBranchResponse } from '@/pages/Resource/Git/List/types';
|
||||||
|
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
|
||||||
import {
|
import {
|
||||||
getTeamConfig,
|
getTeamConfig,
|
||||||
createTeamConfig,
|
createTeamConfig,
|
||||||
@ -46,7 +43,11 @@ import {
|
|||||||
getTeamApplications,
|
getTeamApplications,
|
||||||
createTeamApplication,
|
createTeamApplication,
|
||||||
deleteTeamApplication,
|
deleteTeamApplication,
|
||||||
|
getJenkinsSystems,
|
||||||
|
getJenkinsJobs,
|
||||||
} from '../service';
|
} from '../service';
|
||||||
|
import { getRepositoryBranchesList } from '@/pages/Resource/Git/List/service';
|
||||||
|
import { getPublishedDefinitions } from '@/pages/Workflow/Definition/List/service';
|
||||||
|
|
||||||
interface TeamConfigDialogProps {
|
interface TeamConfigDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -90,7 +91,33 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
const [configuredEnvs, setConfiguredEnvs] = useState<number[]>([]); // 已配置的环境列表
|
const [configuredEnvs, setConfiguredEnvs] = useState<number[]>([]); // 已配置的环境列表
|
||||||
|
|
||||||
// 每个环境的添加表单状态
|
// 每个环境的添加表单状态
|
||||||
const [addForms, setAddForms] = useState<Record<number, { appId: number | null; branch: string }>>({});
|
const [addForms, setAddForms] = useState<Record<number, {
|
||||||
|
appId: number | null;
|
||||||
|
branch: string;
|
||||||
|
deploySystemId: number | null;
|
||||||
|
deployJob: string;
|
||||||
|
workflowDefinitionId: number | null;
|
||||||
|
}>>({});
|
||||||
|
|
||||||
|
// 分支列表状态 - 以应用ID为key
|
||||||
|
const [branchesMap, setBranchesMap] = useState<Record<number, RepositoryBranchResponse[]>>({});
|
||||||
|
const [loadingBranches, setLoadingBranches] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
|
// 分支选择器的搜索和打开状态 - 以环境ID为key
|
||||||
|
const [branchSearchValues, setBranchSearchValues] = useState<Record<number, string>>({});
|
||||||
|
const [branchPopoverOpen, setBranchPopoverOpen] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
|
// Jenkins 系统和 Job 列表
|
||||||
|
const [jenkinsSystems, setJenkinsSystems] = useState<any[]>([]);
|
||||||
|
const [jenkinsJobsMap, setJenkinsJobsMap] = useState<Record<number, any[]>>({});
|
||||||
|
const [loadingJobs, setLoadingJobs] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
|
// Jenkins Job 选择器的搜索和打开状态 - 以环境ID为key
|
||||||
|
const [jobSearchValues, setJobSearchValues] = useState<Record<number, string>>({});
|
||||||
|
const [jobPopoverOpen, setJobPopoverOpen] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
|
// 工作流定义列表
|
||||||
|
const [workflowDefinitions, setWorkflowDefinitions] = useState<WorkflowDefinition[]>([]);
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -103,7 +130,12 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await Promise.all([loadTeamConfig(), loadTeamApplications()]);
|
await Promise.all([
|
||||||
|
loadTeamConfig(),
|
||||||
|
loadTeamApplications(),
|
||||||
|
loadJenkinsSystems(),
|
||||||
|
loadWorkflowDefinitions()
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据失败:', error);
|
console.error('加载数据失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -111,6 +143,28 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载 Jenkins 系统列表
|
||||||
|
const loadJenkinsSystems = async () => {
|
||||||
|
try {
|
||||||
|
const systems = await getJenkinsSystems();
|
||||||
|
setJenkinsSystems(systems || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载Jenkins系统失败:', error);
|
||||||
|
setJenkinsSystems([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载工作流定义列表(已发布)
|
||||||
|
const loadWorkflowDefinitions = async () => {
|
||||||
|
try {
|
||||||
|
const definitions = await getPublishedDefinitions();
|
||||||
|
setWorkflowDefinitions(definitions || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载工作流定义失败:', error);
|
||||||
|
setWorkflowDefinitions([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadTeamConfig = async () => {
|
const loadTeamConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const config = await getTeamConfig(teamId);
|
const config = await getTeamConfig(teamId);
|
||||||
@ -158,6 +212,26 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 当团队应用加载后,自动加载所需的 Jenkins Jobs (用于编辑时的下拉选择)
|
||||||
|
useEffect(() => {
|
||||||
|
if (teamApplications.length > 0) {
|
||||||
|
// 收集所有需要加载的 deploySystemId
|
||||||
|
const systemIds = new Set<number>();
|
||||||
|
teamApplications.forEach(app => {
|
||||||
|
if (app.deploySystemId) {
|
||||||
|
systemIds.add(app.deploySystemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载所有需要的 Jenkins Jobs
|
||||||
|
systemIds.forEach(systemId => {
|
||||||
|
if (!jenkinsJobsMap[systemId]) {
|
||||||
|
loadJenkinsJobs(systemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [teamApplications]);
|
||||||
|
|
||||||
// 处理环境选择
|
// 处理环境选择
|
||||||
const handleEnvToggle = (envId: number, checked: boolean) => {
|
const handleEnvToggle = (envId: number, checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
@ -367,6 +441,75 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
return teamApplications.filter((app) => app.environmentId === envId);
|
return teamApplications.filter((app) => app.environmentId === envId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载 Jenkins Jobs
|
||||||
|
const loadJenkinsJobs = async (systemId: number) => {
|
||||||
|
if (jenkinsJobsMap[systemId]) {
|
||||||
|
return; // 已加载过
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingJobs(prev => ({ ...prev, [systemId]: true }));
|
||||||
|
try {
|
||||||
|
const jobs = await getJenkinsJobs(systemId);
|
||||||
|
setJenkinsJobsMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[systemId]: jobs || [],
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载Jenkins Jobs失败:', error);
|
||||||
|
setJenkinsJobsMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[systemId]: [],
|
||||||
|
}));
|
||||||
|
} finally {
|
||||||
|
setLoadingJobs(prev => ({ ...prev, [systemId]: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载分支列表 - 只在用户选择应用时调用
|
||||||
|
const loadBranches = async (appId: number, app: Application) => {
|
||||||
|
// 如果应用没有关联仓库项目,跳过
|
||||||
|
if (!app.repoProjectId || !app.externalSystemId) {
|
||||||
|
console.log('应用未关联仓库项目,跳过分支加载');
|
||||||
|
setBranchesMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[appId]: [],
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经加载过,跳过
|
||||||
|
if (branchesMap[appId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingBranches(prev => ({ ...prev, [appId]: true }));
|
||||||
|
try {
|
||||||
|
// 调用 /api/v1/repository-branch/list 接口
|
||||||
|
const branches = await getRepositoryBranchesList({
|
||||||
|
externalSystemId: app.externalSystemId,
|
||||||
|
repoProjectId: app.repoProjectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
setBranchesMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[appId]: branches || [],
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载分支列表失败:', error);
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: '加载分支列表失败',
|
||||||
|
description: '无法获取该应用的分支信息',
|
||||||
|
});
|
||||||
|
setBranchesMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[appId]: [],
|
||||||
|
}));
|
||||||
|
} finally {
|
||||||
|
setLoadingBranches(prev => ({ ...prev, [appId]: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 添加应用到环境
|
// 添加应用到环境
|
||||||
const handleAddApplication = async (envId: number) => {
|
const handleAddApplication = async (envId: number) => {
|
||||||
const form = addForms[envId];
|
const form = addForms[envId];
|
||||||
@ -396,6 +539,9 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
applicationId: form.appId,
|
applicationId: form.appId,
|
||||||
environmentId: envId,
|
environmentId: envId,
|
||||||
branch: form.branch || undefined,
|
branch: form.branch || undefined,
|
||||||
|
deploySystemId: form.deploySystemId || undefined,
|
||||||
|
deployJob: form.deployJob || undefined,
|
||||||
|
workflowDefinitionId: form.workflowDefinitionId || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
await createTeamApplication(data);
|
await createTeamApplication(data);
|
||||||
@ -407,7 +553,7 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
// 清空表单
|
// 清空表单
|
||||||
setAddForms({
|
setAddForms({
|
||||||
...addForms,
|
...addForms,
|
||||||
[envId]: { appId: null, branch: '' },
|
[envId]: { appId: null, branch: '', deploySystemId: null, deployJob: '', workflowDefinitionId: null },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重新加载
|
// 重新加载
|
||||||
@ -449,16 +595,6 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 获取环境图标
|
|
||||||
const getEnvIcon = (envCode?: string) => {
|
|
||||||
const code = envCode?.toUpperCase();
|
|
||||||
if (code?.includes('DEV')) return '🟢';
|
|
||||||
if (code?.includes('TEST') || code?.includes('QA')) return '🟡';
|
|
||||||
if (code?.includes('UAT') || code?.includes('PRE')) return '🟠';
|
|
||||||
if (code?.includes('PROD')) return '🔴';
|
|
||||||
return '⚪';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取可添加的应用列表(排除已添加的)
|
// 获取可添加的应用列表(排除已添加的)
|
||||||
const getAvailableApplications = (envId: number) => {
|
const getAvailableApplications = (envId: number) => {
|
||||||
const addedAppIds = getEnvApplications(envId).map((app) => app.applicationId);
|
const addedAppIds = getEnvApplications(envId).map((app) => app.applicationId);
|
||||||
@ -512,7 +648,7 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-4xl max-h-[90vh] p-0">
|
<DialogContent className="max-w-6xl max-h-[90vh] p-0">
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
<DialogHeader className="px-6 pt-6 pb-0">
|
||||||
<DialogTitle>团队配置:{teamName}</DialogTitle>
|
<DialogTitle>团队配置:{teamName}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@ -570,7 +706,6 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-lg">{getEnvIcon(env.envCode)}</span>
|
|
||||||
<label
|
<label
|
||||||
htmlFor={`env-${env.id}`}
|
htmlFor={`env-${env.id}`}
|
||||||
className="font-medium cursor-pointer"
|
className="font-medium cursor-pointer"
|
||||||
@ -595,7 +730,7 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-950/20 border border-yellow-200 dark:border-yellow-800 rounded-md">
|
<div className="mt-4 p-3 bg-yellow-50 dark:bg-yellow-950/20 border border-yellow-200 dark:border-yellow-800 rounded-md">
|
||||||
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
💡 未选择任何环境,点击"下一步"将清空该团队的环境配置
|
未选择任何环境,点击"下一步"将清空该团队的环境配置
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -628,7 +763,6 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
onClick={() => handleSwitchConfigEnv(envId)}
|
onClick={() => handleSwitchConfigEnv(envId)}
|
||||||
>
|
>
|
||||||
<span className="text-sm">{getEnvIcon(env.envCode)}</span>
|
|
||||||
<span className="flex-1 text-sm font-medium truncate">
|
<span className="flex-1 text-sm font-medium truncate">
|
||||||
{env.envName}
|
{env.envName}
|
||||||
</span>
|
</span>
|
||||||
@ -653,11 +787,10 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-lg">{getEnvIcon(currentEnv?.envCode)}</span>
|
|
||||||
<CardTitle className="text-base">{currentEnv?.envName}</CardTitle>
|
<CardTitle className="text-base">{currentEnv?.envName}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
配置审批规则和应用分支
|
配置审批规则和应用配置
|
||||||
</p>
|
</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
@ -698,10 +831,10 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 应用分支配置 */}
|
{/* 应用配置 */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">应用分支</CardTitle>
|
<CardTitle className="text-base">应用配置</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{applications.length === 0 ? (
|
{applications.length === 0 ? (
|
||||||
@ -710,52 +843,104 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
<p className="text-sm text-muted-foreground">暂无可用应用</p>
|
<p className="text-sm text-muted-foreground">暂无可用应用</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Table>
|
<div className="space-y-4">
|
||||||
<TableHeader>
|
{/* 已配置的应用列表 - 卡片式 */}
|
||||||
<TableRow>
|
{envApps(currentConfigEnvId).map((app) => {
|
||||||
<TableHead>应用名称</TableHead>
|
// 查找Jenkins系统名称
|
||||||
<TableHead>分支</TableHead>
|
const deploySystemName = app.deploySystemName ||
|
||||||
<TableHead className="w-[80px] text-right">操作</TableHead>
|
jenkinsSystems.find(s => s.id === app.deploySystemId)?.name;
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
// Job名称直接从后端获取
|
||||||
<TableBody>
|
const deployJob = app.deployJob;
|
||||||
{envApps(currentConfigEnvId).map((app) => (
|
|
||||||
<TableRow key={app.id}>
|
// 工作流定义名称
|
||||||
<TableCell className="font-medium">
|
const workflowName = app.workflowDefinitionName ||
|
||||||
{app.applicationName}
|
workflowDefinitions.find(w => w.id === app.workflowDefinitionId)?.name;
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
return (
|
||||||
<code className="text-xs bg-muted px-2 py-1 rounded">
|
<div key={app.id} className="p-4 border rounded-lg bg-card hover:bg-accent/5 transition-colors">
|
||||||
{app.branch || '-'}
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="flex-1 space-y-3">
|
||||||
|
{/* 应用名称 */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-medium">{app.applicationName}</span>
|
||||||
|
{app.branch && (
|
||||||
|
<code className="text-xs bg-muted px-2 py-0.5 rounded">
|
||||||
|
{app.branch}
|
||||||
</code>
|
</code>
|
||||||
</TableCell>
|
)}
|
||||||
<TableCell className="text-right">
|
</div>
|
||||||
|
|
||||||
|
{/* 配置信息 - 网格布局 */}
|
||||||
|
<div className="grid grid-cols-2 gap-x-6 gap-y-2 text-sm">
|
||||||
|
{deploySystemName && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-muted-foreground">Jenkins:</span>
|
||||||
|
<span className="truncate" title={deploySystemName}>{deploySystemName}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{deployJob && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-muted-foreground">Job:</span>
|
||||||
|
<span className="truncate" title={deployJob}>{deployJob}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{workflowName && (
|
||||||
|
<div className="flex items-center gap-2 col-span-2">
|
||||||
|
<span className="text-muted-foreground">工作流:</span>
|
||||||
|
<span className="truncate" title={workflowName}>{workflowName}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 删除按钮 */}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleDeleteApplication(app.id)}
|
onClick={() => handleDeleteApplication(app.id)}
|
||||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
className="h-8 w-8 p-0 text-destructive hover:text-destructive shrink-0"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TableCell>
|
</div>
|
||||||
</TableRow>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{/* 添加行 */}
|
{/* 添加新应用表单 */}
|
||||||
{getAvailableApplications(currentConfigEnvId).length > 0 && (
|
{getAvailableApplications(currentConfigEnvId).length > 0 && (
|
||||||
<TableRow className="bg-muted/20">
|
<div className="p-4 border-2 border-dashed rounded-lg bg-muted/10">
|
||||||
<TableCell>
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
<span className="font-medium text-sm">添加应用配置</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 应用选择 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs">应用名称 <span className="text-destructive">*</span></Label>
|
||||||
<Select
|
<Select
|
||||||
value={addForms[currentConfigEnvId]?.appId?.toString() || ''}
|
value={addForms[currentConfigEnvId]?.appId?.toString() || ''}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
|
const appId = Number(value);
|
||||||
|
const app = applications.find(a => a.id === appId);
|
||||||
|
|
||||||
setAddForms({
|
setAddForms({
|
||||||
...addForms,
|
...addForms,
|
||||||
[currentConfigEnvId]: {
|
[currentConfigEnvId]: {
|
||||||
...addForms[currentConfigEnvId],
|
appId: appId,
|
||||||
appId: Number(value),
|
branch: '',
|
||||||
|
deploySystemId: null,
|
||||||
|
deployJob: '',
|
||||||
|
workflowDefinitionId: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 加载该应用的分支列表
|
||||||
|
if (app) {
|
||||||
|
loadBranches(appId, app);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-8">
|
<SelectTrigger className="h-8">
|
||||||
@ -769,46 +954,340 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</TableCell>
|
</div>
|
||||||
<TableCell>
|
|
||||||
<Input
|
{/* 分支选择 */}
|
||||||
placeholder="分支名"
|
<div className="space-y-2">
|
||||||
className="h-8"
|
<Label className="text-xs">分支</Label>
|
||||||
value={addForms[currentConfigEnvId]?.branch || ''}
|
{addForms[currentConfigEnvId]?.appId ? (
|
||||||
|
(() => {
|
||||||
|
const appId = addForms[currentConfigEnvId].appId!;
|
||||||
|
const branches = branchesMap[appId] || [];
|
||||||
|
const isLoading = loadingBranches[appId];
|
||||||
|
const searchValue = branchSearchValues[currentConfigEnvId] || "";
|
||||||
|
const open = branchPopoverOpen[currentConfigEnvId] || false;
|
||||||
|
const filteredBranches = branches.filter(branch =>
|
||||||
|
branch.name.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
setBranchPopoverOpen({
|
||||||
|
...branchPopoverOpen,
|
||||||
|
[currentConfigEnvId]: isOpen,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
disabled={isLoading || branches.length === 0}
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-between h-8",
|
||||||
|
!addForms[currentConfigEnvId]?.branch && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{addForms[currentConfigEnvId]?.branch
|
||||||
|
? (() => {
|
||||||
|
const selectedBranch = branches.find(
|
||||||
|
(b) => b.name === addForms[currentConfigEnvId]?.branch
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<span className="flex items-center gap-2 truncate">
|
||||||
|
{selectedBranch?.name}
|
||||||
|
{selectedBranch?.isDefaultBranch && (
|
||||||
|
<span className="text-xs text-muted-foreground">(默认)</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
: isLoading
|
||||||
|
? '加载中...'
|
||||||
|
: branches.length === 0
|
||||||
|
? '无分支'
|
||||||
|
: '选择分支'}
|
||||||
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[300px] p-0">
|
||||||
|
<div className="flex items-center border-b px-3">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<input
|
||||||
|
placeholder="搜索分支..."
|
||||||
|
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
value={searchValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
setBranchSearchValues({
|
||||||
|
...branchSearchValues,
|
||||||
|
[currentConfigEnvId]: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<ScrollArea className="h-[200px] w-full">
|
||||||
|
<div className="p-1">
|
||||||
|
{filteredBranches.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
未找到分支
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredBranches.map((branch) => (
|
||||||
|
<div
|
||||||
|
key={branch.id}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground",
|
||||||
|
branch.name === addForms[currentConfigEnvId]?.branch && "bg-accent text-accent-foreground"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
setAddForms({
|
setAddForms({
|
||||||
...addForms,
|
...addForms,
|
||||||
[currentConfigEnvId]: {
|
[currentConfigEnvId]: {
|
||||||
...addForms[currentConfigEnvId],
|
...addForms[currentConfigEnvId],
|
||||||
branch: e.target.value,
|
branch: branch.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
setBranchSearchValues({
|
||||||
|
...branchSearchValues,
|
||||||
|
[currentConfigEnvId]: "",
|
||||||
|
});
|
||||||
|
setBranchPopoverOpen({
|
||||||
|
...branchPopoverOpen,
|
||||||
|
[currentConfigEnvId]: false,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{branch.name}
|
||||||
|
{branch.isDefaultBranch && (
|
||||||
|
<span className="ml-2 text-xs text-muted-foreground">(默认)</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{branch.name === addForms[currentConfigEnvId]?.branch && (
|
||||||
|
<Check className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
placeholder="请先选择应用"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jenkins配置 - 网格布局 */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{/* Jenkins 系统 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs">Jenkins系统</Label>
|
||||||
|
<Select
|
||||||
|
value={addForms[currentConfigEnvId]?.deploySystemId?.toString() || ''}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const systemId = Number(value);
|
||||||
|
setAddForms({
|
||||||
|
...addForms,
|
||||||
|
[currentConfigEnvId]: {
|
||||||
|
...addForms[currentConfigEnvId],
|
||||||
|
deploySystemId: systemId,
|
||||||
|
deployJob: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 加载Jobs
|
||||||
|
loadJenkinsJobs(systemId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择Jenkins" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{jenkinsSystems.map((system) => (
|
||||||
|
<SelectItem key={system.id} value={system.id.toString()}>
|
||||||
|
{system.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jenkins Job */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs">Jenkins Job</Label>
|
||||||
|
{addForms[currentConfigEnvId]?.deploySystemId ? (
|
||||||
|
(() => {
|
||||||
|
const systemId = addForms[currentConfigEnvId].deploySystemId!;
|
||||||
|
const jobs = jenkinsJobsMap[systemId] || [];
|
||||||
|
const isLoading = loadingJobs[systemId];
|
||||||
|
const searchValue = jobSearchValues[currentConfigEnvId] || "";
|
||||||
|
const open = jobPopoverOpen[currentConfigEnvId] || false;
|
||||||
|
const filteredJobs = jobs.filter(job =>
|
||||||
|
job.jobName.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
setJobPopoverOpen({
|
||||||
|
...jobPopoverOpen,
|
||||||
|
[currentConfigEnvId]: isOpen,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
disabled={isLoading || jobs.length === 0}
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-between h-8",
|
||||||
|
!addForms[currentConfigEnvId]?.deployJob && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="truncate">
|
||||||
|
{addForms[currentConfigEnvId]?.deployJob ||
|
||||||
|
(isLoading
|
||||||
|
? '加载中...'
|
||||||
|
: jobs.length === 0
|
||||||
|
? '无Job'
|
||||||
|
: '选择Job')}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[300px] p-0">
|
||||||
|
<div className="flex items-center border-b px-3">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<input
|
||||||
|
placeholder="搜索Job..."
|
||||||
|
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setJobSearchValues({
|
||||||
|
...jobSearchValues,
|
||||||
|
[currentConfigEnvId]: e.target.value,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</div>
|
||||||
<TableCell className="text-right">
|
<div className="relative">
|
||||||
|
<ScrollArea className="h-[200px] w-full">
|
||||||
|
<div className="p-1">
|
||||||
|
{filteredJobs.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
未找到Job
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredJobs.map((job) => (
|
||||||
|
<div
|
||||||
|
key={job.id}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground",
|
||||||
|
job.jobName === addForms[currentConfigEnvId]?.deployJob && "bg-accent text-accent-foreground"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setAddForms({
|
||||||
|
...addForms,
|
||||||
|
[currentConfigEnvId]: {
|
||||||
|
...addForms[currentConfigEnvId],
|
||||||
|
deployJob: job.jobName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setJobSearchValues({
|
||||||
|
...jobSearchValues,
|
||||||
|
[currentConfigEnvId]: "",
|
||||||
|
});
|
||||||
|
setJobPopoverOpen({
|
||||||
|
...jobPopoverOpen,
|
||||||
|
[currentConfigEnvId]: false,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{job.jobName}
|
||||||
|
</span>
|
||||||
|
{job.jobName === addForms[currentConfigEnvId]?.deployJob && (
|
||||||
|
<Check className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
placeholder="请先选择Jenkins"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 工作流定义 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs">工作流定义</Label>
|
||||||
|
<Select
|
||||||
|
value={addForms[currentConfigEnvId]?.workflowDefinitionId?.toString() || ''}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setAddForms({
|
||||||
|
...addForms,
|
||||||
|
[currentConfigEnvId]: {
|
||||||
|
...addForms[currentConfigEnvId],
|
||||||
|
workflowDefinitionId: Number(value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择工作流(可选)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{workflowDefinitions.map((workflow) => (
|
||||||
|
<SelectItem key={workflow.id} value={workflow.id.toString()}>
|
||||||
|
{workflow.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 添加按钮 */}
|
||||||
|
<div className="flex justify-end pt-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
|
||||||
className="h-8"
|
|
||||||
onClick={() => handleAddApplication(currentConfigEnvId)}
|
onClick={() => handleAddApplication(currentConfigEnvId)}
|
||||||
disabled={!addForms[currentConfigEnvId]?.appId}
|
disabled={!addForms[currentConfigEnvId]?.appId}
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3 mr-1" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
添加
|
添加应用配置
|
||||||
</Button>
|
</Button>
|
||||||
</TableCell>
|
</div>
|
||||||
</TableRow>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 空状态提示 */}
|
||||||
{envApps(currentConfigEnvId).length === 0 && getAvailableApplications(currentConfigEnvId).length === 0 && (
|
{envApps(currentConfigEnvId).length === 0 && getAvailableApplications(currentConfigEnvId).length === 0 && (
|
||||||
<TableRow>
|
<div className="p-4 border border-dashed rounded-lg text-center">
|
||||||
<TableCell colSpan={3} className="text-center text-sm text-muted-foreground py-4">
|
<p className="text-sm text-muted-foreground">所有应用已添加</p>
|
||||||
所有应用已添加
|
</div>
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</div>
|
||||||
</Table>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -841,7 +1320,6 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-lg">{getEnvIcon(env.envCode)}</span>
|
|
||||||
<CardTitle className="text-base">{env.envName}</CardTitle>
|
<CardTitle className="text-base">{env.envName}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -887,15 +1365,52 @@ const TeamConfigDialog: React.FC<TeamConfigDialogProps> = ({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs text-muted-foreground">应用配置</Label>
|
<Label className="text-xs text-muted-foreground">应用配置</Label>
|
||||||
{apps.length > 0 ? (
|
{apps.length > 0 ? (
|
||||||
<div className="space-y-1">
|
<div className="space-y-2">
|
||||||
{apps.map((app) => (
|
{apps.map((app) => {
|
||||||
<div key={app.id} className="flex items-center justify-between text-xs p-2 bg-muted/30 rounded">
|
// 查找Jenkins系统名称
|
||||||
<span>{app.applicationName}</span>
|
const deploySystemName = app.deploySystemName ||
|
||||||
|
jenkinsSystems.find(s => s.id === app.deploySystemId)?.name;
|
||||||
|
|
||||||
|
// Job名称直接从后端获取
|
||||||
|
const deployJob = app.deployJob;
|
||||||
|
|
||||||
|
// 工作流定义名称
|
||||||
|
const workflowName = app.workflowDefinitionName ||
|
||||||
|
workflowDefinitions.find(w => w.id === app.workflowDefinitionId)?.name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={app.id} className="p-3 bg-muted/30 rounded space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs font-medium">{app.applicationName}</span>
|
||||||
<code className="text-xs bg-background px-2 py-0.5 rounded">
|
<code className="text-xs bg-background px-2 py-0.5 rounded">
|
||||||
{app.branch || '-'}
|
{app.branch || '-'}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
))}
|
{(deploySystemName || deployJob || workflowName) && (
|
||||||
|
<div className="flex flex-col gap-1 text-xs text-muted-foreground pl-2 border-l-2 border-primary/20">
|
||||||
|
{deploySystemName && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="font-medium">Jenkins:</span>
|
||||||
|
<span>{deploySystemName}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{deployJob && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="font-medium">Job:</span>
|
||||||
|
<span>{deployJob}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{workflowName && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="font-medium">工作流:</span>
|
||||||
|
<span>{workflowName}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-muted-foreground">暂无应用配置</p>
|
<p className="text-xs text-muted-foreground">暂无应用配置</p>
|
||||||
|
|||||||
@ -108,3 +108,21 @@ export const updateTeamApplication = (id: number, data: TeamApplicationRequest)
|
|||||||
export const deleteTeamApplication = (id: number) =>
|
export const deleteTeamApplication = (id: number) =>
|
||||||
request.delete(`${TEAM_APPLICATION_URL}/${id}`);
|
request.delete(`${TEAM_APPLICATION_URL}/${id}`);
|
||||||
|
|
||||||
|
// ==================== Jenkins 相关 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Jenkins 系统列表
|
||||||
|
*/
|
||||||
|
export const getJenkinsSystems = () =>
|
||||||
|
request.get<any[]>('/api/v1/external-system/list', {
|
||||||
|
params: { type: 'JENKINS' }
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Jenkins Job 列表
|
||||||
|
*/
|
||||||
|
export const getJenkinsJobs = (externalSystemId: number) =>
|
||||||
|
request.get<any[]>('/api/v1/jenkins-job/list', {
|
||||||
|
params: { externalSystemId }
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@ -75,10 +75,15 @@ export interface TeamApplication extends BaseResponse {
|
|||||||
applicationId: number;
|
applicationId: number;
|
||||||
environmentId: number;
|
environmentId: number;
|
||||||
branch?: string;
|
branch?: string;
|
||||||
|
deploySystemId?: number;
|
||||||
|
deployJob?: string;
|
||||||
|
workflowDefinitionId?: number;
|
||||||
teamName?: string;
|
teamName?: string;
|
||||||
applicationName?: string;
|
applicationName?: string;
|
||||||
applicationCode?: string;
|
applicationCode?: string;
|
||||||
environmentName?: string;
|
environmentName?: string;
|
||||||
|
deploySystemName?: string;
|
||||||
|
workflowDefinitionName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,5 +94,8 @@ export interface TeamApplicationRequest {
|
|||||||
applicationId: number;
|
applicationId: number;
|
||||||
environmentId: number;
|
environmentId: number;
|
||||||
branch?: string;
|
branch?: string;
|
||||||
|
deploySystemId?: number;
|
||||||
|
deployJob?: string;
|
||||||
|
workflowDefinitionId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,22 @@ export const getRepositoryBranches = (params: {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取仓库分支列表(不分页,按最后更新时间倒序排序)
|
||||||
|
*/
|
||||||
|
export const getRepositoryBranchesList = (params: {
|
||||||
|
externalSystemId: number;
|
||||||
|
repoProjectId: number;
|
||||||
|
}) =>
|
||||||
|
request.get<RepositoryBranchResponse[]>(`${BRANCH_URL}/list`, {
|
||||||
|
params: {
|
||||||
|
sortField: 'lastUpdateTime',
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
externalSystemId: params.externalSystemId,
|
||||||
|
repoProjectId: params.repoProjectId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步仓库分支数据
|
* 同步仓库分支数据
|
||||||
* @param externalSystemId 外部系统ID(必填)
|
* @param externalSystemId 外部系统ID(必填)
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
|
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
|
||||||
import { DataSourceType } from '@/domain/dataSource';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jenkins构建节点定义(纯配置)
|
* Jenkins构建节点定义(纯配置)
|
||||||
@ -44,20 +43,20 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
title: "输入",
|
title: "输入",
|
||||||
description: "当前节点所需数据配置",
|
description: "当前节点所需数据配置",
|
||||||
properties: {
|
properties: {
|
||||||
jenkinsServerId: {
|
serverId: {
|
||||||
type: "number",
|
type: "string",
|
||||||
title: "服务器",
|
title: "服务器",
|
||||||
description: "选择要使用的服务器",
|
description: "输入要使用的服务器",
|
||||||
'x-dataSource': DataSourceType.JENKINS_SERVERS
|
default: "${jenkins.serverId}"
|
||||||
},
|
},
|
||||||
project: {
|
jobName: {
|
||||||
type: "string",
|
type: "string",
|
||||||
title: "项目",
|
title: "项目",
|
||||||
description: "要触发构建的项目",
|
description: "要触发构建的项目",
|
||||||
default: ""
|
default: "${jenkins.jobName}"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["jenkinsServerId"]
|
required: ["serverId", "jobName"]
|
||||||
},
|
},
|
||||||
outputs: defineNodeOutputs(
|
outputs: defineNodeOutputs(
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user