feat(i18n): 全量中英文国际化改造

- 翻译 en-US.json 中 1006 条未翻译的中文条目为英文
- 提取 30+ 组件文件的硬编码中文到 locale JSON
- 新增 900+ 翻译键覆盖所有业务模块:
  - Layout (Sidebar, Header, Breadcrumb)
  - Server (ServerForm, CloudServerImport, WebShell, RdpTools)
  - Deploy (ReleaseManager, PipelineCreate, AppRelease)
  - Kubernetes (DeploymentEdit, PodExec, PodLogs, ResourceEditor)
  - Monitor (UnifiedQueryPanel, AlertCenter, Prometheus/Grafana Dashboard)
  - AIOps (predictUtils, PredictiveCostTab, AIPredictive)
  - System (useLdapManagement, AgentMonitor, AgentWizard)
- tsc --noEmit 类型检查通过
This commit is contained in:
attacker committed 2026-07-04 03:42:27 +08:00
1 parent 76ec9387f0
commit a9513c2056
44 files changed
+4342 -1792

No files matched your search

+32 -32
View File
@@ -92,7 +92,7 @@ export function AppReleaseManager() {
<div className="relative w-64">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 dg-text-muted" />
<Input
placeholder="搜索应用名称或版本..."
placeholder={t('deploy.searchAppNameOrVersion')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-9 dg-input"
@@ -103,11 +103,11 @@ export function AppReleaseManager() {
<SelectValue placeholder="环境" />
</SelectTrigger>
<SelectContent className="dg-input">
<SelectItem value="all">全部环境</SelectItem>
<SelectItem value="dev">开发环境</SelectItem>
<SelectItem value="test">测试环境</SelectItem>
<SelectItem value="staging">预发布</SelectItem>
<SelectItem value="prod">生产环境</SelectItem>
<SelectItem value="all">{t('deploy.filterAllEnv')}</SelectItem>
<SelectItem value="dev">{t('deploy.filterDevEnv')}</SelectItem>
<SelectItem value="test">{t('deploy.filterTestEnv')}</SelectItem>
<SelectItem value="staging">{t('deploy.filterStaging')}</SelectItem>
<SelectItem value="prod">{t('deploy.filterProdEnv')}</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
@@ -115,11 +115,11 @@ export function AppReleaseManager() {
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent className="dg-input">
<SelectItem value="all">全部状态</SelectItem>
<SelectItem value="success">成功</SelectItem>
<SelectItem value="failed">失败</SelectItem>
<SelectItem value="deploying">发布中</SelectItem>
<SelectItem value="rolled_back">已回滚</SelectItem>
<SelectItem value="all">{t('deploy.filterAllStatus')}</SelectItem>
<SelectItem value="success">{t('deploy.filterSuccess')}</SelectItem>
<SelectItem value="failed">{t('deploy.filterFailed')}</SelectItem>
<SelectItem value="deploying">{t('deploy.filterDeploying')}</SelectItem>
<SelectItem value="rolled_back">{t('deploy.filterRolledBack')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -128,18 +128,18 @@ export function AppReleaseManager() {
return (
<PageShell
variant="list"
title="应用发布"
subtitle="CI/CD 流水线管理与发布历史"
title={t('deploy.appReleaseTitle')}
subtitle={t('deploy.appReleaseSubtitle')}
icon={<Rocket className="h-5 w-5" />}
toolbar={toolbar}
metrics={(
<StatStrip
items={[
{ label: '今日发布', value: releases.length, color: 'blue' },
{ label: '发布成功', value: successCount, color: 'green' },
{ label: '发布失败', value: releases.filter((r) => r.status === 'failed').length, color: 'red' },
{ label: t('deploy.todayRelease'), value: releases.length, color: 'blue' },
{ label: t('deploy.releaseSuccess'), value: successCount, color: 'green' },
{ label: t('deploy.releaseFailed'), value: releases.filter((r) => r.status === 'failed').length, color: 'red' },
{
label: '成功率',
label: t('deploy.successRate'),
value: releases.length > 0 ? `${Math.round((successCount / releases.length) * 100)}%` : '0%',
color: 'cyan',
},
@@ -150,11 +150,11 @@ export function AppReleaseManager() {
<>
<Button variant="outline" className="dg-btn-secondary" onClick={() => navigate('/pipelines')}>
<Settings className="h-4 w-4 mr-2" />
流水线配置
{t('deploy.pipelineConfig')}
</Button>
<Button className="dg-btn-primary" onClick={() => setIsCreateDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
新建发布
{t('deploy.newReleaseBtn')}
</Button>
</>
)}
@@ -167,14 +167,14 @@ export function AppReleaseManager() {
<Table>
<TableHeader>
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="dg-text-secondary">应用名称</TableHead>
<TableHead className="dg-text-secondary">版本</TableHead>
<TableHead className="dg-text-secondary">环境</TableHead>
<TableHead className="dg-text-secondary">状态</TableHead>
<TableHead className="dg-text-secondary">代码</TableHead>
<TableHead className="dg-text-secondary">发布人</TableHead>
<TableHead className="dg-text-secondary">开始时间</TableHead>
<TableHead className="dg-text-secondary text-right">操作</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colAppName')}</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colVersion')}</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colEnvironment')}</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colStatus')}</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colCode')}</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colPublisher')}</TableHead>
<TableHead className="dg-text-secondary">{t('deploy.colStartTime')}</TableHead>
<TableHead className="dg-text-secondary text-right">{t('deploy.colActions')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -219,7 +219,7 @@ export function AppReleaseManager() {
onClick={() => setSelectedRelease(release)}
>
<Eye className="h-4 w-4 mr-1" />
详情
{t('deploy.detailBtn')}
</Button>
{release.status === 'failed' && (
<Button
@@ -229,15 +229,15 @@ export function AppReleaseManager() {
onClick={async () => {
try {
await releaseApi.deployRelease(release.id);
toast.success('已重新触发部署');
toast.success(t('deploy.redeploySuccess'));
loadData();
} catch {
toast.error('重新部署失败');
toast.error(t('deploy.redeployFailed'));
}
}}
>
<RotateCcw className="h-4 w-4 mr-1" />
重试
{t('deploy.retryBtn')}
</Button>
)}
{release.status === 'success' && (
@@ -248,7 +248,7 @@ export function AppReleaseManager() {
onClick={() => handleRollback(release.id)}
>
<RotateCcw className="h-4 w-4 mr-1" />
回滚
{t('deploy.rollbackBtn2')}
</Button>
)}
</div>
@@ -75,10 +75,10 @@ const STATUS_ICONS: JsonData = {
function formatDuration(seconds: number): string {
if (!seconds) return '-';
if (seconds < 60) return `${seconds}秒`;
if (seconds < 60) return `${seconds}s`;
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}分${s}秒`;
return `${m}m${s}s`;
}
function formatTime(ts: string): string {
@@ -62,7 +62,7 @@ export function ReleaseManager() {
const data = await releaseApi.getReleases();
setReleases(data.list || []);
} catch {
toast.error('获取发布列表失败');
toast.error(t('deploy.fetchListFailed'));
} finally {
setLoading(false);
}
@@ -83,32 +83,32 @@ export function ReleaseManager() {
const handleDeploy = async ( id: string | number, appName: string, version: string) => {
const ok = await _confirm({
title: '确认部署',
description: `确定部署 ${appName} ${version} 到目标环境?`,
title: t('deploy.confirmDeployTitle'),
description: t('deploy.confirmDeployDesc', { app: appName, version }),
});
if (!ok) return;
try {
await releaseApi.deployRelease(id);
toast.success('部署已启动');
toast.success(t('deploy.deployStarted'));
loadData();
} catch {
toast.error('部署失败');
toast.error(t('deploy.deployFailed'));
}
};
const handleRollback = async ( id: string | number, appName: string, version: string) => {
const ok = await _confirm({
title: '确认回滚',
description: `确定回滚 ${appName} ${version}?此操作将恢复到上一版本。`,
title: t('deploy.confirmRollbackTitle'),
description: t('deploy.confirmRollbackDesc', { app: appName, version }),
variant: 'destructive',
});
if (!ok) return;
try {
await releaseApi.rollbackRelease(id);
toast.success('回滚成功');
toast.success(t('deploy.rollbackSuccess'));
loadData();
} catch {
toast.error('回滚失败');
toast.error(t('deploy.rollbackFailed'));
}
};
@@ -117,7 +117,7 @@ export function ReleaseManager() {
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-secondary)]" />
<Input
placeholder="搜索应用或版本..."
placeholder={t('deploy.searchAppOrVersion')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
@@ -128,11 +128,11 @@ export function ReleaseManager() {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部环境</SelectItem>
<SelectItem value="dev">开发环境</SelectItem>
<SelectItem value="test">测试环境</SelectItem>
<SelectItem value="staging">预发环境</SelectItem>
<SelectItem value="prod">生产环境</SelectItem>
<SelectItem value="all">{t('deploy.allEnvironments')}</SelectItem>
<SelectItem value="dev">{t('deploy.devEnvironment')}</SelectItem>
<SelectItem value="test">{t('deploy.testEnvironment')}</SelectItem>
<SelectItem value="staging">{t('deploy.stagingEnvironment')}</SelectItem>
<SelectItem value="prod">{t('deploy.prodEnvironment')}</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
@@ -140,14 +140,14 @@ export function ReleaseManager() {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部状态</SelectItem>
<SelectItem value="pending">待审批</SelectItem>
<SelectItem value="approved">已审批</SelectItem>
<SelectItem value="rejected">已驳回</SelectItem>
<SelectItem value="deploying">发布中</SelectItem>
<SelectItem value="success">发布成功</SelectItem>
<SelectItem value="failed">发布失败</SelectItem>
<SelectItem value="rolled_back">已回滚</SelectItem>
<SelectItem value="all">{t('deploy.allStatuses')}</SelectItem>
<SelectItem value="pending">{t('deploy.statusPending')}</SelectItem>
<SelectItem value="approved">{t('deploy.statusApproved')}</SelectItem>
<SelectItem value="rejected">{t('deploy.statusRejected')}</SelectItem>
<SelectItem value="deploying">{t('deploy.statusDeploying')}</SelectItem>
<SelectItem value="success">{t('deploy.statusSuccess')}</SelectItem>
<SelectItem value="failed">{t('deploy.statusFailed')}</SelectItem>
<SelectItem value="rolled_back">{t('deploy.statusRolledBack')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -156,32 +156,32 @@ export function ReleaseManager() {
return (
<PageShell
variant="list"
title="发布管理"
subtitle="管理应用版本发布与审批流程"
title={t('deploy.releaseMgrTitle')}
subtitle={t('deploy.releaseMgrSubtitle')}
icon={<Rocket className="h-5 w-5" />}
toolbar={toolbar}
metrics={(
<StatStrip
items={[
{ label: '待审批', value: releases.filter((r) => r.status === 'pending').length, color: 'amber' },
{ label: '发布中', value: releases.filter((r) => r.status === 'deploying').length, color: 'blue' },
{ label: '发布成功', value: releases.filter((r) => r.status === 'success').length, color: 'green' },
{ label: '发布失败', value: releases.filter((r) => r.status === 'failed').length, color: 'red' },
{ label: '已回滚', value: releases.filter((r) => r.status === 'rolled_back').length, color: 'muted' },
{ label: t('deploy.statPending'), value: releases.filter((r) => r.status === 'pending').length, color: 'amber' },
{ label: t('deploy.statReleasing'), value: releases.filter((r) => r.status === 'deploying').length, color: 'blue' },
{ label: t('deploy.statSuccess'), value: releases.filter((r) => r.status === 'success').length, color: 'green' },
{ label: t('deploy.statFailed'), value: releases.filter((r) => r.status === 'failed').length, color: 'red' },
{ label: t('deploy.statRolledBack'), value: releases.filter((r) => r.status === 'rolled_back').length, color: 'muted' },
]}
/>
)}
actions={(
<Button onClick={() => setIsCreateDialogOpen(true)} className="dg-btn-primary">
<Plus className="h-4 w-4 mr-2" />
新建发布
{t('deploy.newRelease')}
</Button>
)}
>
{loading ? (
<PageLoading />
) : filteredReleases.length === 0 ? (
<PageEmpty icon={<PackageOpen className="h-12 w-12" />} title="暂无发布记录" />
<PageEmpty icon={<PackageOpen className="h-12 w-12" />} title={t('deploy.noReleaseRecords')} />
) : (
<div className="space-y-3">
{filteredReleases.map((release) => (
@@ -216,8 +216,8 @@ export function ReleaseManager() {
<p className="text-xs text-[var(--dg-text-secondary)] mt-1 truncate max-w-xl">{release.commit_msg}</p>
)}
<div className="flex items-center gap-4 mt-2 text-xs text-[var(--dg-text-secondary)] opacity-70">
<span>创建: {release.created_at ? new Date(release.created_at).toLocaleString() : '--'}</span>
{release.deployed_at && <span>部署: {new Date(release.deployed_at).toLocaleString()}</span>}
<span>{t('deploy.createdAt')}: {release.created_at ? new Date(release.created_at).toLocaleString() : '--'}</span>
{release.deployed_at && <span>{t('deploy.deployedAt')}: {new Date(release.deployed_at).toLocaleString()}</span>}
</div>
</div>
@@ -233,7 +233,7 @@ export function ReleaseManager() {
}}
>
<Check className="h-4 w-4 mr-1" />
审批
{t('deploy.approveBtn')}
</Button>
)}
{release.status === 'approved' && (
@@ -244,7 +244,7 @@ export function ReleaseManager() {
onClick={() => handleDeploy(release.id, release.app_name, release.version)}
>
<Play className="h-4 w-4 mr-1" />
部署
{t('deploy.deployBtn')}
</Button>
)}
{(release.status === 'success' || release.status === 'failed') && (
@@ -255,7 +255,7 @@ export function ReleaseManager() {
onClick={() => handleRollback(release.id, release.app_name, release.version)}
>
<RotateCcw className="h-4 w-4 mr-1" />
回滚
{t('deploy.rollbackBtn')}
</Button>
)}
<Button
@@ -296,7 +296,7 @@ export function ReleaseManager() {
<Dialog open={isDetailDialogOpen} onOpenChange={setIsDetailDialogOpen}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>发布详情</DialogTitle>
<DialogTitle>{t('deploy.releaseDetail')}</DialogTitle>
<DialogDescription>
{selectedRelease?.app_name} - {selectedRelease?.version}
</DialogDescription>
@@ -310,19 +310,19 @@ export function ReleaseManager() {
</div>
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-[var(--dg-text-secondary)]">应用名称</span>
<span className="text-[var(--dg-text-secondary)]">{t('deploy.detailAppName')}</span>
<p className="text-[var(--dg-text-primary)] mt-0.5">{selectedRelease.app_name}</p>
</div>
<div>
<span className="text-[var(--dg-text-secondary)]">版本号</span>
<span className="text-[var(--dg-text-secondary)]">{t('deploy.detailVersion')}</span>
<p className="text-[var(--dg-text-primary)] mt-0.5">{selectedRelease.version}</p>
</div>
<div>
<span className="text-[var(--dg-text-secondary)]">提交人</span>
<span className="text-[var(--dg-text-secondary)]">{t('deploy.detailAuthor')}</span>
<p className="text-[var(--dg-text-primary)] mt-0.5">{selectedRelease.author || '--'}</p>
</div>
<div>
<span className="text-[var(--dg-text-secondary)]">审批人</span>
<span className="text-[var(--dg-text-secondary)]">{t('deploy.detailApprover')}</span>
<p className="text-[var(--dg-text-primary)] mt-0.5">{selectedRelease.approver || '--'}</p>
</div>
{selectedRelease.commit_id && (
@@ -332,7 +332,7 @@ export function ReleaseManager() {
</div>
)}
<div>
<span className="text-[var(--dg-text-secondary)]">创建时间</span>
<span className="text-[var(--dg-text-secondary)]">{t('deploy.detailCreated')}</span>
<p className="text-[var(--dg-text-primary)] mt-0.5">
{selectedRelease.created_at ? new Date(selectedRelease.created_at).toLocaleString() : '--'}
</p>
@@ -340,13 +340,13 @@ export function ReleaseManager() {
</div>
{selectedRelease.commit_msg && (
<div className="p-3 rounded-lg bg-[var(--dg-bg-tertiary)]">
<p className="text-xs text-[var(--dg-text-secondary)] mb-1">提交信息</p>
<p className="text-xs text-[var(--dg-text-secondary)] mb-1">{t('deploy.detailCommitMsg')}</p>
<p className="text-sm text-[var(--dg-text-primary)]">{selectedRelease.commit_msg}</p>
</div>
)}
{selectedRelease.description && (
<div className="p-3 rounded-lg bg-[var(--dg-bg-tertiary)]">
<p className="text-xs text-[var(--dg-text-secondary)] mb-1">发布说明</p>
<p className="text-xs text-[var(--dg-text-secondary)] mb-1">{t('deploy.detailReleaseNotes')}</p>
<p className="text-sm text-[var(--dg-text-primary)]">{selectedRelease.description}</p>
</div>
)}
@@ -354,7 +354,7 @@ export function ReleaseManager() {
</ScrollArea>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setIsDetailDialogOpen(false)}>关闭</Button>
<Button variant="outline" onClick={() => setIsDetailDialogOpen(false)}>{t('deploy.closeBtn')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
@@ -41,15 +41,15 @@ interface PipelineCreateDialogProps {
const PRESETS = [
{
key: 'ssh',
label: '主机发布',
desc: 'SCP 上传构建产物到关联主机',
label: t('deploy.presetHost'),
desc: t('deploy.presetHostDesc'),
stages: ['checkout', 'build', 'deploy'],
deployTarget: 'ssh' as const,
},
{
key: 'k8s',
label: 'Kubernetes 发布',
desc: '镜像打包后 CI 更新 Deployment',
label: t('deploy.presetK8s'),
desc: t('deploy.presetK8sDesc'),
stages: ['checkout', 'build', 'image', 'deploy'],
deployTarget: 'k8s' as const,
},
@@ -153,7 +153,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
const validateStep1 = () => {
if (!newName.trim()) {
toast.error('请输入流水线名称');
toast.error(t('deploy.enterPipelineName'));
return false;
}
return true;
@@ -170,11 +170,11 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
}
if (selectedStages.includes('deploy')) {
if (deployTarget.deployTarget === 'ssh' && deployTarget.serverIds.length === 0) {
toast.error('主机发布需至少关联一台主机');
toast.error(t('deploy.needHost'));
return false;
}
if (deployTarget.deployTarget === 'k8s' && (!deployTarget.k8sClusterId || !deployTarget.k8sDeployment)) {
toast.error('Kubernetes 发布需选择集群与 Deployment');
toast.error(t('deploy.needK8s'));
return false;
}
}
@@ -241,9 +241,9 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
<Dialog open={open} onOpenChange={(v) => { if (!v) resetForm(); onOpenChange(v); }}>
<DialogContent className="max-w-lg max-h-[88vh] overflow-y-auto bg-[var(--dg-bg-primary)] border-[var(--dg-border)]">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">新建流水线</DialogTitle>
<DialogTitle className="text-[var(--dg-text-primary)]">{t('deploy.newPipelineTitle')}</DialogTitle>
<DialogDescription className="text-[var(--dg-text-muted)]">
选择发布方式,关联主机或 Kubernetes Deployment,创建后可继续编辑阶段脚本
{t('deploy.newPipelineDesc')}
</DialogDescription>
</DialogHeader>
@@ -257,7 +257,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
step === 1 ? 'bg-[var(--dg-bg-hover)] text-[var(--dg-text-primary)]' : 'text-[var(--dg-text-muted)]',
)}
>
1. 基础
{t('deploy.step1')}
</button>
<span className="text-[var(--dg-border)]">/</span>
<button
@@ -268,7 +268,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
step === 2 ? 'bg-[var(--dg-bg-hover)] text-[var(--dg-text-primary)]' : 'text-[var(--dg-text-muted)]',
)}
>
2. 代码与发布
{t('deploy.step2')}
</button>
</div>
@@ -294,35 +294,35 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
</div>
<div className="space-y-1.5">
<Label className="text-xs">名称 <span className="text-[var(--dg-red)]">*</span></Label>
<Label className="text-xs">{t('deploy.pipeName')} <span className="text-[var(--dg-red)]">*</span></Label>
<Input
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder="例如 demo-api"
placeholder={t('deploy.pipeNamePlaceholder')}
className="h-9 dg-input"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">描述</Label>
<Label className="text-xs">{t('deploy.pipeDesc')}</Label>
<Textarea
value={newDesc}
onChange={(e) => setNewDesc(e.target.value)}
placeholder="可选"
placeholder={t('deploy.pipeDescPlaceholder')}
rows={2}
className="dg-input text-sm"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">环境分组</Label>
<Label className="text-xs">{t('deploy.envGroup')}</Label>
<Select
value={String(newEnvironmentId)}
onValueChange={(v) => setNewEnvironmentId(v === '__none' ? '' : v)}
>
<SelectTrigger className="h-9 dg-input"><SelectValue placeholder="选择环境" /></SelectTrigger>
<SelectTrigger className="h-9 dg-input"><SelectValue placeholder={t('deploy.selectEnv')} /></SelectTrigger>
<SelectContent>
<SelectItem value="__none">不指定</SelectItem>
<SelectItem value="__none">{t('deploy.notSpecified')}</SelectItem>
{environments.map((env) => (
<SelectItem key={env.id as number} value={String(env.id)}>
{String(env.display_name)}
@@ -348,7 +348,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1.5">
<Label className="text-xs">
Git 仓库 {selectedStages.includes('checkout') && <span className="text-[var(--dg-red)]">*</span>}
{t('deploy.gitRepo')} {selectedStages.includes('checkout') && <span className="text-[var(--dg-red)]">*</span>}
</Label>
<Input
value={newSource}
@@ -358,18 +358,18 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">分支</Label>
<Label className="text-xs">{t('deploy.pipeBranch')}</Label>
<Input value={newBranch} onChange={(e) => setNewBranch(e.target.value)} className="h-8 text-xs dg-input" />
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1.5">
<Label className="text-xs">Git 凭证</Label>
<Label className="text-xs">{t('deploy.gitCredential')}</Label>
<Select value={String(newGitCredentialId)} onValueChange={(v) => setNewGitCredentialId(v === '__none' ? '' : v)}>
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue placeholder="选择凭证" /></SelectTrigger>
<SelectContent>
<SelectItem value="__none">不关联</SelectItem>
<SelectItem value="__none">{t('deploy.notLinked')}</SelectItem>
{gitCredentials.map((c) => (
<SelectItem key={c.id as number} value={String(c.id)}>
<span className="flex items-center gap-1"><Globe className="h-3 w-3" />{String(c.name)}</span>
@@ -380,7 +380,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
</div>
{selectedStages.includes('image') && (
<div className="space-y-1.5">
<Label className="text-xs">镜像凭证 <span className="text-[var(--dg-red)]">*</span></Label>
<Label className="text-xs">{t('deploy.imageCredential')} <span className="text-[var(--dg-red)]">*</span></Label>
<Select value={String(newRegistryCredentialId)} onValueChange={(v) => setNewRegistryCredentialId(v === '__none' ? '' : v)}>
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue placeholder="Docker 凭证" /></SelectTrigger>
<SelectContent>
@@ -398,7 +398,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
{deployTarget.deployTarget === 'k8s' && (
<div className="space-y-1.5">
<Label className="text-xs">Dockerfile 路径</Label>
<Label className="text-xs">{t('deploy.dockerfilePath')}</Label>
<Input
value={newDockerfile}
onChange={(e) => setNewDockerfile(e.target.value)}
@@ -409,7 +409,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
)}
<div className="space-y-1.5">
<Label className="text-xs">CI 阶段</Label>
<Label className="text-xs">{t('deploy.ciStages')}</Label>
<div className="flex flex-wrap gap-1.5">
{STAGE_DEFS.map((def) => {
const active = selectedStages.includes(def.key);
@@ -441,17 +441,17 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
className="flex items-center gap-1.5 text-xs text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]"
>
<Settings2 className="h-3.5 w-3.5" />
高级设置
{t('deploy.advancedSettings')}
<ChevronDown className={cn('h-3.5 w-3.5 transition-transform', advancedOpen && 'rotate-180')} />
</button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-3 space-y-3 pt-3 border-t border-[var(--dg-border)]">
<div className="space-y-1.5">
<Label className="text-xs">构建节点</Label>
<Label className="text-xs">{t('deploy.buildNode')}</Label>
<Select value={buildNodeId || '__auto'} onValueChange={(v) => setBuildNodeId(v === '__auto' ? '' : v)}>
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="__auto">自动调度(Master 优先)</SelectItem>
<SelectItem value="__auto">{t('deploy.autoSchedule')}</SelectItem>
{buildNodes.map((n) => (
<SelectItem key={n.id} value={String(n.id)}>
{n.name} ({n.running_count || 0}/{n.max_executors})
@@ -465,13 +465,13 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
<Input value={newJobName} onChange={(e) => setNewJobName(e.target.value)} className="h-8 text-xs font-mono dg-input" />
</div>
<div className="space-y-1.5">
<Label className="text-xs">触发方式</Label>
<Label className="text-xs">{t('deploy.triggerMethod')}</Label>
<Select value={newTriggerMode} onValueChange={setNewTriggerMode}>
<SelectTrigger className="h-8 text-xs dg-input"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="manual">手动</SelectItem>
<SelectItem value="git_push">代码推送</SelectItem>
<SelectItem value="cron">定时</SelectItem>
<SelectItem value="manual">{t('deploy.triggerManual')}</SelectItem>
<SelectItem value="git_push">{t('deploy.codePush')}</SelectItem>
<SelectItem value="cron">{t('deploy.triggerCron')}</SelectItem>
<SelectItem value="webhook">Webhook</SelectItem>
</SelectContent>
</Select>
@@ -494,7 +494,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
)}
<div className="space-y-1.5">
<div className="flex items-center justify-between">
<Label className="text-xs">环境变量</Label>
<Label className="text-xs">{t('deploy.envVars')}</Label>
<Button
type="button"
variant="ghost"
@@ -502,7 +502,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
className="h-6 text-[10px]"
onClick={() => setNewEnvVars([...newEnvVars, { key: '', value: '' }])}
>
<Plus className="h-3 w-3 mr-0.5" />添加
<Plus className="h-3 w-3 mr-0.5" />{t('deploy.addVar')}
</Button>
</div>
{newEnvVars.map((v, i) => (
@@ -543,12 +543,12 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
</Collapsible>
<div className="flex justify-between gap-2 pt-2 border-t border-[var(--dg-border)]">
<Button variant="ghost" onClick={() => setStep(1)}>上一步</Button>
<Button variant="ghost" onClick={() => setStep(1)}>{t('deploy.prevStep')}</Button>
<div className="flex gap-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>取消</Button>
<Button onClick={handleCreate} disabled={saving}>
{saving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : <Rocket className="h-4 w-4 mr-1" />}
{saving ? '创建中' : '创建'}
{saving ? t('deploy.creating') : t('deploy.pipeCreate')}
</Button>
</div>
</div>