From 2f8113cb8e82bc8f4ef9a6a589d39fd9b2c5c524 Mon Sep 17 00:00:00 2001 From: attacker Date: Thu, 9 Jul 2026 01:19:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(quality):=20Q25=20=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=B7=A8=E5=9E=8B=E7=BB=84=E4=BB=B6=E6=8B=86=E5=88=86=20?= =?UTF-8?q?=E2=80=94=20PipelineCreateDialog=20559L=E2=86=92498L=20(-11%),?= =?UTF-8?q?=20=E6=8F=90=E5=8F=96=20pipeline-create/=202=20=E4=B8=AA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20(types(PRESETS/DEFAULT=5FTARGET/PipelineCr?= =?UTF-8?q?eateDialogProps/EnvVar)=20+=20AdvancedSettingsPanel(172L=20?= =?UTF-8?q?=E5=8F=97=E6=8E=A7=E5=B1=95=E5=BC=80=E9=9D=A2=E6=9D=BF:=20?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=8A=82=E7=82=B9/Job=E5=90=8D/=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E6=A8=A1=E5=BC=8F/=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F)),=20=E9=9B=B6=E9=80=BB=E8=BE=91=E5=8F=98=E6=9B=B4,?= =?UTF-8?q?=20ESLint=20errors=3D0/warnings=3D0/any=3D0,=20tsc=20-b=3D0,=20?= =?UTF-8?q?npm=20build=E2=9C=93=20(17.45s),=2017/17=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pipeline-manager/PipelineCreateDialog.tsx | 328 +++++++----------- .../pipeline-create/AdvancedSettingsPanel.tsx | 172 +++++++++ .../pipeline-manager/pipeline-create/types.ts | 48 +++ 3 files changed, 353 insertions(+), 195 deletions(-) create mode 100644 frontend/src/components/deploy/pipeline-manager/pipeline-create/AdvancedSettingsPanel.tsx create mode 100644 frontend/src/components/deploy/pipeline-manager/pipeline-create/types.ts diff --git a/frontend/src/components/deploy/pipeline-manager/PipelineCreateDialog.tsx b/frontend/src/components/deploy/pipeline-manager/PipelineCreateDialog.tsx index 8e0b03a..0f142c4 100644 --- a/frontend/src/components/deploy/pipeline-manager/PipelineCreateDialog.tsx +++ b/frontend/src/components/deploy/pipeline-manager/PipelineCreateDialog.tsx @@ -13,60 +13,17 @@ import { import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import { - Rocket, - Plus, - GitBranch, - Globe, - Container, - Loader2, - Trash2, - ChevronDown, - Settings2, -} from 'lucide-react'; +import { Rocket, GitBranch, Globe, Container, Loader2 } from 'lucide-react'; import { buildNodeApi, credentialApi, deployApi, pipelineApi, type BuildNode } from '@/api'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; import { nameToCode } from '../pipeline-helpers'; import { STAGE_DEFS } from './constants'; import { DeployTargetFields, type DeployTargetValue } from './DeployTargetFields'; +import { PRESETS, DEFAULT_TARGET, type PipelineCreateDialogProps, type EnvVar } from './pipeline-create/types'; +import { AdvancedSettingsPanel } from './pipeline-create/AdvancedSettingsPanel'; -interface PipelineCreateDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onCreated: () => void; -} - -const PRESETS = [ - { - key: 'ssh', - labelKey: 'deploy.presetHost', - descKey: 'deploy.presetHostDesc', - stages: ['checkout', 'build', 'deploy'], - deployTarget: 'ssh' as const, - }, - { - key: 'k8s', - labelKey: 'deploy.presetK8s', - descKey: 'deploy.presetK8sDesc', - stages: ['checkout', 'build', 'image', 'deploy'], - deployTarget: 'k8s' as const, - }, -]; - -const DEFAULT_TARGET: DeployTargetValue = { - deployTarget: 'ssh', - serverIds: [], - deployRemotePath: '', - deployScript: '', - k8sClusterId: '', - k8sNamespace: '', - k8sDeployment: '', - k8sContainerName: '', -}; - -// 新建流水线对话框 — 精简分步 + 双发布目标 // Logan.Li +// 新建流水线对话框 — 精简分步 + 双发布目标 export function PipelineCreateDialog({ open, onOpenChange, onCreated }: PipelineCreateDialogProps) { const { t } = useTranslation(); const [step, setStep] = useState<1 | 2>(1); @@ -91,7 +48,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline const [newTriggerMode, setNewTriggerMode] = useState('manual'); const [newWatchBranches, setNewWatchBranches] = useState(''); const [newCronExpr, setNewCronExpr] = useState(''); - const [newEnvVars, setNewEnvVars] = useState<{ key: string; value: string }[]>([]); + const [newEnvVars, setNewEnvVars] = useState([]); const [environments, setEnvironments] = useState[]>([]); const [buildNodes, setBuildNodes] = useState([]); const [buildNodeId, setBuildNodeId] = useState(''); @@ -103,15 +60,20 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline credentialApi.list({ page_size: 100 }).catch(() => ({ list: [] })), buildNodeApi.list().catch(() => ({ list: [] })), ] as const); - setEnvironments(Array.isArray((rEnv as Record)?.list) - ? (rEnv as Record).list as Record[] - : []); - const allCreds: Record[] = (rCred as Record)?.list as Record[] || []; + setEnvironments( + Array.isArray((rEnv as Record)?.list) + ? ((rEnv as Record).list as Record[]) + : [], + ); + const allCreds: Record[] = + ((rCred as Record)?.list as Record[]) || []; setGitCredentials(allCreds.filter((c) => c.secret_type === 'api')); setDockerCredentials(allCreds.filter((c) => c.secret_type === 'docker')); const nodeData = rNodes as { list?: BuildNode[] }; setBuildNodes(Array.isArray(nodeData?.list) ? nodeData.list.filter((n) => n.enabled) : []); - } catch { /* silent */ } + } catch { + /* silent */ + } }, []); useEffect(() => { @@ -186,12 +148,14 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline setSaving(true); try { const stageNames = selectedStages.map((k) => STAGE_DEFS.find((s) => s.key === k)?.label || k); - const stages = JSON.stringify(stageNames.map((name, i) => ({ - id: `stage-${i + 1}`, - name, - type: selectedStages[i] || 'custom', - tasks: [], - }))); + const stages = JSON.stringify( + stageNames.map((name, i) => ({ + id: `stage-${i + 1}`, + name, + type: selectedStages[i] || 'custom', + tasks: [], + })), + ); await pipelineApi.createPipeline({ name: newName, code: nameToCode(newName), @@ -204,7 +168,8 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline stages, deploy_mode: 'branch', deploy_target: deployTarget.deployTarget, - server_ids: deployTarget.serverIds.length > 0 ? JSON.stringify(deployTarget.serverIds) : undefined, + server_ids: + deployTarget.serverIds.length > 0 ? JSON.stringify(deployTarget.serverIds) : undefined, deploy_remote_path: deployTarget.deployRemotePath || undefined, deploy_script: deployTarget.deployScript || undefined, k8s_cluster_id: deployTarget.k8sClusterId ? Number(deployTarget.k8sClusterId) : null, @@ -232,16 +197,22 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline }; const toggleStage = (key: string) => { - setSelectedStages((prev) => ( - prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key] - )); + setSelectedStages((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key])); }; return ( - { if (!v) resetForm(); onOpenChange(v); }}> + { + if (!v) resetForm(); + onOpenChange(v); + }} + > - {t('deploy.newPipelineTitle')} + + {t('deploy.newPipelineTitle')} + {t('deploy.newPipelineDesc')} @@ -254,7 +225,9 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline onClick={() => setStep(1)} className={cn( 'px-2.5 py-1 rounded-md transition-colors', - step === 1 ? 'bg-[var(--dg-bg-hover)] text-[var(--dg-text-primary)]' : 'text-[var(--dg-text-muted)]', + step === 1 + ? 'bg-[var(--dg-bg-hover)] text-[var(--dg-text-primary)]' + : 'text-[var(--dg-text-muted)]', )} > {t('deploy.step1')} @@ -262,10 +235,14 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline / ))}
- + setNewName(e.target.value)} @@ -320,7 +303,9 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline value={String(newEnvironmentId)} onValueChange={(v) => setNewEnvironmentId(v === '__none' ? '' : v)} > - + + + {t('deploy.notSpecified')} {environments.map((env) => ( @@ -333,8 +318,16 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
- - + +
) : ( @@ -348,7 +341,10 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
- setNewBranch(e.target.value)} className="h-8 text-xs dg-input" /> + setNewBranch(e.target.value)} + className="h-8 text-xs dg-input" + />
- setNewGitCredentialId(v === '__none' ? '' : v)} + > + + + {t('deploy.notLinked')} {gitCredentials.map((c) => ( - {String(c.name)} + + + {String(c.name)} + ))} @@ -380,14 +388,24 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
{selectedStages.includes('image') && (
- - setNewRegistryCredentialId(v === '__none' ? '' : v)} + > + + + 不关联 {dockerCredentials.map((c) => ( - {String(c.name)} + + + {String(c.name)} + ))} @@ -429,125 +447,45 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline {selectedStages.length > 0 && (

- {selectedStages.map((k) => STAGE_DEFS.find((d) => d.key === k)?.label || k).join(' → ')} + {selectedStages + .map((k) => STAGE_DEFS.find((d) => d.key === k)?.label || k) + .join(' → ')}

)}
- - - - - -
- - -
-
- - setNewJobName(e.target.value)} className="h-8 text-xs font-mono dg-input" /> -
-
- - -
- {newTriggerMode === 'git_push' && ( - setNewWatchBranches(e.target.value)} - placeholder="监听分支,如 main, release/*" - className="h-8 text-xs font-mono dg-input" - /> - )} - {newTriggerMode === 'cron' && ( - setNewCronExpr(e.target.value)} - placeholder="0 2 * * *" - className="h-8 text-xs font-mono dg-input" - /> - )} -
-
- - -
- {newEnvVars.map((v, i) => ( -
- { - const nv = [...newEnvVars]; - nv[i] = { ...nv[i], key: e.target.value }; - setNewEnvVars(nv); - }} - placeholder="KEY" - className="h-7 text-xs font-mono dg-input flex-1" - /> - { - const nv = [...newEnvVars]; - nv[i] = { ...nv[i], value: e.target.value }; - setNewEnvVars(nv); - }} - placeholder="value" - className="h-7 text-xs font-mono dg-input flex-[2]" - /> - -
- ))} -
-
-
+
- +
- +
@@ -557,4 +495,4 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
); -} \ No newline at end of file +} diff --git a/frontend/src/components/deploy/pipeline-manager/pipeline-create/AdvancedSettingsPanel.tsx b/frontend/src/components/deploy/pipeline-manager/pipeline-create/AdvancedSettingsPanel.tsx new file mode 100644 index 0000000..215262d --- /dev/null +++ b/frontend/src/components/deploy/pipeline-manager/pipeline-create/AdvancedSettingsPanel.tsx @@ -0,0 +1,172 @@ +import { useTranslation } from 'react-i18next'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; +import { Plus, ChevronDown, Settings2, Trash2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import type { BuildNode } from '@/api'; +import type { EnvVar } from './types'; + +export interface AdvancedSettingsPanelProps { + open: boolean; + onOpenChange: (open: boolean) => void; + buildNodeId: string; + onBuildNodeIdChange: (v: string) => void; + buildNodes: BuildNode[]; + jobName: string; + onJobNameChange: (v: string) => void; + triggerMode: string; + onTriggerModeChange: (v: string) => void; + watchBranches: string; + onWatchBranchesChange: (v: string) => void; + cronExpr: string; + onCronExprChange: (v: string) => void; + envVars: EnvVar[]; + onEnvVarsChange: (vars: EnvVar[]) => void; +} + +export function AdvancedSettingsPanel({ + open, + onOpenChange, + buildNodeId, + onBuildNodeIdChange, + buildNodes, + jobName, + onJobNameChange, + triggerMode, + onTriggerModeChange, + watchBranches, + onWatchBranchesChange, + cronExpr, + onCronExprChange, + envVars, + onEnvVarsChange, +}: AdvancedSettingsPanelProps) { + const { t } = useTranslation(); + + return ( + + + + + +
+ + +
+
+ + onJobNameChange(e.target.value)} + className="h-8 text-xs font-mono dg-input" + /> +
+
+ + +
+ {triggerMode === 'git_push' && ( + onWatchBranchesChange(e.target.value)} + placeholder="监听分支,如 main, release/*" + className="h-8 text-xs font-mono dg-input" + /> + )} + {triggerMode === 'cron' && ( + onCronExprChange(e.target.value)} + placeholder="0 2 * * *" + className="h-8 text-xs font-mono dg-input" + /> + )} +
+
+ + +
+ {envVars.map((v, i) => ( +
+ { + const nv = [...envVars]; + nv[i] = { ...nv[i], key: e.target.value }; + onEnvVarsChange(nv); + }} + placeholder="KEY" + className="h-7 text-xs font-mono dg-input flex-1" + /> + { + const nv = [...envVars]; + nv[i] = { ...nv[i], value: e.target.value }; + onEnvVarsChange(nv); + }} + placeholder="value" + className="h-7 text-xs font-mono dg-input flex-[2]" + /> + +
+ ))} +
+
+
+ ); +} diff --git a/frontend/src/components/deploy/pipeline-manager/pipeline-create/types.ts b/frontend/src/components/deploy/pipeline-manager/pipeline-create/types.ts new file mode 100644 index 0000000..bb0848a --- /dev/null +++ b/frontend/src/components/deploy/pipeline-manager/pipeline-create/types.ts @@ -0,0 +1,48 @@ +import type { DeployTargetValue } from '../DeployTargetFields'; + +export interface PipelineCreateDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onCreated: () => void; +} + +export interface Preset { + key: string; + labelKey: string; + descKey: string; + stages: string[]; + deployTarget: 'ssh' | 'k8s'; +} + +export const PRESETS: Preset[] = [ + { + key: 'ssh', + labelKey: 'deploy.presetHost', + descKey: 'deploy.presetHostDesc', + stages: ['checkout', 'build', 'deploy'], + deployTarget: 'ssh', + }, + { + key: 'k8s', + labelKey: 'deploy.presetK8s', + descKey: 'deploy.presetK8sDesc', + stages: ['checkout', 'build', 'image', 'deploy'], + deployTarget: 'k8s', + }, +]; + +export const DEFAULT_TARGET: DeployTargetValue = { + deployTarget: 'ssh', + serverIds: [], + deployRemotePath: '', + deployScript: '', + k8sClusterId: '', + k8sNamespace: '', + k8sDeployment: '', + k8sContainerName: '', +}; + +export interface EnvVar { + key: string; + value: string; +}