三方系统密码加密
This commit is contained in:
parent
d0538de830
commit
8b15f8ae71
@ -277,5 +277,5 @@ http://localhost:3000/form-designer/workflow-example
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Made with ❤️ for Deploy Ease Platform
|
Made with ❤️ for 链宇Deploy Ease平台
|
||||||
|
|
||||||
|
|||||||
@ -6,19 +6,32 @@ import { cn } from "@/lib/utils"
|
|||||||
const ScrollArea = React.forwardRef<
|
const ScrollArea = React.forwardRef<
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => {
|
||||||
|
const viewportRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("relative overflow-hidden", className)}
|
className={cn("relative overflow-hidden", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
ref={viewportRef}
|
||||||
|
className="h-full w-full rounded-[inherit]"
|
||||||
|
onWheel={(e) => {
|
||||||
|
// 允许滚轮在整个viewport区域工作
|
||||||
|
if (viewportRef.current) {
|
||||||
|
viewportRef.current.scrollTop += e.deltaY;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
))
|
);
|
||||||
|
})
|
||||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||||
|
|
||||||
const ScrollBar = React.forwardRef<
|
const ScrollBar = React.forwardRef<
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import type {Application, RepositoryProject} from '../types';
|
import type {Application} from '../types';
|
||||||
import {DevelopmentLanguageTypeEnum} from '../types';
|
import {DevelopmentLanguageTypeEnum} from '../types';
|
||||||
import {createApplication, updateApplication, getRepositoryProjectsBySystem} from '../service';
|
import {createApplication, updateApplication} from '../service';
|
||||||
import type {ExternalSystemResponse} from '@/pages/Resource/External/List/types';
|
|
||||||
import {SystemType} from '@/pages/Resource/External/List/types';
|
|
||||||
import {getExternalSystems} from '@/pages/Resource/External/List/service';
|
|
||||||
import {getEnabledCategories} from '../../Category/service';
|
import {getEnabledCategories} from '../../Category/service';
|
||||||
import type {ApplicationCategoryResponse} from '../../Category/types';
|
import type {ApplicationCategoryResponse} from '../../Category/types';
|
||||||
import {
|
import {
|
||||||
@ -37,16 +34,6 @@ import {useForm} from "react-hook-form";
|
|||||||
import {zodResolver} from "@hookform/resolvers/zod";
|
import {zodResolver} from "@hookform/resolvers/zod";
|
||||||
import {applicationFormSchema, type ApplicationFormValues} from "../schema";
|
import {applicationFormSchema, type ApplicationFormValues} from "../schema";
|
||||||
import {Textarea} from "@/components/ui/textarea";
|
import {Textarea} from "@/components/ui/textarea";
|
||||||
import {ScrollArea} from "@/components/ui/scroll-area";
|
|
||||||
import {Check, ChevronDown, Search} from "lucide-react";
|
|
||||||
import {cn} from "@/lib/utils";
|
|
||||||
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
|
|
||||||
interface ApplicationModalProps {
|
interface ApplicationModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -62,8 +49,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
initialValues,
|
initialValues,
|
||||||
}) => {
|
}) => {
|
||||||
const [categories, setCategories] = useState<ApplicationCategoryResponse[]>([]);
|
const [categories, setCategories] = useState<ApplicationCategoryResponse[]>([]);
|
||||||
const [externalSystems, setExternalSystems] = useState<ExternalSystemResponse[]>([]);
|
|
||||||
const [repositoryProjects, setRepositoryProjects] = useState<RepositoryProject[]>([]);
|
|
||||||
const {toast} = useToast();
|
const {toast} = useToast();
|
||||||
const isEdit = !!initialValues?.id;
|
const isEdit = !!initialValues?.id;
|
||||||
|
|
||||||
@ -77,8 +62,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
language: DevelopmentLanguageTypeEnum.JAVA,
|
language: DevelopmentLanguageTypeEnum.JAVA,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
sort: 0,
|
sort: 0,
|
||||||
externalSystemId: undefined,
|
|
||||||
repoProjectId: undefined,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,29 +80,8 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载Gitlab列表
|
|
||||||
const loadExternalSystems = async () => {
|
|
||||||
try {
|
|
||||||
const response = await getExternalSystems({
|
|
||||||
type: SystemType.GIT,
|
|
||||||
enabled: true,
|
|
||||||
});
|
|
||||||
if (response?.content) {
|
|
||||||
setExternalSystems(response.content);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "加载Gitlab失败",
|
|
||||||
description: error instanceof Error ? error.message : undefined,
|
|
||||||
duration: 3000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCategories();
|
loadCategories();
|
||||||
loadExternalSystems();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -133,36 +95,13 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
language: initialValues.language,
|
language: initialValues.language,
|
||||||
enabled: initialValues.enabled,
|
enabled: initialValues.enabled,
|
||||||
sort: initialValues.sort,
|
sort: initialValues.sort,
|
||||||
externalSystemId: initialValues.externalSystemId,
|
|
||||||
repoProjectId: initialValues.repoProjectId
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果有Gitlab ID,加载仓库项目
|
|
||||||
if (initialValues.externalSystemId) {
|
|
||||||
fetchRepositoryProjects(initialValues.externalSystemId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
// 当选择Gitlab时,获取对应的仓库项目列表
|
|
||||||
const handleExternalSystemChange = (externalSystemId: number | undefined) => {
|
|
||||||
form.setValue('repoProjectId', undefined);
|
|
||||||
setRepositoryProjects([]);
|
|
||||||
if (externalSystemId) {
|
|
||||||
fetchRepositoryProjects(externalSystemId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchRepositoryProjects = async (externalSystemId: number | undefined) => {
|
|
||||||
if (!externalSystemId) return;
|
|
||||||
const response = await getRepositoryProjectsBySystem(externalSystemId);
|
|
||||||
setRepositoryProjects(response || []);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 字段,传给后端
|
|
||||||
const submitData = values;
|
const submitData = values;
|
||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
@ -203,9 +142,9 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
<DialogTitle>{isEdit ? '编辑' : '新建'}应用</DialogTitle>
|
<DialogTitle>{isEdit ? '编辑' : '新建'}应用</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form className="flex flex-col flex-1 overflow-hidden">
|
<form className="space-y-6">
|
||||||
<ScrollArea className="flex-1 px-6">
|
<div className="px-6">
|
||||||
<div className="space-y-4 pr-4">
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@ -298,166 +237,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Git 配置区域(可折叠) */}
|
|
||||||
<Accordion type="single" collapsible className="border rounded-lg">
|
|
||||||
<AccordionItem value="git-config" className="border-0">
|
|
||||||
<AccordionTrigger className="px-4 py-3 hover:no-underline">
|
|
||||||
<span className="text-sm font-medium">Git 仓库配置(选填)</span>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="px-4 pb-4">
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="externalSystemId"
|
|
||||||
render={({field}) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Gitlab</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={(value) => {
|
|
||||||
field.onChange(Number(value));
|
|
||||||
handleExternalSystemChange(Number(value));
|
|
||||||
}}
|
|
||||||
value={field.value?.toString()}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="请选择Gitlab"/>
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{externalSystems.map((system) => (
|
|
||||||
<SelectItem
|
|
||||||
key={system.id}
|
|
||||||
value={system.id.toString()}
|
|
||||||
>
|
|
||||||
{system.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage/>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="repoProjectId"
|
|
||||||
render={({field}) => {
|
|
||||||
const [searchValue, setSearchValue] = React.useState("");
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
const filteredProjects = repositoryProjects.filter(project =>
|
|
||||||
project.name.toLowerCase().includes(searchValue.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>代码仓库</FormLabel>
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
disabled={!form.watch('externalSystemId')}
|
|
||||||
className={cn(
|
|
||||||
"w-full justify-between",
|
|
||||||
!field.value && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{field.value
|
|
||||||
? (() => {
|
|
||||||
const project = repositoryProjects.find(
|
|
||||||
(p) => p.repoProjectId === field.value
|
|
||||||
);
|
|
||||||
if (!project) return '';
|
|
||||||
|
|
||||||
// 如果有组别名称,显示组别/项目名
|
|
||||||
if (project.repoGroupName) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span className="text-muted-foreground">{project.repoGroupName}/</span>
|
|
||||||
{project.name}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return project.name;
|
|
||||||
})()
|
|
||||||
: !form.watch('externalSystemId')
|
|
||||||
? "请先选择Gitlab"
|
|
||||||
: "请选择项目"}
|
|
||||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-[--radix-popover-trigger-width] 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) => setSearchValue(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
|
||||||
<ScrollArea className="h-[200px] w-full">
|
|
||||||
<div className="p-1">
|
|
||||||
{filteredProjects.length === 0 ? (
|
|
||||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
|
||||||
未找到项目
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
filteredProjects.map((project) => (
|
|
||||||
<div
|
|
||||||
key={project.repoProjectId}
|
|
||||||
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",
|
|
||||||
project.repoProjectId === field.value && "bg-accent text-accent-foreground"
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
form.setValue("repoProjectId", project.repoProjectId);
|
|
||||||
setSearchValue("");
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
onWheel={(e) => {
|
|
||||||
const scrollArea = e.currentTarget.closest('[data-radix-scroll-area-viewport]');
|
|
||||||
if (scrollArea) {
|
|
||||||
scrollArea.scrollTop += e.deltaY;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex-1 truncate">
|
|
||||||
{project.repoGroupName ? (
|
|
||||||
<>
|
|
||||||
<span className="text-muted-foreground">{project.repoGroupName}/</span>
|
|
||||||
{project.name}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
project.name
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{project.repoProjectId === field.value && (
|
|
||||||
<Check className="ml-auto h-4 w-4 flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="appDesc"
|
name="appDesc"
|
||||||
@ -514,7 +293,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</div>
|
||||||
<DialogFooter className="px-6 py-4 border-t mt-0">
|
<DialogFooter className="px-6 py-4 border-t mt-0">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
@ -242,8 +242,8 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
<Table minWidth="890px">
|
<Table minWidth="890px">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead width="160px">分类名称</TableHead>
|
|
||||||
<TableHead width="140px">分类代码</TableHead>
|
<TableHead width="140px">分类代码</TableHead>
|
||||||
|
<TableHead width="160px">分类名称</TableHead>
|
||||||
<TableHead width="60px">图标</TableHead>
|
<TableHead width="60px">图标</TableHead>
|
||||||
<TableHead width="120px">应用数量</TableHead>
|
<TableHead width="120px">应用数量</TableHead>
|
||||||
<TableHead width="80px">排序</TableHead>
|
<TableHead width="80px">排序</TableHead>
|
||||||
@ -265,14 +265,14 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
) : data?.content && data.content.length > 0 ? (
|
) : data?.content && data.content.length > 0 ? (
|
||||||
data.content.map((record) => (
|
data.content.map((record) => (
|
||||||
<TableRow key={record.id}>
|
<TableRow key={record.id}>
|
||||||
<TableCell width="160px" className="font-medium">
|
|
||||||
{record.name}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell width="140px">
|
<TableCell width="140px">
|
||||||
<code className="text-xs bg-muted px-2 py-0.5 rounded whitespace-nowrap">
|
<code className="text-xs bg-muted px-2 py-0.5 rounded whitespace-nowrap">
|
||||||
{record.code}
|
{record.code}
|
||||||
</code>
|
</code>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell width="160px" className="font-medium">
|
||||||
|
{record.name}
|
||||||
|
</TableCell>
|
||||||
<TableCell width="60px">
|
<TableCell width="60px">
|
||||||
{record.icon ? (
|
{record.icon ? (
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
@ -350,13 +350,13 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="code"
|
||||||
rules={{ required: '请输入分类名称' }}
|
rules={{ required: '请输入分类代码' }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>分类名称</FormLabel>
|
<FormLabel>分类代码</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="输入分类名称" {...field} />
|
<Input placeholder="输入分类代码" {...field} disabled={!!editRecord} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -365,13 +365,13 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="code"
|
name="name"
|
||||||
rules={{ required: '请输入分类代码' }}
|
rules={{ required: '请输入分类名称' }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>分类代码</FormLabel>
|
<FormLabel>分类名称</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="输入分类代码" {...field} disabled={!!editRecord} />
|
<Input placeholder="输入分类名称" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -228,48 +228,14 @@ const ApplicationList: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'teamCount',
|
id: 'teamCount',
|
||||||
header: '团队数量',
|
header: '团队使用数量',
|
||||||
size: 100,
|
size: 120,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<span className="font-medium">{row.original.teamCount || 0}</span>
|
<span className="font-medium">{row.original.teamCount || 0}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'externalSystem',
|
|
||||||
header: 'Gitlab',
|
|
||||||
size: 150,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{row.original.externalSystem?.name || '-'}</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'repository',
|
|
||||||
header: '代码仓库',
|
|
||||||
size: 280,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const project = row.original.repositoryProject;
|
|
||||||
return project ? (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<GithubOutlined />
|
|
||||||
<a
|
|
||||||
href={project.webUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-500 hover:text-blue-700 flex items-center gap-1"
|
|
||||||
>
|
|
||||||
{project.name}
|
|
||||||
<ExternalLink className="h-3 w-3" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
'-'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'language',
|
accessorKey: 'language',
|
||||||
header: '开发语言',
|
header: '开发语言',
|
||||||
|
|||||||
@ -16,8 +16,6 @@ export const applicationFormSchema = z.object({
|
|||||||
required_error: '请选择应用分类',
|
required_error: '请选择应用分类',
|
||||||
invalid_type_error: '应用分类必须是数字',
|
invalid_type_error: '应用分类必须是数字',
|
||||||
}),
|
}),
|
||||||
externalSystemId: z.number().optional(),
|
|
||||||
repoProjectId: z.number().optional(),
|
|
||||||
language: z.nativeEnum(DevelopmentLanguageTypeEnum, {
|
language: z.nativeEnum(DevelopmentLanguageTypeEnum, {
|
||||||
required_error: "请选择开发语言",
|
required_error: "请选择开发语言",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -28,6 +28,8 @@ import type {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import type { RepositoryBranchResponse } from '@/pages/Resource/Git/List/types';
|
import type { RepositoryBranchResponse } from '@/pages/Resource/Git/List/types';
|
||||||
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
|
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
|
||||||
|
import { getExternalSystemList } from '@/pages/Resource/External/List/service';
|
||||||
|
import { getRepositoryProjectsList, getRepositoryBranchesList } from '@/pages/Resource/Git/List/service';
|
||||||
|
|
||||||
interface TeamApplicationDialogProps {
|
interface TeamApplicationDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -48,6 +50,8 @@ interface TeamApplicationDialogProps {
|
|||||||
deploySystemId: number | null;
|
deploySystemId: number | null;
|
||||||
deployJob: string;
|
deployJob: string;
|
||||||
workflowDefinitionId: number | null;
|
workflowDefinitionId: number | null;
|
||||||
|
codeSourceSystemId: number | null;
|
||||||
|
codeSourceProjectId: number | null;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
onLoadBranches: (appId: number, app: Application) => Promise<RepositoryBranchResponse[]>;
|
onLoadBranches: (appId: number, app: Application) => Promise<RepositoryBranchResponse[]>;
|
||||||
onLoadJenkinsJobs: (systemId: number) => Promise<any[]>;
|
onLoadJenkinsJobs: (systemId: number) => Promise<any[]>;
|
||||||
@ -78,6 +82,8 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
deploySystemId: null as number | null,
|
deploySystemId: null as number | null,
|
||||||
deployJob: '',
|
deployJob: '',
|
||||||
workflowDefinitionId: null as number | null,
|
workflowDefinitionId: null as number | null,
|
||||||
|
codeSourceSystemId: null as number | null,
|
||||||
|
codeSourceProjectId: null as number | null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
@ -86,10 +92,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
const [jenkinsJobs, setJenkinsJobs] = useState<any[]>([]);
|
const [jenkinsJobs, setJenkinsJobs] = useState<any[]>([]);
|
||||||
const [loadingJobs, setLoadingJobs] = useState(false);
|
const [loadingJobs, setLoadingJobs] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [gitSystems, setGitSystems] = useState<any[]>([]);
|
||||||
|
const [repoProjects, setRepoProjects] = useState<any[]>([]);
|
||||||
|
const [loadingRepoProjects, setLoadingRepoProjects] = useState(false);
|
||||||
|
|
||||||
// 搜索和弹窗状态
|
// 搜索和弹窗状态
|
||||||
const [branchSearchValue, setBranchSearchValue] = useState('');
|
const [branchSearchValue, setBranchSearchValue] = useState('');
|
||||||
const [branchPopoverOpen, setBranchPopoverOpen] = useState(false);
|
const [branchPopoverOpen, setBranchPopoverOpen] = useState(false);
|
||||||
|
const [projectSearchValue, setProjectSearchValue] = useState('');
|
||||||
|
const [projectPopoverOpen, setProjectPopoverOpen] = useState(false);
|
||||||
const [jobSearchValue, setJobSearchValue] = useState('');
|
const [jobSearchValue, setJobSearchValue] = useState('');
|
||||||
const [jobPopoverOpen, setJobPopoverOpen] = useState(false);
|
const [jobPopoverOpen, setJobPopoverOpen] = useState(false);
|
||||||
|
|
||||||
@ -104,13 +115,28 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
deploySystemId: application.deploySystemId || null,
|
deploySystemId: application.deploySystemId || null,
|
||||||
deployJob: application.deployJob || '',
|
deployJob: application.deployJob || '',
|
||||||
workflowDefinitionId: application.workflowDefinitionId || null,
|
workflowDefinitionId: application.workflowDefinitionId || null,
|
||||||
|
codeSourceSystemId: application.codeSourceSystemId || null,
|
||||||
|
codeSourceProjectId: application.codeSourceProjectId || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载分支和Jobs
|
// 加载仓库项目
|
||||||
|
if (application.codeSourceSystemId) {
|
||||||
|
loadRepoProjects(application.codeSourceSystemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载分支(优先使用代码源信息,向后兼容旧数据)
|
||||||
|
if (application.codeSourceSystemId && application.codeSourceProjectId) {
|
||||||
|
// 使用代码源信息加载分支
|
||||||
|
loadBranchesFromCodeSource(application.codeSourceSystemId, application.codeSourceProjectId);
|
||||||
|
} else {
|
||||||
|
// 向后兼容:使用应用信息加载分支
|
||||||
const app = applications.find(a => a.id === application.applicationId);
|
const app = applications.find(a => a.id === application.applicationId);
|
||||||
if (app) {
|
if (app) {
|
||||||
loadBranches(application.applicationId, app);
|
loadBranches(application.applicationId, app);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载Jenkins Jobs
|
||||||
if (application.deploySystemId) {
|
if (application.deploySystemId) {
|
||||||
loadJenkinsJobs(application.deploySystemId);
|
loadJenkinsJobs(application.deploySystemId);
|
||||||
}
|
}
|
||||||
@ -122,6 +148,8 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
deploySystemId: null,
|
deploySystemId: null,
|
||||||
deployJob: '',
|
deployJob: '',
|
||||||
workflowDefinitionId: null,
|
workflowDefinitionId: null,
|
||||||
|
codeSourceSystemId: null,
|
||||||
|
codeSourceProjectId: null,
|
||||||
});
|
});
|
||||||
setBranches([]);
|
setBranches([]);
|
||||||
setJenkinsJobs([]);
|
setJenkinsJobs([]);
|
||||||
@ -157,21 +185,51 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载Git系统列表
|
||||||
|
const loadGitSystems = async () => {
|
||||||
|
try {
|
||||||
|
const systems = await getExternalSystemList({ type: 'GIT' });
|
||||||
|
setGitSystems(systems || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载Git系统失败:', error);
|
||||||
|
setGitSystems([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载仓库项目列表
|
||||||
|
const loadRepoProjects = async (externalSystemId: number) => {
|
||||||
|
setLoadingRepoProjects(true);
|
||||||
|
try {
|
||||||
|
const projects = await getRepositoryProjectsList({ externalSystemId });
|
||||||
|
setRepoProjects(projects || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载仓库项目失败:', error);
|
||||||
|
setRepoProjects([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingRepoProjects(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化加载Git系统列表
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
loadGitSystems();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
// 处理应用选择
|
// 处理应用选择
|
||||||
const handleAppChange = (appId: number) => {
|
const handleAppChange = (appId: number) => {
|
||||||
const app = applications.find(a => a.id === appId);
|
|
||||||
setFormData({
|
setFormData({
|
||||||
appId: appId,
|
appId: appId,
|
||||||
branch: '',
|
branch: '',
|
||||||
deploySystemId: null,
|
deploySystemId: null,
|
||||||
deployJob: '',
|
deployJob: '',
|
||||||
workflowDefinitionId: null,
|
workflowDefinitionId: null,
|
||||||
|
codeSourceSystemId: null,
|
||||||
|
codeSourceProjectId: null,
|
||||||
});
|
});
|
||||||
|
// 清空分支列表(分支现在基于代码源,不基于应用)
|
||||||
// 加载该应用的分支列表
|
setBranches([]);
|
||||||
if (app) {
|
|
||||||
loadBranches(appId, app);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理 Jenkins 系统选择
|
// 处理 Jenkins 系统选择
|
||||||
@ -184,6 +242,45 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
loadJenkinsJobs(systemId);
|
loadJenkinsJobs(systemId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载基于代码源的分支列表
|
||||||
|
const loadBranchesFromCodeSource = async (externalSystemId: number, repoProjectId: number) => {
|
||||||
|
setLoadingBranches(true);
|
||||||
|
try {
|
||||||
|
const branchList = await getRepositoryBranchesList({ externalSystemId, repoProjectId });
|
||||||
|
setBranches(branchList || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载分支失败:', error);
|
||||||
|
setBranches([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingBranches(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理代码源系统选择
|
||||||
|
const handleCodeSourceSystemChange = (systemId: number) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
codeSourceSystemId: systemId,
|
||||||
|
codeSourceProjectId: null,
|
||||||
|
branch: '', // 清空分支
|
||||||
|
});
|
||||||
|
setBranches([]); // 清空分支列表
|
||||||
|
loadRepoProjects(systemId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理仓库项目选择
|
||||||
|
const handleCodeSourceProjectChange = (projectId: number) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
codeSourceProjectId: projectId,
|
||||||
|
branch: '', // 清空分支
|
||||||
|
});
|
||||||
|
// 加载该项目的分支
|
||||||
|
if (formData.codeSourceSystemId) {
|
||||||
|
loadBranchesFromCodeSource(formData.codeSourceSystemId, projectId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
// 表单验证
|
// 表单验证
|
||||||
@ -204,6 +301,8 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
deploySystemId: formData.deploySystemId,
|
deploySystemId: formData.deploySystemId,
|
||||||
deployJob: formData.deployJob,
|
deployJob: formData.deployJob,
|
||||||
workflowDefinitionId: formData.workflowDefinitionId,
|
workflowDefinitionId: formData.workflowDefinitionId,
|
||||||
|
codeSourceSystemId: formData.codeSourceSystemId,
|
||||||
|
codeSourceProjectId: formData.codeSourceProjectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@ -279,10 +378,124 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 代码源选择 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>代码源</Label>
|
||||||
|
<Select
|
||||||
|
value={formData.codeSourceSystemId?.toString() || ''}
|
||||||
|
onValueChange={(value) => handleCodeSourceSystemChange(Number(value))}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择代码源" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{gitSystems.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
暂无可用的Git系统
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
gitSystems.map((system) => (
|
||||||
|
<SelectItem key={system.id} value={system.id.toString()}>
|
||||||
|
{system.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 仓库项目选择 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>仓库项目</Label>
|
||||||
|
{formData.codeSourceSystemId ? (
|
||||||
|
<Popover
|
||||||
|
open={projectPopoverOpen}
|
||||||
|
onOpenChange={setProjectPopoverOpen}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
disabled={loadingRepoProjects || repoProjects.length === 0}
|
||||||
|
className={cn(
|
||||||
|
'w-full justify-between',
|
||||||
|
!formData.codeSourceProjectId && 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formData.codeSourceProjectId
|
||||||
|
? (() => {
|
||||||
|
const selectedProject = repoProjects.find(
|
||||||
|
(p) => p.repoProjectId === formData.codeSourceProjectId
|
||||||
|
);
|
||||||
|
return selectedProject
|
||||||
|
? (selectedProject.repoGroupName
|
||||||
|
? `${selectedProject.repoGroupName} / ${selectedProject.name}`
|
||||||
|
: selectedProject.name)
|
||||||
|
: '选择仓库项目';
|
||||||
|
})()
|
||||||
|
: loadingRepoProjects
|
||||||
|
? '加载中...'
|
||||||
|
: repoProjects.length === 0
|
||||||
|
? '暂无项目'
|
||||||
|
: '选择仓库项目'}
|
||||||
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[var(--radix-popover-trigger-width)] 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"
|
||||||
|
value={projectSearchValue}
|
||||||
|
onChange={(e) => setProjectSearchValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="h-[200px]">
|
||||||
|
<div className="p-1">
|
||||||
|
{repoProjects
|
||||||
|
.filter((project) =>
|
||||||
|
project.name.toLowerCase().includes(projectSearchValue.toLowerCase()) ||
|
||||||
|
(project.repoGroupName?.toLowerCase().includes(projectSearchValue.toLowerCase()))
|
||||||
|
)
|
||||||
|
.map((project) => (
|
||||||
|
<div
|
||||||
|
key={project.repoProjectId}
|
||||||
|
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',
|
||||||
|
project.repoProjectId === formData.codeSourceProjectId &&
|
||||||
|
'bg-accent text-accent-foreground'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
handleCodeSourceProjectChange(project.repoProjectId);
|
||||||
|
setProjectSearchValue('');
|
||||||
|
setProjectPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex-1 truncate">
|
||||||
|
{project.repoGroupName && (
|
||||||
|
<span className="text-muted-foreground">{project.repoGroupName} / </span>
|
||||||
|
)}
|
||||||
|
{project.name}
|
||||||
|
</div>
|
||||||
|
{project.repoProjectId === formData.codeSourceProjectId && (
|
||||||
|
<Check className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<Input placeholder="请先选择代码源" disabled />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 分支选择 */}
|
{/* 分支选择 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>分支</Label>
|
<Label>分支</Label>
|
||||||
{formData.appId ? (
|
{formData.codeSourceProjectId ? (
|
||||||
<Popover
|
<Popover
|
||||||
open={branchPopoverOpen}
|
open={branchPopoverOpen}
|
||||||
onOpenChange={setBranchPopoverOpen}
|
onOpenChange={setBranchPopoverOpen}
|
||||||
@ -371,7 +584,7 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
) : (
|
) : (
|
||||||
<Input placeholder="请先选择应用" disabled />
|
<Input placeholder="请先选择仓库项目" disabled />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -140,6 +140,8 @@ export const TeamApplicationManageDialog: React.FC<
|
|||||||
deploySystemId: number | null;
|
deploySystemId: number | null;
|
||||||
deployJob: string;
|
deployJob: string;
|
||||||
workflowDefinitionId: number | null;
|
workflowDefinitionId: number | null;
|
||||||
|
codeSourceSystemId: number | null;
|
||||||
|
codeSourceProjectId: number | null;
|
||||||
}) => {
|
}) => {
|
||||||
if (!editingEnvironment) return;
|
if (!editingEnvironment) return;
|
||||||
|
|
||||||
@ -151,6 +153,8 @@ export const TeamApplicationManageDialog: React.FC<
|
|||||||
deploySystemId: data.deploySystemId || undefined,
|
deploySystemId: data.deploySystemId || undefined,
|
||||||
deployJob: data.deployJob,
|
deployJob: data.deployJob,
|
||||||
workflowDefinitionId: data.workflowDefinitionId || undefined,
|
workflowDefinitionId: data.workflowDefinitionId || undefined,
|
||||||
|
codeSourceSystemId: data.codeSourceSystemId || undefined,
|
||||||
|
codeSourceProjectId: data.codeSourceProjectId || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (appDialogMode === 'edit' && data.id) {
|
if (appDialogMode === 'edit' && data.id) {
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@ -35,14 +37,14 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
|
|
||||||
interface TeamModalProps {
|
interface TeamModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onCancel: () => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
initialValues?: TeamResponse;
|
initialValues?: TeamResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TeamModal: React.FC<TeamModalProps> = ({
|
const TeamModal: React.FC<TeamModalProps> = ({
|
||||||
open,
|
open,
|
||||||
onCancel,
|
onOpenChange,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
initialValues,
|
initialValues,
|
||||||
}) => {
|
}) => {
|
||||||
@ -120,7 +122,7 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
onSuccess();
|
onSuccess();
|
||||||
onCancel();
|
onOpenChange(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast({
|
toast({
|
||||||
@ -133,15 +135,14 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-[600px] max-h-[85vh] flex flex-col p-0">
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
<DialogHeader className="px-6 pt-6 pb-4">
|
<DialogHeader>
|
||||||
<DialogTitle>{isEdit ? '编辑' : '新建'}团队</DialogTitle>
|
<DialogTitle>{isEdit ? '编辑' : '新建'}团队</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
<DialogBody>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form className="flex flex-col flex-1 overflow-hidden">
|
<form className="space-y-4">
|
||||||
<div className="flex-1 overflow-y-auto px-6">
|
|
||||||
<div className="space-y-4 pb-4">
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@ -277,11 +278,11 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-6" />
|
</form>
|
||||||
</div>
|
</Form>
|
||||||
</div>
|
</DialogBody>
|
||||||
<div className="px-6 py-4 border-t bg-muted/30 flex justify-end gap-2">
|
<DialogFooter>
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -295,9 +296,7 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
|||||||
>
|
>
|
||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</DialogFooter>
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -189,9 +189,11 @@ const TeamList: React.FC = () => {
|
|||||||
setEnvManageDialogOpen(true);
|
setEnvManageDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalClose = () => {
|
const handleModalOpenChange = (open: boolean) => {
|
||||||
setModalVisible(false);
|
setModalVisible(open);
|
||||||
|
if (!open) {
|
||||||
setCurrentTeam(undefined);
|
setCurrentTeam(undefined);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePageChange = (newPage: number) => {
|
const handlePageChange = (newPage: number) => {
|
||||||
@ -461,14 +463,12 @@ const TeamList: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 对话框 */}
|
{/* 对话框 */}
|
||||||
{modalVisible && (
|
|
||||||
<TeamModal
|
<TeamModal
|
||||||
open={modalVisible}
|
open={modalVisible}
|
||||||
onCancel={handleModalClose}
|
onOpenChange={handleModalOpenChange}
|
||||||
onSuccess={handleSuccess}
|
onSuccess={handleSuccess}
|
||||||
initialValues={currentTeam}
|
initialValues={currentTeam}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<DeleteDialog
|
<DeleteDialog
|
||||||
open={deleteDialogOpen}
|
open={deleteDialogOpen}
|
||||||
record={currentTeam}
|
record={currentTeam}
|
||||||
|
|||||||
@ -90,12 +90,16 @@ export interface TeamApplication extends BaseResponse {
|
|||||||
deploySystemId?: number;
|
deploySystemId?: number;
|
||||||
deployJob?: string;
|
deployJob?: string;
|
||||||
workflowDefinitionId?: number;
|
workflowDefinitionId?: number;
|
||||||
|
codeSourceSystemId?: number; // 代码源系统ID
|
||||||
|
codeSourceProjectId?: number; // 代码源项目ID
|
||||||
teamName?: string;
|
teamName?: string;
|
||||||
applicationName?: string;
|
applicationName?: string;
|
||||||
applicationCode?: string;
|
applicationCode?: string;
|
||||||
environmentName?: string;
|
environmentName?: string;
|
||||||
deploySystemName?: string;
|
deploySystemName?: string;
|
||||||
workflowDefinitionName?: string;
|
workflowDefinitionName?: string;
|
||||||
|
codeSourceSystemName?: string; // 代码源系统名称
|
||||||
|
codeSourceProjectName?: string; // 代码源项目名称
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,5 +113,7 @@ export interface TeamApplicationRequest {
|
|||||||
deploySystemId?: number;
|
deploySystemId?: number;
|
||||||
deployJob?: string;
|
deployJob?: string;
|
||||||
workflowDefinitionId?: number;
|
workflowDefinitionId?: number;
|
||||||
|
codeSourceSystemId?: number; // 代码源系统ID
|
||||||
|
codeSourceProjectId?: number; // 代码源项目ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,16 +40,22 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
authType: 'BASIC' as AuthType,
|
authType: 'BASIC' as AuthType,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存原始密码和Token(用于判断是否被修改)
|
// 标记是否有原始密码和Token(用于判断是否需要提交)
|
||||||
const [originalPassword, setOriginalPassword] = useState<string | undefined>(undefined);
|
const [hasOriginalPassword, setHasOriginalPassword] = useState(false);
|
||||||
const [originalToken, setOriginalToken] = useState<string | undefined>(undefined);
|
const [hasOriginalToken, setHasOriginalToken] = useState(false);
|
||||||
|
|
||||||
|
// 掩码常量
|
||||||
|
const MASK = '********';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
if (record) {
|
if (record) {
|
||||||
// 保存原始掩码值
|
// 判断是否有密码/Token(无论返回什么值,只要不为空就认为有)
|
||||||
setOriginalPassword(record.password);
|
const hasPwd = !!record.password && record.password.trim() !== '';
|
||||||
setOriginalToken(record.token);
|
const hasTkn = !!record.token && record.token.trim() !== '';
|
||||||
|
|
||||||
|
setHasOriginalPassword(hasPwd);
|
||||||
|
setHasOriginalToken(hasTkn);
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
name: record.name,
|
name: record.name,
|
||||||
@ -57,15 +63,17 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
url: record.url,
|
url: record.url,
|
||||||
authType: record.authType,
|
authType: record.authType,
|
||||||
username: record.username,
|
username: record.username,
|
||||||
password: record.password || '', // 显示掩码或空
|
// 如果有密码,显示掩码;否则显示空
|
||||||
token: record.token || '', // 显示掩码或空
|
password: hasPwd ? MASK : '',
|
||||||
|
// 如果有Token,显示掩码;否则显示空
|
||||||
|
token: hasTkn ? MASK : '',
|
||||||
sort: record.sort,
|
sort: record.sort,
|
||||||
remark: record.remark,
|
remark: record.remark,
|
||||||
enabled: record.enabled,
|
enabled: record.enabled,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setOriginalPassword(undefined);
|
setHasOriginalPassword(false);
|
||||||
setOriginalToken(undefined);
|
setHasOriginalToken(false);
|
||||||
setFormData({ enabled: true, sort: 1, authType: 'BASIC' as AuthType });
|
setFormData({ enabled: true, sort: 1, authType: 'BASIC' as AuthType });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,9 +112,9 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
toast({ title: '提示', description: '请输入密码', variant: 'destructive' });
|
toast({ title: '提示', description: '请输入密码', variant: 'destructive' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 编辑时,如果清空了原有密码(掩码),也需要输入新密码
|
// 编辑时,如果清空了原有密码(掩码),需要提示
|
||||||
if (record && originalPassword === '********' && !formData.password?.trim()) {
|
if (record && hasOriginalPassword && !formData.password?.trim()) {
|
||||||
toast({ title: '提示', description: '请输入新密码或保持原密码不变', variant: 'destructive' });
|
toast({ title: '提示', description: '密码不能为空,请输入新密码或保持原密码不变', variant: 'destructive' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,9 +125,9 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
toast({ title: '提示', description: '请输入访问令牌', variant: 'destructive' });
|
toast({ title: '提示', description: '请输入访问令牌', variant: 'destructive' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 编辑时,如果清空了原有Token(掩码),也需要输入新Token
|
// 编辑时,如果清空了原有Token(掩码),需要提示
|
||||||
if (record && originalToken === '********' && !formData.token?.trim()) {
|
if (record && hasOriginalToken && !formData.token?.trim()) {
|
||||||
toast({ title: '提示', description: '请输入新令牌或保持原令牌不变', variant: 'destructive' });
|
toast({ title: '提示', description: '令牌不能为空,请输入新令牌或保持原令牌不变', variant: 'destructive' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,22 +137,22 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
...formData as ExternalSystemRequest,
|
...formData as ExternalSystemRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理密码:如果用户没有修改,保持掩码;如果修改了,提交新值
|
// 处理密码:如果值还是掩码,说明没修改,提交掩码;否则提交新值
|
||||||
if (formData.authType === 'BASIC') {
|
if (formData.authType === 'BASIC') {
|
||||||
if (record && formData.password === originalPassword) {
|
if (record && formData.password === MASK) {
|
||||||
// 没有修改,保持掩码
|
// 没有修改,保持掩码
|
||||||
submitData.password = originalPassword;
|
submitData.password = MASK;
|
||||||
} else {
|
} else {
|
||||||
// 修改了或新建,提交新密码
|
// 修改了或新建,提交新密码
|
||||||
submitData.password = formData.password;
|
submitData.password = formData.password;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理Token:如果用户没有修改,保持掩码;如果修改了,提交新值
|
// 处理Token:如果值还是掩码,说明没修改,提交掩码;否则提交新值
|
||||||
if (formData.authType === 'TOKEN') {
|
if (formData.authType === 'TOKEN') {
|
||||||
if (record && formData.token === originalToken) {
|
if (record && formData.token === MASK) {
|
||||||
// 没有修改,保持掩码
|
// 没有修改,保持掩码
|
||||||
submitData.token = originalToken;
|
submitData.token = MASK;
|
||||||
} else {
|
} else {
|
||||||
// 修改了或新建,提交新Token
|
// 修改了或新建,提交新Token
|
||||||
submitData.token = formData.token;
|
submitData.token = formData.token;
|
||||||
@ -243,7 +251,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="password">
|
<Label htmlFor="password">
|
||||||
密码 {!record && '*'}
|
密码 {!record && '*'}
|
||||||
{record && originalPassword === '********' && (
|
{record && hasOriginalPassword && (
|
||||||
<span className="ml-2 text-xs text-muted-foreground">
|
<span className="ml-2 text-xs text-muted-foreground">
|
||||||
(已配置,修改请输入新密码)
|
(已配置,修改请输入新密码)
|
||||||
</span>
|
</span>
|
||||||
@ -254,7 +262,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
type="password"
|
type="password"
|
||||||
value={formData.password || ''}
|
value={formData.password || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||||
placeholder={record ? (originalPassword === '********' ? '保持不变或输入新密码' : '请输入密码') : '请输入密码'}
|
placeholder={record ? (hasOriginalPassword ? '保持不变或输入新密码' : '请输入密码') : '请输入密码'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -264,7 +272,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="token">
|
<Label htmlFor="token">
|
||||||
访问令牌 {!record && '*'}
|
访问令牌 {!record && '*'}
|
||||||
{record && originalToken === '********' && (
|
{record && hasOriginalToken && (
|
||||||
<span className="ml-2 text-xs text-muted-foreground">
|
<span className="ml-2 text-xs text-muted-foreground">
|
||||||
(已配置,修改请输入新令牌)
|
(已配置,修改请输入新令牌)
|
||||||
</span>
|
</span>
|
||||||
@ -275,7 +283,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
type="password"
|
type="password"
|
||||||
value={formData.token || ''}
|
value={formData.token || ''}
|
||||||
onChange={(e) => setFormData({ ...formData, token: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, token: e.target.value })}
|
||||||
placeholder={record ? (originalToken === '********' ? '保持不变或输入新令牌' : '请输入访问令牌') : '请输入访问令牌'}
|
placeholder={record ? (hasOriginalToken ? '保持不变或输入新令牌' : '请输入访问令牌') : '请输入访问令牌'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -93,6 +93,8 @@ const ExternalPage: React.FC = () => {
|
|||||||
description: success ? '外部系统连接正常' : '无法连接到外部系统',
|
description: success ? '外部系统连接正常' : '无法连接到外部系统',
|
||||||
variant: success ? 'default' : 'destructive',
|
variant: success ? 'default' : 'destructive',
|
||||||
});
|
});
|
||||||
|
// 刷新列表以更新最后连接时间等信息
|
||||||
|
loadData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ title: '测试失败', description: '无法测试连接', variant: 'destructive' });
|
toast({ title: '测试失败', description: '无法测试连接', variant: 'destructive' });
|
||||||
}
|
}
|
||||||
@ -263,6 +265,10 @@ const ExternalPage: React.FC = () => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline flex items-center gap-1"
|
className="text-blue-600 hover:underline flex items-center gap-1"
|
||||||
|
onClick={() => {
|
||||||
|
// 点击链接后刷新列表
|
||||||
|
setTimeout(() => loadData(), 100);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Link className="h-3 w-3" />
|
<Link className="h-3 w-3" />
|
||||||
{item.url}
|
{item.url}
|
||||||
|
|||||||
@ -30,3 +30,7 @@ export const testConnection = (id: number) =>
|
|||||||
// 更新状态
|
// 更新状态
|
||||||
export const updateStatus = (id: number, enabled: boolean) =>
|
export const updateStatus = (id: number, enabled: boolean) =>
|
||||||
request.put(`${BASE_URL}/${id}/status`, null, { params: { enabled } });
|
request.put(`${BASE_URL}/${id}/status`, null, { params: { enabled } });
|
||||||
|
|
||||||
|
// 获取外部系统列表(不分页)
|
||||||
|
export const getExternalSystemList = (params?: { type?: string }) =>
|
||||||
|
request.get<ExternalSystemResponse[]>(`${BASE_URL}/list`, { params });
|
||||||
@ -61,6 +61,17 @@ export const getRepositoryProjects = (params: {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取仓库项目列表(不分页)
|
||||||
|
*/
|
||||||
|
export const getRepositoryProjectsList = (params: {
|
||||||
|
externalSystemId: number;
|
||||||
|
repoGroupId?: number;
|
||||||
|
}) =>
|
||||||
|
request.get<RepositoryProjectResponse[]>(`${PROJECT_URL}/list`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步仓库项目数据
|
* 同步仓库项目数据
|
||||||
* @param externalSystemId 外部系统ID(必填)
|
* @param externalSystemId 外部系统ID(必填)
|
||||||
@ -94,19 +105,14 @@ export const getRepositoryBranches = (params: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取仓库分支列表(不分页,按最后更新时间倒序排序)
|
* 获取仓库分支列表(不分页)
|
||||||
*/
|
*/
|
||||||
export const getRepositoryBranchesList = (params: {
|
export const getRepositoryBranchesList = (params: {
|
||||||
externalSystemId: number;
|
externalSystemId: number;
|
||||||
repoProjectId: number;
|
repoProjectId: number;
|
||||||
}) =>
|
}) =>
|
||||||
request.get<RepositoryBranchResponse[]>(`${BRANCH_URL}/list`, {
|
request.get<RepositoryBranchResponse[]>(`${BRANCH_URL}/list`, {
|
||||||
params: {
|
params,
|
||||||
sortField: 'lastUpdateTime',
|
|
||||||
sortOrder: 'DESC',
|
|
||||||
externalSystemId: params.externalSystemId,
|
|
||||||
repoProjectId: params.repoProjectId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user