1.33 日志通用查询

This commit is contained in:
dengqichen 2025-12-16 10:54:54 +08:00
parent 5056b133ca
commit 9225414a87
9 changed files with 825 additions and 32 deletions

View File

@ -6,6 +6,7 @@ interface UsePendingApprovalOptions {
teams: DeployTeam[]; teams: DeployTeam[];
currentTeamId?: number | null; // 当前选中的团队ID currentTeamId?: number | null; // 当前选中的团队ID
currentEnvId?: number | null; // 当前选中的环境ID currentEnvId?: number | null; // 当前选中的环境ID
canApprove?: boolean; // 当前用户是否有审批权限
pollingEnabled?: boolean; pollingEnabled?: boolean;
pollingInterval?: number; // 轮询间隔默认30秒 pollingInterval?: number; // 轮询间隔默认30秒
} }
@ -20,6 +21,7 @@ export function usePendingApproval({
teams, teams,
currentTeamId, currentTeamId,
currentEnvId, currentEnvId,
canApprove = false,
pollingEnabled = true, pollingEnabled = true,
pollingInterval = 30000 // 默认30秒 pollingInterval = 30000 // 默认30秒
}: UsePendingApprovalOptions) { }: UsePendingApprovalOptions) {
@ -41,7 +43,9 @@ export function usePendingApproval({
// 加载待审批数量 - 使用 useCallback 避免重复创建 // 加载待审批数量 - 使用 useCallback 避免重复创建
const loadPendingApprovalCount = useCallback(async () => { const loadPendingApprovalCount = useCallback(async () => {
if (!pollingEnabled || workflowDefinitionKeys.length === 0) { // 只有当用户有审批权限时才调用接口
if (!canApprove || !pollingEnabled || workflowDefinitionKeys.length === 0) {
setPendingApprovalCount(0);
return; return;
} }
@ -59,11 +63,18 @@ export function usePendingApproval({
// 静默失败,不影响主页面 // 静默失败,不影响主页面
console.error('Failed to load pending approval count:', error); console.error('Failed to load pending approval count:', error);
} }
}, [workflowDefinitionKeys, currentTeamId, currentEnvId, pollingEnabled]); }, [canApprove, workflowDefinitionKeys, currentTeamId, currentEnvId, pollingEnabled]);
// 轮询待审批数量 // 轮询待审批数量
useEffect(() => { useEffect(() => {
if (!pollingEnabled || workflowDefinitionKeys.length === 0) { // 只有当用户有审批权限时才启动轮询
if (!canApprove || !pollingEnabled || workflowDefinitionKeys.length === 0) {
// 清除计数并停止轮询
setPendingApprovalCount(0);
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return; return;
} }
@ -83,7 +94,7 @@ export function usePendingApproval({
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
} }
}; };
}, [loadPendingApprovalCount, pollingEnabled, workflowDefinitionKeys.length, pollingInterval]); }, [canApprove, loadPendingApprovalCount, pollingEnabled, workflowDefinitionKeys.length, pollingInterval]);
return { return {
approvalModalOpen, approvalModalOpen,

View File

@ -55,10 +55,22 @@ const Dashboard: React.FC = () => {
} }
}); });
// 计算当前用户是否有审批权限(需要在 usePendingApproval 之前计算)
const currentCanApprove = React.useMemo(() => {
if (!deploymentData.currentTeam || !deploymentData.currentEnvId) {
return false;
}
const currentEnv = deploymentData.currentTeam.environments.find(
env => env.environmentId === deploymentData.currentEnvId
);
return currentEnv?.canApprove === true;
}, [deploymentData.currentTeam, deploymentData.currentEnvId]);
const approvalData = usePendingApproval({ const approvalData = usePendingApproval({
teams: deploymentData.teams, teams: deploymentData.teams,
currentTeamId: deploymentData.currentTeamId, currentTeamId: deploymentData.currentTeamId,
currentEnvId: deploymentData.currentEnvId, currentEnvId: deploymentData.currentEnvId,
canApprove: currentCanApprove,
pollingEnabled: !deploymentData.loading pollingEnabled: !deploymentData.loading
}); });

View File

@ -0,0 +1,85 @@
import React, { useEffect, useState } from 'react';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { getServerList } from '@/pages/Resource/Server/List/service';
import type { ServerResponse } from '@/pages/Resource/Server/List/types';
interface DockerRuntimeConfigProps {
dockerServerId: number | null;
dockerContainerName: string;
onDockerServerChange: (serverId: number | null) => void;
onDockerContainerNameChange: (containerName: string) => void;
}
export const DockerRuntimeConfig: React.FC<DockerRuntimeConfigProps> = ({
dockerServerId,
dockerContainerName,
onDockerServerChange,
onDockerContainerNameChange,
}) => {
const [servers, setServers] = useState<ServerResponse[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadServers();
}, []);
const loadServers = async () => {
setLoading(true);
try {
const result = await getServerList();
setServers(result || []);
} catch (error) {
console.error('加载服务器列表失败:', error);
setServers([]);
} finally {
setLoading(false);
}
};
return (
<div className="space-y-4 pl-4 border-l-2 border-muted">
<div className="space-y-2">
<Label>Docker服务器</Label>
<Select
value={dockerServerId?.toString() || ''}
onValueChange={(value) => onDockerServerChange(value ? Number(value) : null)}
disabled={loading}
>
<SelectTrigger>
<SelectValue placeholder={loading ? '加载中...' : '选择Docker服务器'} />
</SelectTrigger>
<SelectContent>
{servers.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
{loading ? '加载中...' : '暂无服务器'}
</div>
) : (
servers.map((server) => (
<SelectItem key={server.id} value={server.id!.toString()}>
{server.serverName} ({server.hostIp})
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Input
placeholder="请输入Docker容器名称"
value={dockerContainerName}
onChange={(e) => onDockerContainerNameChange(e.target.value)}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,197 @@
import React, { useState, useEffect } from 'react';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Loader2 } from 'lucide-react';
import { getExternalSystemList } from '@/pages/Resource/External/List/service';
import { getK8sNamespaceList, getK8sDeploymentByNamespace } from '@/pages/Resource/K8s/List/service';
interface K8sRuntimeConfigProps {
k8sSystemId: number | null;
k8sNamespaceId: number | null;
k8sDeploymentId: number | null;
onK8sSystemChange: (systemId: number | null) => void;
onK8sNamespaceChange: (namespaceId: number | null) => void;
onK8sDeploymentIdChange: (deploymentId: number | null) => void;
}
export const K8sRuntimeConfig: React.FC<K8sRuntimeConfigProps> = ({
k8sSystemId,
k8sNamespaceId,
k8sDeploymentId,
onK8sSystemChange,
onK8sNamespaceChange,
onK8sDeploymentIdChange,
}) => {
const [k8sSystems, setK8sSystems] = useState<any[]>([]);
const [namespaces, setNamespaces] = useState<any[]>([]);
const [deployments, setDeployments] = useState<any[]>([]);
const [loadingNamespaces, setLoadingNamespaces] = useState(false);
const [loadingDeployments, setLoadingDeployments] = useState(false);
// 加载K8S系统列表
useEffect(() => {
const loadK8sSystems = async () => {
try {
const systems = await getExternalSystemList({ type: 'K8S' });
setK8sSystems(systems || []);
} catch (error) {
console.error('加载K8S系统失败:', error);
setK8sSystems([]);
}
};
loadK8sSystems();
}, []);
// 加载Namespace列表
useEffect(() => {
const loadNamespaces = async () => {
if (!k8sSystemId) {
setNamespaces([]);
return;
}
setLoadingNamespaces(true);
try {
const data = await getK8sNamespaceList(k8sSystemId);
setNamespaces(data || []);
} catch (error) {
console.error('加载Namespace失败:', error);
setNamespaces([]);
} finally {
setLoadingNamespaces(false);
}
};
loadNamespaces();
}, [k8sSystemId]);
// 加载Deployment列表
useEffect(() => {
const loadDeployments = async () => {
if (!k8sSystemId || !k8sNamespaceId) {
setDeployments([]);
return;
}
setLoadingDeployments(true);
try {
const data = await getK8sDeploymentByNamespace(k8sSystemId, k8sNamespaceId);
setDeployments(data || []);
} catch (error) {
console.error('加载Deployment失败:', error);
setDeployments([]);
} finally {
setLoadingDeployments(false);
}
};
loadDeployments();
}, [k8sSystemId, k8sNamespaceId]);
return (
<div className="space-y-4 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center gap-2 mb-2">
<div className="h-1 w-1 rounded-full bg-blue-500" />
<span className="text-sm font-medium text-muted-foreground">Kubernetes </span>
</div>
{/* K8S系统选择 */}
<div className="space-y-2">
<Label>K8S系统</Label>
<Select
value={k8sSystemId?.toString() || ''}
onValueChange={(value) => {
onK8sSystemChange(Number(value));
}}
>
<SelectTrigger>
<SelectValue placeholder="选择K8S系统" />
</SelectTrigger>
<SelectContent>
{k8sSystems.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
K8S系统
</div>
) : (
k8sSystems.map((system) => (
<SelectItem key={system.id} value={system.id.toString()}>
{system.name}
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
{/* Namespace选择 */}
<div className="space-y-2">
<Label>Namespace</Label>
<Select
value={k8sNamespaceId?.toString() || ''}
onValueChange={(value) => {
onK8sNamespaceChange(Number(value));
}}
disabled={!k8sSystemId || loadingNamespaces}
>
<SelectTrigger>
<SelectValue placeholder={loadingNamespaces ? '加载中...' : '选择Namespace'} />
</SelectTrigger>
<SelectContent>
{loadingNamespaces ? (
<div className="p-4 text-center">
<Loader2 className="h-4 w-4 animate-spin mx-auto" />
</div>
) : namespaces.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
{k8sSystemId ? '暂无Namespace' : '请先选择K8S系统'}
</div>
) : (
namespaces.map((ns) => (
<SelectItem key={ns.id} value={ns.id.toString()}>
{ns.namespaceName}
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
{/* Deployment选择 */}
<div className="space-y-2">
<Label>Deployment</Label>
<Select
value={k8sDeploymentId?.toString() || ''}
onValueChange={(value) => {
onK8sDeploymentIdChange(Number(value));
}}
disabled={!k8sNamespaceId || loadingDeployments}
>
<SelectTrigger>
<SelectValue placeholder={loadingDeployments ? '加载中...' : '选择Deployment'} />
</SelectTrigger>
<SelectContent>
{loadingDeployments ? (
<div className="p-4 text-center">
<Loader2 className="h-4 w-4 animate-spin mx-auto" />
</div>
) : deployments.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
{k8sNamespaceId ? '暂无Deployment' : '请先选择Namespace'}
</div>
) : (
deployments.map((deployment) => (
<SelectItem key={deployment.id} value={deployment.id.toString()}>
{deployment.deploymentName}
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
</div>
);
};

View File

@ -0,0 +1,114 @@
import React from 'react';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { K8sRuntimeConfig } from './K8sRuntimeConfig';
import { DockerRuntimeConfig } from './DockerRuntimeConfig';
import { ServerRuntimeConfig } from './ServerRuntimeConfig';
import type { RuntimeType } from '../types';
import { RUNTIME_TYPE_OPTIONS } from '../types';
interface RuntimeConfigSectionProps {
runtimeType: RuntimeType | null;
onRuntimeTypeChange: (type: RuntimeType | null) => void;
// K8S配置
k8sSystemId: number | null;
k8sNamespaceId: number | null;
k8sDeploymentId: number | null;
onK8sSystemChange: (systemId: number | null) => void;
onK8sNamespaceChange: (namespaceId: number | null) => void;
onK8sDeploymentIdChange: (deploymentId: number | null) => void;
// Docker配置预留
dockerServerId?: number | null;
dockerContainerName?: string;
onDockerServerChange?: (serverId: number | null) => void;
onDockerContainerNameChange?: (containerName: string) => void;
// Server配置预留
serverId?: number | null;
logQueryCommand?: string;
onServerChange?: (serverId: number | null) => void;
onLogQueryCommandChange?: (command: string) => void;
}
export const RuntimeConfigSection: React.FC<RuntimeConfigSectionProps> = ({
runtimeType,
onRuntimeTypeChange,
k8sSystemId,
k8sNamespaceId,
k8sDeploymentId,
onK8sSystemChange,
onK8sNamespaceChange,
onK8sDeploymentIdChange,
dockerServerId,
dockerContainerName,
onDockerServerChange,
onDockerContainerNameChange,
serverId,
logQueryCommand,
onServerChange,
onLogQueryCommandChange,
}) => {
return (
<div className="space-y-4">
{/* 运行时类型选择 */}
<div className="space-y-2">
<Label></Label>
<Select
value={runtimeType || ''}
onValueChange={(value) => {
onRuntimeTypeChange(value as RuntimeType);
}}
>
<SelectTrigger>
<SelectValue placeholder="选择运行时类型" />
</SelectTrigger>
<SelectContent>
{RUNTIME_TYPE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
</p>
</div>
{/* 根据运行时类型显示对应的配置 */}
{runtimeType === 'K8S' && (
<K8sRuntimeConfig
k8sSystemId={k8sSystemId}
k8sNamespaceId={k8sNamespaceId}
k8sDeploymentId={k8sDeploymentId}
onK8sSystemChange={onK8sSystemChange}
onK8sNamespaceChange={onK8sNamespaceChange}
onK8sDeploymentIdChange={onK8sDeploymentIdChange}
/>
)}
{runtimeType === 'DOCKER' && (
<DockerRuntimeConfig
dockerServerId={dockerServerId || null}
dockerContainerName={dockerContainerName || ''}
onDockerServerChange={onDockerServerChange || (() => {})}
onDockerContainerNameChange={onDockerContainerNameChange || (() => {})}
/>
)}
{runtimeType === 'SERVER' && (
<ServerRuntimeConfig
serverId={serverId || null}
logQueryCommand={logQueryCommand || ''}
onServerChange={onServerChange || (() => {})}
onLogQueryCommandChange={onLogQueryCommandChange || (() => {})}
/>
)}
</div>
);
};

View File

@ -0,0 +1,88 @@
import React, { useEffect, useState } from 'react';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { getServerList } from '@/pages/Resource/Server/List/service';
import type { ServerResponse } from '@/pages/Resource/Server/List/types';
interface ServerRuntimeConfigProps {
serverId: number | null;
logQueryCommand: string;
onServerChange: (serverId: number | null) => void;
onLogQueryCommandChange: (command: string) => void;
}
export const ServerRuntimeConfig: React.FC<ServerRuntimeConfigProps> = ({
serverId,
logQueryCommand,
onServerChange,
onLogQueryCommandChange,
}) => {
const [servers, setServers] = useState<ServerResponse[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadServers();
}, []);
const loadServers = async () => {
setLoading(true);
try {
const result = await getServerList();
setServers(result || []);
} catch (error) {
console.error('加载服务器列表失败:', error);
setServers([]);
} finally {
setLoading(false);
}
};
return (
<div className="space-y-4 pl-4 border-l-2 border-muted">
<div className="space-y-2">
<Label></Label>
<Select
value={serverId?.toString() || ''}
onValueChange={(value) => onServerChange(value ? Number(value) : null)}
disabled={loading}
>
<SelectTrigger>
<SelectValue placeholder={loading ? '加载中...' : '选择服务器'} />
</SelectTrigger>
<SelectContent>
{servers.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
{loading ? '加载中...' : '暂无服务器'}
</div>
) : (
servers.map((server) => (
<SelectItem key={server.id} value={server.id!.toString()}>
{server.serverName} ({server.hostIp})
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Input
placeholder="请输入日志查询命令tail -f /var/log/app.log"
value={logQueryCommand}
onChange={(e) => onLogQueryCommandChange(e.target.value)}
/>
<p className="text-xs text-muted-foreground">
Shell命令
</p>
</div>
</div>
);
};

View File

@ -26,8 +26,10 @@ import type {
TeamApplication, TeamApplication,
Application, Application,
BuildType, BuildType,
RuntimeType,
} from '../types'; } from '../types';
import { BUILD_TYPE_OPTIONS } from '../types'; import { BUILD_TYPE_OPTIONS } from '../types';
import { RuntimeConfigSection } from './RuntimeConfigSection';
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 { getExternalSystemList } from '@/pages/Resource/External/List/service';
@ -57,9 +59,18 @@ interface TeamApplicationDialogProps {
workflowDefinitionId: number | null; workflowDefinitionId: number | null;
sourceGitSystemId: number | null; sourceGitSystemId: number | null;
sourceGitProjectId: number | null; sourceGitProjectId: number | null;
targetGitSystemId: number | null; // 🆕 目标Git系统ID targetGitSystemId: number | null;
targetGitProjectId: number | null; // 🆕 目标Git项目ID targetGitProjectId: number | null;
targetBranch: string; // 🆕 目标分支 targetBranch: string;
// 运行时配置
runtimeType: RuntimeType | null;
k8sSystemId: number | null;
k8sNamespaceId: number | null;
k8sDeploymentId: number | null;
dockerServerId: number | null;
dockerContainerName: string;
serverId: number | null;
logQueryCommand: string;
}) => 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[]>;
@ -97,6 +108,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
targetGitSystemId: null as number | null, // 目标Git系统ID targetGitSystemId: null as number | null, // 目标Git系统ID
targetGitProjectId: null as number | null, // 目标Git项目ID targetGitProjectId: null as number | null, // 目标Git项目ID
targetBranch: '', // 目标分支名称 targetBranch: '', // 目标分支名称
// 运行时配置
runtimeType: null as RuntimeType | null, // 运行时类型
k8sSystemId: null as number | null, // K8S系统ID
k8sNamespaceId: null as number | null, // K8S命名空间ID
k8sDeploymentId: null as number | null, // K8S Deployment ID
dockerServerId: null as number | null, // Docker服务器ID
dockerContainerName: '', // Docker容器名称
serverId: null as number | null, // 服务器ID
logQueryCommand: '', // 日志查询命令
}); });
// 加载状态 // 加载状态
@ -137,6 +157,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
targetGitSystemId: application.targetGitSystemId || null, targetGitSystemId: application.targetGitSystemId || null,
targetGitProjectId: application.targetGitProjectId || null, targetGitProjectId: application.targetGitProjectId || null,
targetBranch: application.targetBranch || '', targetBranch: application.targetBranch || '',
// 运行时配置
runtimeType: application.runtimeType || null,
k8sSystemId: application.k8sSystemId || null,
k8sNamespaceId: application.k8sNamespaceId || null,
k8sDeploymentId: application.k8sDeploymentId || null,
dockerServerId: application.dockerServerId || null,
dockerContainerName: application.dockerContainerName || '',
serverId: application.serverId || null,
logQueryCommand: application.logQueryCommand || '',
}); });
// 加载源仓库项目 // 加载源仓库项目
@ -184,6 +213,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
targetGitSystemId: null, targetGitSystemId: null,
targetGitProjectId: null, targetGitProjectId: null,
targetBranch: '', targetBranch: '',
// 运行时配置
runtimeType: null,
k8sSystemId: null,
k8sNamespaceId: null,
k8sDeploymentId: null,
dockerServerId: null,
dockerContainerName: '',
serverId: null,
logQueryCommand: '',
}); });
setBranches([]); setBranches([]);
setJenkinsJobs([]); setJenkinsJobs([]);
@ -279,6 +317,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
targetGitSystemId: null, targetGitSystemId: null,
targetGitProjectId: null, targetGitProjectId: null,
targetBranch: '', targetBranch: '',
// 保留运行时配置
runtimeType: formData.runtimeType,
k8sSystemId: formData.k8sSystemId,
k8sNamespaceId: formData.k8sNamespaceId,
k8sDeploymentId: formData.k8sDeploymentId,
dockerServerId: formData.dockerServerId,
dockerContainerName: formData.dockerContainerName,
serverId: formData.serverId,
logQueryCommand: formData.logQueryCommand,
}); });
// 清空分支列表(分支现在基于代码源,不基于应用) // 清空分支列表(分支现在基于代码源,不基于应用)
setBranches([]); setBranches([]);
@ -435,6 +482,34 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
} }
} }
// 运行时配置验证
if (formData.runtimeType === 'K8S') {
if (!formData.k8sSystemId) {
toast({
variant: 'destructive',
title: '请选择K8S系统',
description: '已选择K8S运行时类型必须配置K8S系统',
});
return;
}
if (!formData.k8sNamespaceId) {
toast({
variant: 'destructive',
title: '请选择Namespace',
description: '已选择K8S运行时类型必须配置Namespace',
});
return;
}
if (!formData.k8sDeploymentId) {
toast({
variant: 'destructive',
title: '请选择Deployment',
description: '已选择K8S运行时类型必须配置Deployment',
});
return;
}
}
setSaving(true); setSaving(true);
try { try {
await onSave({ await onSave({
@ -450,6 +525,15 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
targetGitSystemId: formData.targetGitSystemId, targetGitSystemId: formData.targetGitSystemId,
targetGitProjectId: formData.targetGitProjectId, targetGitProjectId: formData.targetGitProjectId,
targetBranch: formData.targetBranch, targetBranch: formData.targetBranch,
// 运行时配置
runtimeType: formData.runtimeType,
k8sSystemId: formData.k8sSystemId,
k8sNamespaceId: formData.k8sNamespaceId,
k8sDeploymentId: formData.k8sDeploymentId,
dockerServerId: formData.dockerServerId,
dockerContainerName: formData.dockerContainerName,
serverId: formData.serverId,
logQueryCommand: formData.logQueryCommand,
}); });
toast({ toast({
@ -783,6 +867,48 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
{/* 运行时配置 */}
<RuntimeConfigSection
runtimeType={formData.runtimeType}
onRuntimeTypeChange={(type) => {
setFormData({
...formData,
runtimeType: type,
// 切换类型时清空所有运行时配置
k8sSystemId: null,
k8sNamespaceId: null,
k8sDeploymentId: null,
dockerServerId: null,
dockerContainerName: '',
serverId: null,
logQueryCommand: '',
});
}}
k8sSystemId={formData.k8sSystemId}
k8sNamespaceId={formData.k8sNamespaceId}
k8sDeploymentId={formData.k8sDeploymentId}
onK8sSystemChange={(systemId) => setFormData(prev => ({
...prev,
k8sSystemId: systemId,
k8sNamespaceId: null,
k8sDeploymentId: null
}))}
onK8sNamespaceChange={(namespaceId) => setFormData(prev => ({
...prev,
k8sNamespaceId: namespaceId,
k8sDeploymentId: null
}))}
onK8sDeploymentIdChange={(deploymentId) => setFormData(prev => ({ ...prev, k8sDeploymentId: deploymentId }))}
dockerServerId={formData.dockerServerId}
dockerContainerName={formData.dockerContainerName}
onDockerServerChange={(serverId) => setFormData(prev => ({ ...prev, dockerServerId: serverId }))}
onDockerContainerNameChange={(containerName) => setFormData(prev => ({ ...prev, dockerContainerName: containerName }))}
serverId={formData.serverId}
logQueryCommand={formData.logQueryCommand}
onServerChange={(serverId) => setFormData(prev => ({ ...prev, serverId: serverId }))}
onLogQueryCommandChange={(command) => setFormData(prev => ({ ...prev, logQueryCommand: command }))}
/>
</div> </div>
</DialogBody> </DialogBody>

View File

@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button';
import { useToast } from '@/components/ui/use-toast'; import { useToast } from '@/components/ui/use-toast';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { PaginatedTable, type ColumnDef, type PaginatedTableRef } from '@/components/ui/paginated-table'; import { PaginatedTable, type ColumnDef, type PaginatedTableRef } from '@/components/ui/paginated-table';
import { Plus, Edit, Trash2, GitBranch } from 'lucide-react'; import { Plus, Edit, Trash2, GitBranch, Hammer, Box, Container, Server } from 'lucide-react';
import type { Environment } from '@/pages/Deploy/Environment/List/types'; import type { Environment } from '@/pages/Deploy/Environment/List/types';
import type { TeamApplication, Application } from '../types'; import type { TeamApplication, Application } from '../types';
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types'; import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
@ -129,9 +129,18 @@ export const TeamApplicationManageDialog: React.FC<
workflowDefinitionId: number | null; workflowDefinitionId: number | null;
sourceGitSystemId: number | null; sourceGitSystemId: number | null;
sourceGitProjectId: number | null; sourceGitProjectId: number | null;
targetGitSystemId: number | null; // 🆕 目标Git系统ID targetGitSystemId: number | null;
targetGitProjectId: number | null; // 🆕 目标Git项目ID targetGitProjectId: number | null;
targetBranch: string; // 🆕 目标分支 targetBranch: string;
// 运行时配置
runtimeType: 'K8S' | 'DOCKER' | 'SERVER' | null;
k8sSystemId: number | null;
k8sNamespaceId: number | null;
k8sDeploymentId: number | null;
dockerServerId: number | null;
dockerContainerName: string;
serverId: number | null;
logQueryCommand: string;
}) => { }) => {
if (!editingEnvironment) return; if (!editingEnvironment) return;
@ -147,10 +156,19 @@ export const TeamApplicationManageDialog: React.FC<
workflowDefinitionId: data.workflowDefinitionId || undefined, workflowDefinitionId: data.workflowDefinitionId || undefined,
sourceGitSystemId: data.sourceGitSystemId || undefined, sourceGitSystemId: data.sourceGitSystemId || undefined,
sourceGitProjectId: data.sourceGitProjectId || undefined, sourceGitProjectId: data.sourceGitProjectId || undefined,
// 🆕 目标Git相关字段 // 目标Git相关字段
targetGitSystemId: data.targetGitSystemId || undefined, targetGitSystemId: data.targetGitSystemId || undefined,
targetGitProjectId: data.targetGitProjectId || undefined, targetGitProjectId: data.targetGitProjectId || undefined,
targetBranch: data.targetBranch || undefined, targetBranch: data.targetBranch || undefined,
// 运行时配置字段
runtimeType: data.runtimeType || undefined,
k8sSystemId: data.k8sSystemId || undefined,
k8sNamespaceId: data.k8sNamespaceId || undefined,
k8sDeploymentId: data.k8sDeploymentId || undefined,
dockerServerId: data.dockerServerId || undefined,
dockerContainerName: data.dockerContainerName || undefined,
serverId: data.serverId || undefined,
logQueryCommand: data.logQueryCommand || undefined,
}; };
if (appDialogMode === 'edit' && data.id) { if (appDialogMode === 'edit' && data.id) {
@ -295,26 +313,50 @@ export const TeamApplicationManageDialog: React.FC<
}, },
}, },
{ {
key: 'buildType', key: 'buildConfig',
title: '构建类型', title: '构建配置',
width: '120px', width: '250px',
render: (_, app) => render: (_, app) => {
app.buildType === 'JENKINS' ? 'Jenkins构建' : app.buildType === 'NATIVE' ? '脚本部署' : '-', if (!app.buildType) {
return <span className="text-muted-foreground">-</span>;
}
// Jenkins 构建
if (app.buildType === 'JENKINS') {
return (
<div className="space-y-1">
<div className="flex items-center gap-1.5">
<Hammer className="h-3.5 w-3.5 text-blue-500" />
<span className="inline-flex items-center rounded-md bg-blue-50 px-2 py-0.5 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
Jenkins构建
</span>
</div>
<div className="text-xs text-muted-foreground pl-5">
{app.deploySystemName || '-'}
</div>
{app.deployJob && (
<div className="text-xs text-muted-foreground pl-5 truncate" title={app.deployJob}>
{app.deployJob}
</div>
)}
</div>
);
}
// 脚本部署
if (app.buildType === 'NATIVE') {
return (
<div className="flex items-center gap-1.5">
<Hammer className="h-3.5 w-3.5 text-green-500" />
<span className="inline-flex items-center rounded-md bg-green-50 px-2 py-0.5 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
</span>
</div>
);
}
return <span className="text-muted-foreground">-</span>;
}, },
{
key: 'deploySystemName',
title: 'Jenkins系统',
width: '150px',
render: (_, app) =>
app.buildType === 'JENKINS'
? (app.deploySystemName || (app.deploySystemId ? `系统 ${app.deploySystemId}` : '-'))
: '-',
},
{
key: 'deployJob',
title: 'Jenkins Job',
width: '150px',
render: (_, app) => (app.buildType === 'JENKINS' ? (app.deployJob || '-') : '-'),
}, },
{ {
key: 'workflowDefinitionName', key: 'workflowDefinitionName',
@ -323,6 +365,85 @@ export const TeamApplicationManageDialog: React.FC<
width: '180px', width: '180px',
render: (value) => value || '-', render: (value) => value || '-',
}, },
{
key: 'runtimeConfig',
title: '运行时配置',
width: '250px',
render: (_, app) => {
if (!app.runtimeType) {
return <span className="text-muted-foreground">-</span>;
}
// K8S 运行时配置
if (app.runtimeType === 'K8S') {
const k8sPath = [
app.k8sSystemName,
app.k8sNamespaceName,
app.k8sDeploymentName
].filter(Boolean).join(' / ') || '-';
return (
<div className="space-y-1">
<div className="flex items-center gap-1.5">
<Box className="h-3.5 w-3.5 text-purple-500" />
<span className="inline-flex items-center rounded-md bg-purple-50 px-2 py-0.5 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10">
Kubernetes
</span>
</div>
<div className="text-xs text-muted-foreground pl-5 truncate" title={k8sPath}>
{k8sPath}
</div>
</div>
);
}
// Docker 运行时配置
if (app.runtimeType === 'DOCKER') {
return (
<div className="space-y-1">
<div className="flex items-center gap-1.5">
<Container className="h-3.5 w-3.5 text-orange-500" />
<span className="inline-flex items-center rounded-md bg-orange-50 px-2 py-0.5 text-xs font-medium text-orange-700 ring-1 ring-inset ring-orange-700/10">
Docker
</span>
</div>
<div className="text-xs text-muted-foreground pl-5">
{app.dockerServerName || '-'}
</div>
{app.dockerContainerName && (
<div className="text-xs text-muted-foreground pl-5 truncate" title={app.dockerContainerName}>
{app.dockerContainerName}
</div>
)}
</div>
);
}
// Server 运行时配置
if (app.runtimeType === 'SERVER') {
return (
<div className="space-y-1">
<div className="flex items-center gap-1.5">
<Server className="h-3.5 w-3.5 text-gray-500" />
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-700/10">
</span>
</div>
<div className="text-xs text-muted-foreground pl-5">
{app.serverName || '-'}
</div>
{app.logQueryCommand && (
<div className="text-xs text-muted-foreground pl-5 truncate" title={app.logQueryCommand}>
{app.logQueryCommand}
</div>
)}
</div>
);
}
return <span className="text-muted-foreground">-</span>;
},
},
{ {
key: 'actions', key: 'actions',
title: '操作', title: '操作',

View File

@ -172,6 +172,20 @@ export const BUILD_TYPE_OPTIONS = [
{ value: 'NATIVE', label: '脚本部署' }, { value: 'NATIVE', label: '脚本部署' },
] as const; ] as const;
/**
*
*/
export type RuntimeType = 'K8S' | 'DOCKER' | 'SERVER';
/**
*
*/
export const RUNTIME_TYPE_OPTIONS = [
{ value: 'K8S', label: 'Kubernetes' },
{ value: 'DOCKER', label: 'Docker' },
{ value: 'SERVER', label: '服务器' },
] as const;
/** /**
* *
*/ */
@ -189,6 +203,16 @@ export interface TeamApplication extends BaseResponse {
targetGitSystemId?: number; // 目标Git系统ID仅SYNC_MODE targetGitSystemId?: number; // 目标Git系统ID仅SYNC_MODE
targetGitProjectId?: number; // 目标Git项目ID targetGitProjectId?: number; // 目标Git项目ID
targetBranch?: string; // 目标分支名称 targetBranch?: string; // 目标分支名称
// 运行时配置字段
runtimeType?: RuntimeType; // 运行时类型
k8sSystemId?: number; // K8S系统ID
k8sNamespaceId?: number; // K8S命名空间ID
k8sDeploymentId?: number; // K8S Deployment ID
dockerServerId?: number; // Docker服务器ID
dockerContainerName?: string; // Docker容器名称
serverId?: number; // 服务器ID
logQueryCommand?: string; // 日志查询命令Server类型使用
// 关联数据
teamName?: string; teamName?: string;
applicationName?: string; applicationName?: string;
applicationCode?: string; applicationCode?: string;
@ -199,6 +223,12 @@ export interface TeamApplication extends BaseResponse {
sourceGitProjectName?: string; // 源Git项目名称 sourceGitProjectName?: string; // 源Git项目名称
targetGitSystemName?: string; // 目标Git系统名称 targetGitSystemName?: string; // 目标Git系统名称
targetGitProjectName?: string; // 目标Git项目名称 targetGitProjectName?: string; // 目标Git项目名称
// 运行时配置关联数据
k8sSystemName?: string; // K8S系统名称
k8sNamespaceName?: string; // K8S命名空间名称
k8sDeploymentName?: string; // K8S Deployment名称
dockerServerName?: string; // Docker服务器名称
serverName?: string; // 服务器名称
} }
/** /**
@ -218,5 +248,14 @@ export interface TeamApplicationRequest {
targetGitSystemId?: number; // 目标Git系统ID仅SYNC_MODE targetGitSystemId?: number; // 目标Git系统ID仅SYNC_MODE
targetGitProjectId?: number; // 目标Git项目ID targetGitProjectId?: number; // 目标Git项目ID
targetBranch?: string; // 目标分支名称 targetBranch?: string; // 目标分支名称
// 运行时配置字段
runtimeType?: RuntimeType; // 运行时类型
k8sSystemId?: number; // K8S系统ID
k8sNamespaceId?: number; // K8S命名空间ID
k8sDeploymentId?: number; // K8S Deployment ID
dockerServerId?: number; // Docker服务器ID
dockerContainerName?: string; // Docker容器名称
serverId?: number; // 服务器ID
logQueryCommand?: string; // 日志查询命令Server类型使用
} }