refactor(i18n): U35 Deploy + Task 模块残余 JSX 硬编码中文清理 — PipelineRunPanel 12处 + CronJobTable 12处 + ExecutionHistoryDialog 10处 + ScrollButtons 2处, 新增 54 翻译键(deploy.runStatus/task.cronJobCol/task.execHistory/deploy.credCol/common.*), zh/en 7565→7616 对齐, tsc -b=0, npm build✓ (17.02s), ESLint=0/43/0, go build=0, 17/17 测试通过
This commit is contained in:
1 parent
0dbb85f5b8
commit
8d64936caf
7 files changed
+163
-43
No files matched your search
@@ -1,6 +1,7 @@
|
||||
// ScrollButtons.tsx — 浮动滚动按钮(回到顶部/底部),AgentCLI 对话区复用
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ChevronUp, ChevronDown } from 'lucide-react';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export interface ScrollButtonsProps {
|
||||
show: boolean;
|
||||
@@ -21,7 +22,7 @@ export default function ScrollButtons({ show, atTop, atBottom, onScrollTop, onSc
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full shadow-md border border-[var(--dg-border)] bg-[var(--surface-raised)]/95 opacity-90 hover:opacity-100"
|
||||
onClick={onScrollTop}
|
||||
title="回到顶部"
|
||||
title={i18n.t('common.scrollToTop')}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -33,7 +34,7 @@ export default function ScrollButtons({ show, atTop, atBottom, onScrollTop, onSc
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full shadow-md border border-[var(--dg-border)] bg-[var(--surface-raised)]/95 opacity-90 hover:opacity-100"
|
||||
onClick={onScrollBottom}
|
||||
title="回到底部"
|
||||
title={i18n.t('common.scrollToBottom')}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -74,7 +74,7 @@ function PipelineQueuePanel({ pipeline }: { pipeline: Record<string, unknown> })
|
||||
<span className="font-bold text-[var(--dg-text-primary)]">{running}/{max}</span>
|
||||
{queued > 0 && (
|
||||
<span className="text-[var(--dg-text-secondary)] ml-2">
|
||||
构建队列 <span className="font-bold dg-text-yellow">({queued})</span>
|
||||
{i18n.t('deploy.runStatus.buildQueue')} <span className="font-bold dg-text-yellow">({queued})</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -90,7 +90,7 @@ function PipelineQueuePanel({ pipeline }: { pipeline: Record<string, unknown> })
|
||||
<span className="text-[var(--dg-text-secondary)] opacity-50">{String(run.triggered_by || '-')}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="h-6 text-xs dg-text-red hover:dg-text-red" onClick={() => handleCancel(run.id as number)}>
|
||||
<XCircle className="h-3 w-3 mr-1" />取消
|
||||
<XCircle className="h-3 w-3 mr-1" />{i18n.t('deploy.runStatus.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
@@ -106,7 +106,7 @@ function PipelineQueuePanel({ pipeline }: { pipeline: Record<string, unknown> })
|
||||
<span className="text-[var(--dg-text-muted)] font-mono">Run #{id}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="h-6 text-xs dg-text-red hover:dg-text-red" onClick={() => handleCancel(id)}>
|
||||
<XCircle className="h-3 w-3 mr-1" />取消
|
||||
<XCircle className="h-3 w-3 mr-1" />{i18n.t('deploy.runStatus.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
@@ -163,19 +163,19 @@ export function PipelineRunPanel({ pipeline, pipelineRuns, isLoadingRuns, onStop
|
||||
<td className="px-4 py-2">
|
||||
{r.status === 'success' ? (
|
||||
<Badge className="bg-emerald-500/20 dg-text-green border-emerald-500/30 text-[11px]">
|
||||
<CheckCircle className="h-3 w-3 mr-0.5" />成功
|
||||
<CheckCircle className="h-3 w-3 mr-0.5" />{i18n.t('deploy.runStatus.success')}
|
||||
</Badge>
|
||||
) : r.status === 'failed' ? (
|
||||
<Badge className="bg-red-500/20 dg-text-red border-red-500/30 text-[11px]">
|
||||
<XCircle className="h-3 w-3 mr-0.5" />失败
|
||||
<XCircle className="h-3 w-3 mr-0.5" />{i18n.t('deploy.runStatus.failed')}
|
||||
</Badge>
|
||||
) : r.status === 'running' ? (
|
||||
<Badge className="bg-blue-500/20 dg-text-blue border-blue-500/30 text-[11px]">
|
||||
<Loader2 className="h-3 w-3 animate-spin mr-0.5" />执行中
|
||||
<Loader2 className="h-3 w-3 animate-spin mr-0.5" />{i18n.t('deploy.runStatus.running')}
|
||||
</Badge>
|
||||
) : r.status === 'cancelled' ? (
|
||||
<Badge className="bg-yellow-500/20 dg-text-yellow border-yellow-500/30 text-[11px]">
|
||||
<StopCircle className="h-3 w-3 mr-0.5" />已停止
|
||||
<StopCircle className="h-3 w-3 mr-0.5" />{i18n.t('deploy.runStatus.cancelled')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary" className="text-[11px]">{String(r.status)}</Badge>
|
||||
@@ -189,11 +189,11 @@ export function PipelineRunPanel({ pipeline, pipelineRuns, isLoadingRuns, onStop
|
||||
<td className="px-4 py-2 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-blue hover:text-blue-300" onClick={() => handleViewLog(r)}>
|
||||
<FileText className="h-3.5 w-3.5 mr-1" />日志
|
||||
<FileText className="h-3.5 w-3.5 mr-1" />{i18n.t('deploy.runStatus.log')}
|
||||
</Button>
|
||||
{(r.status === 'running' || r.status === 'pending') && (
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-red hover:dg-text-red" onClick={() => onStopRun(r.id as number | string)}>
|
||||
<StopCircle className="h-3.5 w-3.5 mr-1" />停止
|
||||
<StopCircle className="h-3.5 w-3.5 mr-1" />{i18n.t('deploy.runStatus.stop')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -208,14 +208,14 @@ export function PipelineRunPanel({ pipeline, pipelineRuns, isLoadingRuns, onStop
|
||||
<DialogContent className="max-w-3xl max-h-[85vh] overflow-y-auto bg-[var(--dg-bg-primary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5" />执行日志 #{String(logRun?.build_number ?? logRun?.id ?? '')}
|
||||
<FileText className="h-5 w-5" />{i18n.t('deploy.runStatus.execLog', { number: String(logRun?.build_number ?? logRun?.id ?? '') })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap gap-3 text-sm text-[var(--dg-text-secondary)]">
|
||||
<span>触发方式: {String(logRun?.triggered_by || 'manual')}</span>
|
||||
<span>分支: {String(logRun?.branch || '-')}</span>
|
||||
<span>耗时: {fmtDuration(logRun?.duration as number | undefined)}</span>
|
||||
<span>{i18n.t('deploy.runStatus.triggerMethod')} {String(logRun?.triggered_by || 'manual')}</span>
|
||||
<span>{i18n.t('deploy.runStatus.branch')} {String(logRun?.branch || '-')}</span>
|
||||
<span>{i18n.t('deploy.runStatus.duration')} {fmtDuration(logRun?.duration as number | undefined)}</span>
|
||||
</div>
|
||||
<div className="bg-[var(--dg-bg-primary)] rounded-md p-3 max-h-[50vh] overflow-y-auto font-mono text-xs">
|
||||
{logs.length === 0 ? (
|
||||
|
||||
@@ -48,14 +48,14 @@ export function CronJobTable({
|
||||
className="border-[var(--dg-border)]"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs min-w-[140px]">任务</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[120px] hidden lg:table-cell">调度</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[88px]">类型</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[72px] hidden sm:table-cell">开关</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[72px]">最近</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[100px] hidden md:table-cell">上次执行</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[96px] hidden md:table-cell">成功率</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[148px] text-right">操作</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs min-w-[140px]">{t('task.cronJobCol.task')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[120px] hidden lg:table-cell">{t('task.cronJobCol.schedule')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[88px]">{t('task.cronJobCol.type')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[72px] hidden sm:table-cell">{t('task.cronJobCol.switch')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[72px]">{t('task.cronJobCol.recent')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[100px] hidden md:table-cell">{t('task.cronJobCol.lastExec')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[96px] hidden md:table-cell">{t('task.cronJobCol.successRate')}</TableHead>
|
||||
<TableHead className="dg-text-secondary text-xs w-[148px] text-right">{t('task.cronJobCol.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -122,7 +122,7 @@ export function CronJobTable({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 dg-text-green hover:bg-emerald-500/10"
|
||||
title="立即执行"
|
||||
title={t('task.cronJobCol.execNow')}
|
||||
disabled={isExecuting}
|
||||
onClick={() => onExecute(job.id)}
|
||||
>
|
||||
@@ -136,7 +136,7 @@ export function CronJobTable({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
title="执行历史"
|
||||
title={t('task.cronJobCol.execHistory')}
|
||||
onClick={() => onHistory(job)}
|
||||
>
|
||||
<History className="h-3.5 w-3.5" />
|
||||
@@ -145,7 +145,7 @@ export function CronJobTable({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
title={job.enabled ? '禁用' : '启用'}
|
||||
title={job.enabled ? t('task.cronJobCol.disable') : t('task.cronJobCol.enable')}
|
||||
onClick={() => onToggle(job.id)}
|
||||
>
|
||||
{job.enabled ? <Pause className="h-3.5 w-3.5" /> : <Play className="h-3.5 w-3.5" />}
|
||||
@@ -155,7 +155,7 @@ export function CronJobTable({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
title="编辑"
|
||||
title={t('task.cronJobCol.edit')}
|
||||
onClick={() => onEdit(job)}
|
||||
>
|
||||
<Edit className="h-3.5 w-3.5" />
|
||||
@@ -166,7 +166,7 @@ export function CronJobTable({
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
title="删除"
|
||||
title={t('task.cronJobCol.delete')}
|
||||
onClick={() => onDelete(job)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 dg-text-red" />
|
||||
|
||||
@@ -118,7 +118,7 @@ export function ExecutionHistoryDialog({
|
||||
[executions, viewingExecId],
|
||||
);
|
||||
|
||||
const title = taskName ? `执行历史 — ${taskName}` : '全部执行历史';
|
||||
const title = taskName ? `${t('task.execHistory.title')} — ${taskName}` : t('task.execHistory.title');
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -126,7 +126,7 @@ export function ExecutionHistoryDialog({
|
||||
<DialogHeader className="shrink-0">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription className="text-[var(--dg-text-secondary)]">
|
||||
近 7 天趋势与执行日志(最近 100 条)
|
||||
{t('task.execHistory.recentTrend', { defaultValue: '近 7 天趋势与执行日志(最近 100 条)' })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -137,7 +137,7 @@ export function ExecutionHistoryDialog({
|
||||
) : executions.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-[var(--dg-text-secondary)]">
|
||||
<History className="h-10 w-10 mb-3 opacity-40" />
|
||||
<p className="text-sm">暂无执行记录</p>
|
||||
<p className="text-sm">{t('task.execHistory.noRecords', { defaultValue: '暂无执行记录' })}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 min-h-0 flex-1 overflow-y-auto mt-1">
|
||||
@@ -146,27 +146,27 @@ export function ExecutionHistoryDialog({
|
||||
<CheckCircle className="h-7 w-7 text-emerald-500 shrink-0" />
|
||||
<div>
|
||||
<div className="text-xl font-semibold text-emerald-500 tabular-nums">{stats.success}</div>
|
||||
<div className="text-[11px] dg-text-secondary">成功 · {stats.successRate}%</div>
|
||||
<div className="text-[11px] dg-text-secondary">{t('common.success', { defaultValue: '成功' })} · {stats.successRate}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-red-500/20 bg-red-500/5 px-3 py-2.5 flex items-center gap-2.5">
|
||||
<XCircle className="h-7 w-7 text-red-500 shrink-0" />
|
||||
<div>
|
||||
<div className="text-xl font-semibold text-red-500 tabular-nums">{stats.failed}</div>
|
||||
<div className="text-[11px] dg-text-secondary">失败 · {stats.failedRate}%</div>
|
||||
<div className="text-[11px] dg-text-secondary">{t('common.failed', { defaultValue: '失败' })} · {stats.failedRate}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-2.5 flex items-center gap-2.5">
|
||||
<BarChart3 className="h-7 w-7 dg-text-blue shrink-0" />
|
||||
<div>
|
||||
<div className="text-xl font-semibold tabular-nums">{stats.total}</div>
|
||||
<div className="text-[11px] dg-text-secondary">总计</div>
|
||||
<div className="text-[11px] dg-text-secondary">{t('common.total', { defaultValue: '总计' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-3 shrink-0">
|
||||
<div className="text-xs font-medium mb-2 dg-text-secondary">最近 7 天</div>
|
||||
<div className="text-xs font-medium mb-2 dg-text-secondary">{t('task.execHistory.last7days', { defaultValue: '最近 7 天' })}</div>
|
||||
<div className="flex items-end justify-between gap-1.5 h-24">
|
||||
{trend.map((item) => {
|
||||
const heightPct = item.total > 0 ? Math.max((item.total / trendMax) * 100, 12) : 4;
|
||||
@@ -197,10 +197,10 @@ export function ExecutionHistoryDialog({
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] hover:bg-[var(--dg-bg-secondary)]">
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs w-8" />
|
||||
{!taskId && <TableHead className="text-[var(--dg-text-secondary)] text-xs">任务</TableHead>}
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs w-[72px]">状态</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs w-[80px]">耗时</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs">执行时间</TableHead>
|
||||
{!taskId && <TableHead className="text-[var(--dg-text-secondary)] text-xs">{t('task.cronJobCol.task')}</TableHead>}
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs w-[72px]">{t('task.execHistory.status')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs w-[80px]">{t('task.execHistory.duration')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-xs">{t('task.execHistory.startTime')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -256,7 +256,7 @@ export function ExecutionHistoryDialog({
|
||||
</div>
|
||||
|
||||
{viewingExec && !viewingExec.output && (
|
||||
<p className="text-xs dg-text-muted text-center py-2">该记录无输出日志</p>
|
||||
<p className="text-xs dg-text-muted text-center py-2">{t('task.execHistory.noOutput')}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user