三方系统密码加密
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<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const viewportRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
ref={viewportRef}
|
||||
className="h-full w-full rounded-[inherit]"
|
||||
onWheel={(e) => {
|
||||
// 允许滚轮在整个viewport区域工作
|
||||
if (viewportRef.current) {
|
||||
viewportRef.current.scrollTop += e.deltaY;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
);
|
||||
})
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import type {Application, RepositoryProject} from '../types';
|
||||
import type {Application} from '../types';
|
||||
import {DevelopmentLanguageTypeEnum} from '../types';
|
||||
import {createApplication, updateApplication, getRepositoryProjectsBySystem} 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 {createApplication, updateApplication} from '../service';
|
||||
import {getEnabledCategories} from '../../Category/service';
|
||||
import type {ApplicationCategoryResponse} from '../../Category/types';
|
||||
import {
|
||||
@ -37,16 +34,6 @@ import {useForm} from "react-hook-form";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
import {applicationFormSchema, type ApplicationFormValues} from "../schema";
|
||||
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 {
|
||||
open: boolean;
|
||||
@ -62,8 +49,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||
initialValues,
|
||||
}) => {
|
||||
const [categories, setCategories] = useState<ApplicationCategoryResponse[]>([]);
|
||||
const [externalSystems, setExternalSystems] = useState<ExternalSystemResponse[]>([]);
|
||||
const [repositoryProjects, setRepositoryProjects] = useState<RepositoryProject[]>([]);
|
||||
const {toast} = useToast();
|
||||
const isEdit = !!initialValues?.id;
|
||||
|
||||
@ -77,8 +62,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||
language: DevelopmentLanguageTypeEnum.JAVA,
|
||||
enabled: true,
|
||||
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(() => {
|
||||
loadCategories();
|
||||
loadExternalSystems();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -133,36 +95,13 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||
language: initialValues.language,
|
||||
enabled: initialValues.enabled,
|
||||
sort: initialValues.sort,
|
||||
externalSystemId: initialValues.externalSystemId,
|
||||
repoProjectId: initialValues.repoProjectId
|
||||
});
|
||||
|
||||
// 如果有Gitlab ID,加载仓库项目
|
||||
if (initialValues.externalSystemId) {
|
||||
fetchRepositoryProjects(initialValues.externalSystemId);
|
||||
}
|
||||
}
|
||||
}, [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) => {
|
||||
console.log('Form submitted with values:', values);
|
||||
try {
|
||||
// 保留 externalSystemId 字段,传给后端
|
||||
const submitData = values;
|
||||
|
||||
if (isEdit) {
|
||||
@ -203,9 +142,9 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||
<DialogTitle>{isEdit ? '编辑' : '新建'}应用</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form className="flex flex-col flex-1 overflow-hidden">
|
||||
<ScrollArea className="flex-1 px-6">
|
||||
<div className="space-y-4 pr-4">
|
||||
<form className="space-y-6">
|
||||
<div className="px-6">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -298,166 +237,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||
/>
|
||||
</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
|
||||
control={form.control}
|
||||
name="appDesc"
|
||||
@ -514,7 +293,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||
/>
|
||||
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<DialogFooter className="px-6 py-4 border-t mt-0">
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
|
||||
@ -242,8 +242,8 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
<Table minWidth="890px">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead width="160px">分类名称</TableHead>
|
||||
<TableHead width="140px">分类代码</TableHead>
|
||||
<TableHead width="160px">分类名称</TableHead>
|
||||
<TableHead width="60px">图标</TableHead>
|
||||
<TableHead width="120px">应用数量</TableHead>
|
||||
<TableHead width="80px">排序</TableHead>
|
||||
@ -265,14 +265,14 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
) : data?.content && data.content.length > 0 ? (
|
||||
data.content.map((record) => (
|
||||
<TableRow key={record.id}>
|
||||
<TableCell width="160px" className="font-medium">
|
||||
{record.name}
|
||||
</TableCell>
|
||||
<TableCell width="140px">
|
||||
<code className="text-xs bg-muted px-2 py-0.5 rounded whitespace-nowrap">
|
||||
{record.code}
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell width="160px" className="font-medium">
|
||||
{record.name}
|
||||
</TableCell>
|
||||
<TableCell width="60px">
|
||||
{record.icon ? (
|
||||
<div className="flex items-center justify-center">
|
||||
@ -350,13 +350,13 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
rules={{ required: '请输入分类名称' }}
|
||||
name="code"
|
||||
rules={{ required: '请输入分类代码' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>分类名称</FormLabel>
|
||||
<FormLabel>分类代码</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="输入分类名称" {...field} />
|
||||
<Input placeholder="输入分类代码" {...field} disabled={!!editRecord} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -365,13 +365,13 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="code"
|
||||
rules={{ required: '请输入分类代码' }}
|
||||
name="name"
|
||||
rules={{ required: '请输入分类名称' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>分类代码</FormLabel>
|
||||
<FormLabel>分类名称</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="输入分类代码" {...field} disabled={!!editRecord} />
|
||||
<Input placeholder="输入分类名称" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -228,48 +228,14 @@ const ApplicationList: React.FC = () => {
|
||||
},
|
||||
{
|
||||
id: 'teamCount',
|
||||
header: '团队数量',
|
||||
size: 100,
|
||||
header: '团队使用数量',
|
||||
size: 120,
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<span className="font-medium">{row.original.teamCount || 0}</span>
|
||||
</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',
|
||||
header: '开发语言',
|
||||
|
||||
@ -16,8 +16,6 @@ export const applicationFormSchema = z.object({
|
||||
required_error: '请选择应用分类',
|
||||
invalid_type_error: '应用分类必须是数字',
|
||||
}),
|
||||
externalSystemId: z.number().optional(),
|
||||
repoProjectId: z.number().optional(),
|
||||
language: z.nativeEnum(DevelopmentLanguageTypeEnum, {
|
||||
required_error: "请选择开发语言",
|
||||
}),
|
||||
|
||||
@ -28,6 +28,8 @@ import type {
|
||||
} from '../types';
|
||||
import type { RepositoryBranchResponse } from '@/pages/Resource/Git/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 {
|
||||
open: boolean;
|
||||
@ -48,6 +50,8 @@ interface TeamApplicationDialogProps {
|
||||
deploySystemId: number | null;
|
||||
deployJob: string;
|
||||
workflowDefinitionId: number | null;
|
||||
codeSourceSystemId: number | null;
|
||||
codeSourceProjectId: number | null;
|
||||
}) => Promise<void>;
|
||||
onLoadBranches: (appId: number, app: Application) => Promise<RepositoryBranchResponse[]>;
|
||||
onLoadJenkinsJobs: (systemId: number) => Promise<any[]>;
|
||||
@ -78,6 +82,8 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
deploySystemId: null as number | null,
|
||||
deployJob: '',
|
||||
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 [loadingJobs, setLoadingJobs] = 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 [branchPopoverOpen, setBranchPopoverOpen] = useState(false);
|
||||
const [projectSearchValue, setProjectSearchValue] = useState('');
|
||||
const [projectPopoverOpen, setProjectPopoverOpen] = useState(false);
|
||||
const [jobSearchValue, setJobSearchValue] = useState('');
|
||||
const [jobPopoverOpen, setJobPopoverOpen] = useState(false);
|
||||
|
||||
@ -104,13 +115,28 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
deploySystemId: application.deploySystemId || null,
|
||||
deployJob: application.deployJob || '',
|
||||
workflowDefinitionId: application.workflowDefinitionId || null,
|
||||
codeSourceSystemId: application.codeSourceSystemId || null,
|
||||
codeSourceProjectId: application.codeSourceProjectId || null,
|
||||
});
|
||||
|
||||
// 加载分支和Jobs
|
||||
const app = applications.find(a => a.id === application.applicationId);
|
||||
if (app) {
|
||||
loadBranches(application.applicationId, app);
|
||||
// 加载仓库项目
|
||||
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);
|
||||
if (app) {
|
||||
loadBranches(application.applicationId, app);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载Jenkins Jobs
|
||||
if (application.deploySystemId) {
|
||||
loadJenkinsJobs(application.deploySystemId);
|
||||
}
|
||||
@ -122,6 +148,8 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
deploySystemId: null,
|
||||
deployJob: '',
|
||||
workflowDefinitionId: null,
|
||||
codeSourceSystemId: null,
|
||||
codeSourceProjectId: null,
|
||||
});
|
||||
setBranches([]);
|
||||
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 app = applications.find(a => a.id === appId);
|
||||
setFormData({
|
||||
appId: appId,
|
||||
branch: '',
|
||||
deploySystemId: null,
|
||||
deployJob: '',
|
||||
workflowDefinitionId: null,
|
||||
codeSourceSystemId: null,
|
||||
codeSourceProjectId: null,
|
||||
});
|
||||
|
||||
// 加载该应用的分支列表
|
||||
if (app) {
|
||||
loadBranches(appId, app);
|
||||
}
|
||||
// 清空分支列表(分支现在基于代码源,不基于应用)
|
||||
setBranches([]);
|
||||
};
|
||||
|
||||
// 处理 Jenkins 系统选择
|
||||
@ -184,6 +242,45 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
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 () => {
|
||||
// 表单验证
|
||||
@ -204,6 +301,8 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
deploySystemId: formData.deploySystemId,
|
||||
deployJob: formData.deployJob,
|
||||
workflowDefinitionId: formData.workflowDefinitionId,
|
||||
codeSourceSystemId: formData.codeSourceSystemId,
|
||||
codeSourceProjectId: formData.codeSourceProjectId,
|
||||
});
|
||||
|
||||
toast({
|
||||
@ -279,10 +378,124 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
)}
|
||||
</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">
|
||||
<Label>分支</Label>
|
||||
{formData.appId ? (
|
||||
{formData.codeSourceProjectId ? (
|
||||
<Popover
|
||||
open={branchPopoverOpen}
|
||||
onOpenChange={setBranchPopoverOpen}
|
||||
@ -371,7 +584,7 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<Input placeholder="请先选择应用" disabled />
|
||||
<Input placeholder="请先选择仓库项目" disabled />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@ -140,6 +140,8 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
deploySystemId: number | null;
|
||||
deployJob: string;
|
||||
workflowDefinitionId: number | null;
|
||||
codeSourceSystemId: number | null;
|
||||
codeSourceProjectId: number | null;
|
||||
}) => {
|
||||
if (!editingEnvironment) return;
|
||||
|
||||
@ -151,6 +153,8 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
deploySystemId: data.deploySystemId || undefined,
|
||||
deployJob: data.deployJob,
|
||||
workflowDefinitionId: data.workflowDefinitionId || undefined,
|
||||
codeSourceSystemId: data.codeSourceSystemId || undefined,
|
||||
codeSourceProjectId: data.codeSourceProjectId || undefined,
|
||||
};
|
||||
|
||||
if (appDialogMode === 'edit' && data.id) {
|
||||
|
||||
@ -7,7 +7,9 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@ -35,14 +37,14 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
interface TeamModalProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSuccess: () => void;
|
||||
initialValues?: TeamResponse;
|
||||
}
|
||||
|
||||
const TeamModal: React.FC<TeamModalProps> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onOpenChange,
|
||||
onSuccess,
|
||||
initialValues,
|
||||
}) => {
|
||||
@ -120,7 +122,7 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
});
|
||||
}
|
||||
onSuccess();
|
||||
onCancel();
|
||||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast({
|
||||
@ -133,15 +135,14 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
|
||||
<DialogContent className="max-w-[600px] max-h-[85vh] flex flex-col p-0">
|
||||
<DialogHeader className="px-6 pt-6 pb-4">
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isEdit ? '编辑' : '新建'}团队</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form className="flex flex-col flex-1 overflow-hidden">
|
||||
<div className="flex-1 overflow-y-auto px-6">
|
||||
<div className="space-y-4 pb-4">
|
||||
<DialogBody>
|
||||
<Form {...form}>
|
||||
<form className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -277,27 +278,25 @@ const TeamModal: React.FC<TeamModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="h-6" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 border-t bg-muted/30 flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const isValid = await form.trigger();
|
||||
if (isValid) {
|
||||
handleSubmit(form.getValues());
|
||||
}
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const isValid = await form.trigger();
|
||||
if (isValid) {
|
||||
handleSubmit(form.getValues());
|
||||
}
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@ -189,9 +189,11 @@ const TeamList: React.FC = () => {
|
||||
setEnvManageDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setModalVisible(false);
|
||||
setCurrentTeam(undefined);
|
||||
const handleModalOpenChange = (open: boolean) => {
|
||||
setModalVisible(open);
|
||||
if (!open) {
|
||||
setCurrentTeam(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageChange = (newPage: number) => {
|
||||
@ -461,14 +463,12 @@ const TeamList: React.FC = () => {
|
||||
</Card>
|
||||
|
||||
{/* 对话框 */}
|
||||
{modalVisible && (
|
||||
<TeamModal
|
||||
open={modalVisible}
|
||||
onCancel={handleModalClose}
|
||||
onSuccess={handleSuccess}
|
||||
initialValues={currentTeam}
|
||||
/>
|
||||
)}
|
||||
<TeamModal
|
||||
open={modalVisible}
|
||||
onOpenChange={handleModalOpenChange}
|
||||
onSuccess={handleSuccess}
|
||||
initialValues={currentTeam}
|
||||
/>
|
||||
<DeleteDialog
|
||||
open={deleteDialogOpen}
|
||||
record={currentTeam}
|
||||
|
||||
@ -90,12 +90,16 @@ export interface TeamApplication extends BaseResponse {
|
||||
deploySystemId?: number;
|
||||
deployJob?: string;
|
||||
workflowDefinitionId?: number;
|
||||
codeSourceSystemId?: number; // 代码源系统ID
|
||||
codeSourceProjectId?: number; // 代码源项目ID
|
||||
teamName?: string;
|
||||
applicationName?: string;
|
||||
applicationCode?: string;
|
||||
environmentName?: string;
|
||||
deploySystemName?: string;
|
||||
workflowDefinitionName?: string;
|
||||
codeSourceSystemName?: string; // 代码源系统名称
|
||||
codeSourceProjectName?: string; // 代码源项目名称
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,5 +113,7 @@ export interface TeamApplicationRequest {
|
||||
deploySystemId?: number;
|
||||
deployJob?: string;
|
||||
workflowDefinitionId?: number;
|
||||
codeSourceSystemId?: number; // 代码源系统ID
|
||||
codeSourceProjectId?: number; // 代码源项目ID
|
||||
}
|
||||
|
||||
|
||||
@ -40,16 +40,22 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
authType: 'BASIC' as AuthType,
|
||||
});
|
||||
|
||||
// 保存原始密码和Token(用于判断是否被修改)
|
||||
const [originalPassword, setOriginalPassword] = useState<string | undefined>(undefined);
|
||||
const [originalToken, setOriginalToken] = useState<string | undefined>(undefined);
|
||||
// 标记是否有原始密码和Token(用于判断是否需要提交)
|
||||
const [hasOriginalPassword, setHasOriginalPassword] = useState(false);
|
||||
const [hasOriginalToken, setHasOriginalToken] = useState(false);
|
||||
|
||||
// 掩码常量
|
||||
const MASK = '********';
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (record) {
|
||||
// 保存原始掩码值
|
||||
setOriginalPassword(record.password);
|
||||
setOriginalToken(record.token);
|
||||
// 判断是否有密码/Token(无论返回什么值,只要不为空就认为有)
|
||||
const hasPwd = !!record.password && record.password.trim() !== '';
|
||||
const hasTkn = !!record.token && record.token.trim() !== '';
|
||||
|
||||
setHasOriginalPassword(hasPwd);
|
||||
setHasOriginalToken(hasTkn);
|
||||
|
||||
setFormData({
|
||||
name: record.name,
|
||||
@ -57,15 +63,17 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
url: record.url,
|
||||
authType: record.authType,
|
||||
username: record.username,
|
||||
password: record.password || '', // 显示掩码或空
|
||||
token: record.token || '', // 显示掩码或空
|
||||
// 如果有密码,显示掩码;否则显示空
|
||||
password: hasPwd ? MASK : '',
|
||||
// 如果有Token,显示掩码;否则显示空
|
||||
token: hasTkn ? MASK : '',
|
||||
sort: record.sort,
|
||||
remark: record.remark,
|
||||
enabled: record.enabled,
|
||||
});
|
||||
} else {
|
||||
setOriginalPassword(undefined);
|
||||
setOriginalToken(undefined);
|
||||
setHasOriginalPassword(false);
|
||||
setHasOriginalToken(false);
|
||||
setFormData({ enabled: true, sort: 1, authType: 'BASIC' as AuthType });
|
||||
}
|
||||
}
|
||||
@ -104,9 +112,9 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
toast({ title: '提示', description: '请输入密码', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
// 编辑时,如果清空了原有密码(掩码),也需要输入新密码
|
||||
if (record && originalPassword === '********' && !formData.password?.trim()) {
|
||||
toast({ title: '提示', description: '请输入新密码或保持原密码不变', variant: 'destructive' });
|
||||
// 编辑时,如果清空了原有密码(掩码),需要提示
|
||||
if (record && hasOriginalPassword && !formData.password?.trim()) {
|
||||
toast({ title: '提示', description: '密码不能为空,请输入新密码或保持原密码不变', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -117,9 +125,9 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
toast({ title: '提示', description: '请输入访问令牌', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
// 编辑时,如果清空了原有Token(掩码),也需要输入新Token
|
||||
if (record && originalToken === '********' && !formData.token?.trim()) {
|
||||
toast({ title: '提示', description: '请输入新令牌或保持原令牌不变', variant: 'destructive' });
|
||||
// 编辑时,如果清空了原有Token(掩码),需要提示
|
||||
if (record && hasOriginalToken && !formData.token?.trim()) {
|
||||
toast({ title: '提示', description: '令牌不能为空,请输入新令牌或保持原令牌不变', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -129,22 +137,22 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
...formData as ExternalSystemRequest,
|
||||
};
|
||||
|
||||
// 处理密码:如果用户没有修改,保持掩码;如果修改了,提交新值
|
||||
// 处理密码:如果值还是掩码,说明没修改,提交掩码;否则提交新值
|
||||
if (formData.authType === 'BASIC') {
|
||||
if (record && formData.password === originalPassword) {
|
||||
if (record && formData.password === MASK) {
|
||||
// 没有修改,保持掩码
|
||||
submitData.password = originalPassword;
|
||||
submitData.password = MASK;
|
||||
} else {
|
||||
// 修改了或新建,提交新密码
|
||||
submitData.password = formData.password;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理Token:如果用户没有修改,保持掩码;如果修改了,提交新值
|
||||
// 处理Token:如果值还是掩码,说明没修改,提交掩码;否则提交新值
|
||||
if (formData.authType === 'TOKEN') {
|
||||
if (record && formData.token === originalToken) {
|
||||
if (record && formData.token === MASK) {
|
||||
// 没有修改,保持掩码
|
||||
submitData.token = originalToken;
|
||||
submitData.token = MASK;
|
||||
} else {
|
||||
// 修改了或新建,提交新Token
|
||||
submitData.token = formData.token;
|
||||
@ -243,7 +251,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="password">
|
||||
密码 {!record && '*'}
|
||||
{record && originalPassword === '********' && (
|
||||
{record && hasOriginalPassword && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(已配置,修改请输入新密码)
|
||||
</span>
|
||||
@ -254,7 +262,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
type="password"
|
||||
value={formData.password || ''}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
placeholder={record ? (originalPassword === '********' ? '保持不变或输入新密码' : '请输入密码') : '请输入密码'}
|
||||
placeholder={record ? (hasOriginalPassword ? '保持不变或输入新密码' : '请输入密码') : '请输入密码'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
@ -264,7 +272,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="token">
|
||||
访问令牌 {!record && '*'}
|
||||
{record && originalToken === '********' && (
|
||||
{record && hasOriginalToken && (
|
||||
<span className="ml-2 text-xs text-muted-foreground">
|
||||
(已配置,修改请输入新令牌)
|
||||
</span>
|
||||
@ -275,7 +283,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
||||
type="password"
|
||||
value={formData.token || ''}
|
||||
onChange={(e) => setFormData({ ...formData, token: e.target.value })}
|
||||
placeholder={record ? (originalToken === '********' ? '保持不变或输入新令牌' : '请输入访问令牌') : '请输入访问令牌'}
|
||||
placeholder={record ? (hasOriginalToken ? '保持不变或输入新令牌' : '请输入访问令牌') : '请输入访问令牌'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -93,6 +93,8 @@ const ExternalPage: React.FC = () => {
|
||||
description: success ? '外部系统连接正常' : '无法连接到外部系统',
|
||||
variant: success ? 'default' : 'destructive',
|
||||
});
|
||||
// 刷新列表以更新最后连接时间等信息
|
||||
loadData();
|
||||
} catch (error) {
|
||||
toast({ title: '测试失败', description: '无法测试连接', variant: 'destructive' });
|
||||
}
|
||||
@ -263,6 +265,10 @@ const ExternalPage: React.FC = () => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline flex items-center gap-1"
|
||||
onClick={() => {
|
||||
// 点击链接后刷新列表
|
||||
setTimeout(() => loadData(), 100);
|
||||
}}
|
||||
>
|
||||
<Link className="h-3 w-3" />
|
||||
{item.url}
|
||||
|
||||
@ -30,3 +30,7 @@ export const testConnection = (id: number) =>
|
||||
// 更新状态
|
||||
export const updateStatus = (id: number, enabled: boolean) =>
|
||||
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(必填)
|
||||
@ -94,19 +105,14 @@ 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,
|
||||
},
|
||||
params,
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user