feat(i18n): 增量国际化迁移 — deploy/kubernetes/monitor/task 全模块 + 补全 28 缺失翻译键
迁移范围: - deploy 模块 (PipelineEditor/PipelineDetailDialog/PipelineFormDialog/PipelineRunDialog/ReleaseCreateDialog/ReleaseDetailDialog/ReleaseManager/ToolConfigTab/JobRoleCreateDialog 等 14 文件) - kubernetes 模块 (YamlEditorToolbar/DeploymentHpaTab/DeploymentLogsTab/DeploymentPodsTab/DnsHostSection/SchedulingSection/EventsTab/HPACronHPATab/HistoryTab/PodsTab/ServicesTab/ActionModals 等 12 文件) - monitor 模块 (AlertEventsTab/AlertCenterDetail/DatasourcePage/LogAlertBell/LogCenter/LogCenterArchBanner/StatChartPanel/LogRoleCreateDialog 等 8 文件) - task 模块 (CronJobStatsPanel/CronJobTable/CronJobViewDialog/ExecutionHistoryDialog 4 文件) - layout (PageEmpty/PageLoading 2 文件) - site-monitor/snmp-monitor 模块 (6 文件) 翻译键: 3975 → 4003 (+28 缺失键补全), 22 命名空间, zh-CN/en-US 完全对齐 新增命名空间: agent/aiops/cloud/kubernetes/monitor/pipeline/rbac/system/task/ui 验证: go build✅ tsc -b=0 npm build✅ (20.78s) 17/17 集成测试通过
This commit is contained in:
1 parent
eddf01dd79
commit
63e4c220d4
59 files changed
+6713
-641
No files matched your search
@@ -1,4 +1,5 @@
|
||||
// YAML 查看/编辑统一工具栏:复制 · 下载 · 编辑 · 保存 // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Copy, Check, Save, Loader2, Pencil, Download } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -23,29 +24,30 @@ export function YamlEditorToolbar({
|
||||
readOnly = false, showRawToggle = true,
|
||||
onCopy, onDownload, onEdit, onCancel, onSave, onToggleRaw,
|
||||
}: YamlEditorToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const sz = compact ? 'h-7 text-xs' : 'h-8';
|
||||
return (
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Button variant="outline" size="sm" className={`${sz} border-[var(--dg-border)] text-[var(--dg-text-secondary)]`} onClick={onCopy}>
|
||||
{copied ? <Check className="h-3 w-3 mr-1 dg-text-green" /> : <Copy className="h-3 w-3 mr-1" />}
|
||||
{copied ? '已复制' : '复制'}
|
||||
{copied ? t('common.copied') : t('common.copy')}
|
||||
</Button>
|
||||
{onDownload && (
|
||||
<Button variant="outline" size="sm" className={`${sz} border-[var(--dg-border)] text-[var(--dg-text-secondary)]`} onClick={onDownload}>
|
||||
<Download className="h-3 w-3 mr-1" />下载
|
||||
<Download className="h-3 w-3 mr-1" />{t('common.download')}
|
||||
</Button>
|
||||
)}
|
||||
{!readOnly && (
|
||||
!editing ? (
|
||||
<Button variant="outline" size="sm" className={`${sz} border-[var(--dg-border)] text-[var(--dg-text-secondary)]`} onClick={onEdit}>
|
||||
<Pencil className="h-3 w-3 mr-1" />编辑
|
||||
<Pencil className="h-3 w-3 mr-1" />{t('common.edit')}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="outline" size="sm" className={`${sz} border-[var(--dg-border)] text-[var(--dg-text-secondary)]`} onClick={onCancel}>取消</Button>
|
||||
<Button variant="outline" size="sm" className={`${sz} border-[var(--dg-border)] text-[var(--dg-text-secondary)]`} onClick={onCancel}>{t('common.cancel')}</Button>
|
||||
<Button size="sm" className={`${sz} bg-blue-600 hover:bg-blue-700`} onClick={onSave} disabled={saving}>
|
||||
{saving ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <Save className="h-3 w-3 mr-1" />}
|
||||
保存
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
@@ -56,9 +58,9 @@ export function YamlEditorToolbar({
|
||||
onClick={onToggleRaw}
|
||||
className="text-[11px] dg-text-muted hover:text-[var(--dg-text-secondary)] underline-offset-2 hover:underline"
|
||||
>
|
||||
{showRaw ? '精简视图' : '原始 YAML'}
|
||||
{showRaw ? t('k8s.yamlToolbar.compactView') : t('k8s.yamlToolbar.rawYaml')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// DeploymentHpaTab.tsx — Deployment 详情 HPA Tab // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Gauge, Trash2 } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -26,12 +27,13 @@ export function DeploymentHpaTab({
|
||||
onCreated,
|
||||
onDeleteHpa,
|
||||
}: DeploymentHpaTabProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TabsContent value="hpa">
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm dg-text-secondary">HPA 自动扩缩容</CardTitle>
|
||||
<CardTitle className="text-sm dg-text-secondary">{t('k8s.deploymentDetail.hpaTitle')}</CardTitle>
|
||||
<CreateHPADialog
|
||||
clusterCode={clusterCode}
|
||||
namespace={namespace}
|
||||
@@ -44,18 +46,18 @@ export function DeploymentHpaTab({
|
||||
{hpas.length === 0 ? (
|
||||
<div className="text-center py-12 dg-text-muted">
|
||||
<Gauge className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||
<p>未配置 HPA</p>
|
||||
<p className="text-xs mt-1">点击右上角「创建 HPA」设置自动扩缩容</p>
|
||||
<p>{t('k8s.deploymentDetail.noHpa')}</p>
|
||||
<p className="text-xs mt-1">{t('k8s.deploymentDetail.createHpaHint')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<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">{t('k8s.column.name')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.hpaCronHpa.col.minMax')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.hpaCronHpa.col.metrics')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.deploymentDetail.currentTarget')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('common.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -74,7 +76,7 @@ export function DeploymentHpaTab({
|
||||
<TableCell className="text-[var(--dg-text-secondary)]">{hpa.current_replicas || 0}</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-red hover:dg-text-red"
|
||||
onClick={() => onDeleteHpa(hpa)} title="删除 HPA">
|
||||
onClick={() => onDeleteHpa(hpa)} title={t('k8s.deploymentDetail.deleteHpa')}>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
@@ -88,4 +90,4 @@ export function DeploymentHpaTab({
|
||||
</Card>
|
||||
</TabsContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// DeploymentLogsTab.tsx — Deployment 详情日志 Tab // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FileText, ExternalLink } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -31,19 +32,20 @@ export function DeploymentLogsTab({
|
||||
onLogsTabPodChange,
|
||||
onOpenLogsViewer,
|
||||
}: DeploymentLogsTabProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TabsContent value="logs">
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm dg-text-secondary">Pod 日志</CardTitle>
|
||||
<CardTitle className="text-sm dg-text-secondary">{t('k8s.deploymentDetail.podLogs')}</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs dg-text-muted">最近</span>
|
||||
<span className="text-xs dg-text-muted">{t('k8s.deploymentDetail.recent')}</span>
|
||||
<Input
|
||||
type="number" value={logTail} onChange={(e) => onLogTailChange(Number(e.target.value))}
|
||||
className="w-20 h-7 dg-input text-xs"
|
||||
/>
|
||||
<span className="text-xs dg-text-muted">行</span>
|
||||
<span className="text-xs dg-text-muted">{t('k8s.deploymentDetail.lines')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -51,15 +53,15 @@ export function DeploymentLogsTab({
|
||||
{pods.length === 0 ? (
|
||||
<div className="text-center py-12 dg-text-muted">
|
||||
<FileText className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||
<p>暂无 Pod</p>
|
||||
<p>{t('k8s.deploymentDetail.noPod')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-3 py-10 justify-center flex-wrap">
|
||||
<FileText className="h-5 w-5 dg-text-blue" />
|
||||
<span className="text-sm dg-text-secondary">选择 Pod 打开增强版日志查看器</span>
|
||||
<span className="text-sm dg-text-secondary">{t('k8s.deploymentDetail.selectPodHint')}</span>
|
||||
<Select value={logsTabPod} onValueChange={onLogsTabPodChange}>
|
||||
<SelectTrigger className="w-[280px] h-8 dg-input text-xs">
|
||||
<SelectValue placeholder="选择 Pod…" />
|
||||
<SelectValue placeholder={t('k8s.deploymentDetail.selectPodPlaceholder')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pods.map((p: JsonData) => (
|
||||
@@ -76,10 +78,10 @@ export function DeploymentLogsTab({
|
||||
onOpenLogsViewer(logsTabPod, pod ? extractContainers(pod) : []);
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-1" />打开日志
|
||||
<ExternalLink className="h-3 w-3 mr-1" />{t('k8s.deploymentDetail.openLogs')}
|
||||
</Button>
|
||||
<span className="text-xs dg-text-muted block w-full text-center mt-2">
|
||||
支持实时跟踪、多容器切换、previous 日志、关键字过滤、导出
|
||||
{t('k8s.deploymentDetail.logsFeatures')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -87,4 +89,4 @@ export function DeploymentLogsTab({
|
||||
</Card>
|
||||
</TabsContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// DeploymentPodsTab.tsx — Deployment 详情 Pod 列表 Tab // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FileText, Terminal, FolderOpen, FileCode } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -20,6 +21,7 @@ export interface DeploymentPodsTabProps {
|
||||
}
|
||||
|
||||
export function DeploymentPodsTab({ pods, onOpenLogsViewer, onOpenExec, onOpenFileBrowser, onOpenYaml }: DeploymentPodsTabProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TabsContent value="pods">
|
||||
<Card className="dg-card">
|
||||
@@ -27,19 +29,19 @@ export function DeploymentPodsTab({ pods, onOpenLogsViewer, onOpenExec, onOpenFi
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
|
||||
<TableHead className="dg-text-secondary">Pod 名称</TableHead>
|
||||
<TableHead className="dg-text-secondary">状态</TableHead>
|
||||
<TableHead className="dg-text-secondary">节点</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.deploymentDetail.podName')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.column.status')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.column.node')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">IP</TableHead>
|
||||
<TableHead className="dg-text-secondary">重启</TableHead>
|
||||
<TableHead className="dg-text-secondary">操作</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('k8s.deploymentDetail.restart')}</TableHead>
|
||||
<TableHead className="dg-text-secondary">{t('common.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pods.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center dg-text-muted py-8">
|
||||
暂无 Pod
|
||||
{t('k8s.deploymentDetail.noPod')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -61,22 +63,22 @@ export function DeploymentPodsTab({ pods, onOpenLogsViewer, onOpenExec, onOpenFi
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:dg-text-blue"
|
||||
title="查看日志"
|
||||
title={t('k8s.deploymentDetail.viewLogs')}
|
||||
onClick={() => onOpenLogsViewer({ name: pod.name, containers: extractContainers(pod) })}>
|
||||
<FileText className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:dg-text-green"
|
||||
title="exec 终端"
|
||||
title={t('k8s.deploymentDetail.execTerminal')}
|
||||
onClick={() => onOpenExec({ name: pod.name, containers: extractContainers(pod) })}>
|
||||
<Terminal className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:dg-text-yellow"
|
||||
title="文件浏览器"
|
||||
title={t('k8s.deploymentDetail.fileBrowser')}
|
||||
onClick={() => onOpenFileBrowser({ name: pod.name, containers: extractContainers(pod) })}>
|
||||
<FolderOpen className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-secondary hover:dg-text-blue"
|
||||
title="查看 YAML"
|
||||
title={t('k8s.deploymentDetail.viewYaml')}
|
||||
onClick={() => onOpenYaml(pod.name)}>
|
||||
<FileCode className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
@@ -91,4 +93,4 @@ export function DeploymentPodsTab({ pods, onOpenLogsViewer, onOpenExec, onOpenFi
|
||||
</Card>
|
||||
</TabsContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// DnsHostSection.tsx — DNS 配置 + 主机别名 // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Globe, MapPin, Plus, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -36,6 +37,7 @@ export function DnsHostSection({
|
||||
hostAliases,
|
||||
onHostAliasesChange,
|
||||
}: DnsHostSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const addDnsOption = () => onDnsOptionsChange([...dnsOptions, { name: '', value: '' }]);
|
||||
const updateDnsOption = (index: number, patch: Partial<DnsOptionEntry>) =>
|
||||
onDnsOptionsChange(dnsOptions.map((o, i) => (i === index ? { ...o, ...patch } : o)));
|
||||
@@ -53,16 +55,16 @@ export function DnsHostSection({
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Globe className="h-4 w-4 text-indigo-400" />DNS 配置 (dnsConfig)
|
||||
<Globe className="h-4 w-4 text-indigo-400" />{t('k8s.dnsHost.dnsConfigTitle')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3">
|
||||
<p className="text-[11px] dg-text-muted">
|
||||
提示:dnsConfig 通常与 <code className="dg-text-secondary">dnsPolicy: None</code> 搭配使用。每行一个值。
|
||||
{t('k8s.dnsHost.hintPrefix')} <code className="dg-text-secondary">dnsPolicy: None</code> {t('k8s.dnsHost.hintSuffix')}
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">nameservers(每行一个 IP)</Label>
|
||||
<Label className="dg-text-muted text-xs">{t('k8s.dnsHost.nameservers')}</Label>
|
||||
<textarea
|
||||
value={dnsNameservers}
|
||||
onChange={e => onDnsNameserversChange(e.target.value)}
|
||||
@@ -72,7 +74,7 @@ export function DnsHostSection({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="dg-text-muted text-xs">searches(搜索域,每行一个)</Label>
|
||||
<Label className="dg-text-muted text-xs">{t('k8s.dnsHost.searches')}</Label>
|
||||
<textarea
|
||||
value={dnsSearches}
|
||||
onChange={e => onDnsSearchesChange(e.target.value)}
|
||||
@@ -84,13 +86,13 @@ export function DnsHostSection({
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<Label className="dg-text-secondary text-xs">options(DNS 选项)</Label>
|
||||
<Label className="dg-text-secondary text-xs">{t('k8s.dnsHost.options')}</Label>
|
||||
<Button size="sm" variant="ghost" className="h-6 text-xs dg-text-secondary" onClick={addDnsOption}>
|
||||
<Plus className="h-3 w-3 mr-1" />添加
|
||||
<Plus className="h-3 w-3 mr-1" />{t('common.add')}
|
||||
</Button>
|
||||
</div>
|
||||
{dnsOptions.length === 0 ? (
|
||||
<p className="text-xs dg-text-muted italic">未配置</p>
|
||||
<p className="text-xs dg-text-muted italic">{t('k8s.dnsHost.notConfigured')}</p>
|
||||
) : (
|
||||
dnsOptions.map((o, i) => (
|
||||
<div key={i} className="flex items-center gap-2 mb-1.5">
|
||||
@@ -104,7 +106,7 @@ export function DnsHostSection({
|
||||
<Input
|
||||
value={o.value}
|
||||
onChange={e => updateDnsOption(i, { value: e.target.value })}
|
||||
placeholder="5(可留空)"
|
||||
placeholder={`5${t('k8s.dnsHost.optionalHint')}`}
|
||||
className="flex-1 h-7 dg-input text-[var(--dg-text-secondary)] text-xs font-mono"
|
||||
/>
|
||||
<Button size="icon" variant="ghost" className="h-6 w-6 dg-text-red" onClick={() => removeDnsOption(i)}>
|
||||
@@ -120,18 +122,18 @@ export function DnsHostSection({
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<MapPin className="h-4 w-4 dg-text-yellow" />主机别名 (hostAliases)
|
||||
<MapPin className="h-4 w-4 dg-text-yellow" />{t('k8s.dnsHost.hostAliasTitle')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-2">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<Label className="dg-text-secondary text-xs">IP → 主机名映射(注入 Pod 的 /etc/hosts)</Label>
|
||||
<Label className="dg-text-secondary text-xs">{t('k8s.dnsHost.hostAliasHint')}</Label>
|
||||
<Button size="sm" variant="ghost" className="h-6 text-xs dg-text-secondary" onClick={addHostAlias}>
|
||||
<Plus className="h-3 w-3 mr-1" />添加
|
||||
<Plus className="h-3 w-3 mr-1" />{t('common.add')}
|
||||
</Button>
|
||||
</div>
|
||||
{hostAliases.length === 0 ? (
|
||||
<p className="text-xs dg-text-muted italic">未配置</p>
|
||||
<p className="text-xs dg-text-muted italic">{t('k8s.dnsHost.notConfigured')}</p>
|
||||
) : (
|
||||
hostAliases.map((a, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
@@ -158,4 +160,4 @@ export function DnsHostSection({
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// SchedulingSection.tsx — 调度:节点名称 / 标签选择器 / 节点亲和性 // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader2, Plus, Server, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -31,10 +32,10 @@ export interface SchedulingSectionProps {
|
||||
nodeLabels: Record<string, string[]>;
|
||||
}
|
||||
|
||||
const SCHEDULE_MODES: Array<{ key: ScheduleMode; label: string }> = [
|
||||
{ key: 'nodeName', label: '节点名称' },
|
||||
{ key: 'nodeSelector', label: '标签选择器' },
|
||||
{ key: 'nodeAffinity', label: '节点亲和性' },
|
||||
const SCHEDULE_MODES: Array<{ key: ScheduleMode }> = [
|
||||
{ key: 'nodeName' },
|
||||
{ key: 'nodeSelector' },
|
||||
{ key: 'nodeAffinity' },
|
||||
];
|
||||
|
||||
export function SchedulingSection({
|
||||
@@ -50,12 +51,13 @@ export function SchedulingSection({
|
||||
onUpdateLabelSelector,
|
||||
nodeLabels,
|
||||
}: SchedulingSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||
<Server className="h-4 w-4 dg-text-green" />调度 (Scheduling)
|
||||
<Server className="h-4 w-4 dg-text-green" />{t('k8s.scheduling.title')}
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-1 bg-[var(--dg-bg-tertiary)] rounded-lg p-0.5 border border-[var(--dg-border)]">
|
||||
{SCHEDULE_MODES.map(m => (
|
||||
@@ -68,24 +70,24 @@ export function SchedulingSection({
|
||||
: 'dg-text-secondary hover:text-[var(--dg-text-secondary)]'
|
||||
}`}
|
||||
>
|
||||
{m.label}
|
||||
{t(`k8s.scheduling.mode.${m.key}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs dg-text-muted mt-1">
|
||||
{scheduleMode === 'nodeName' && 'Pod 将直接调度到指定节点'}
|
||||
{scheduleMode === 'nodeSelector' && 'Pod 将调度到匹配标签的节点'}
|
||||
{scheduleMode === 'nodeAffinity' && '通过亲和性规则控制 Pod 调度到指定节点'}
|
||||
{scheduleMode === 'nodeName' && t('k8s.scheduling.hint.nodeName')}
|
||||
{scheduleMode === 'nodeSelector' && t('k8s.scheduling.hint.nodeSelector')}
|
||||
{scheduleMode === 'nodeAffinity' && t('k8s.scheduling.hint.nodeAffinity')}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{scheduleMode === 'nodeName' && (
|
||||
<div className="space-y-2">
|
||||
<Label className="dg-text-secondary text-xs">请选择节点</Label>
|
||||
<Label className="dg-text-secondary text-xs">{t('k8s.scheduling.selectNode')}</Label>
|
||||
{nodesLoading ? (
|
||||
<div className="flex items-center gap-1 dg-text-muted text-xs">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />加载节点列表...
|
||||
<Loader2 className="h-3 w-3 animate-spin" />{t('k8s.scheduling.loadingNodes')}
|
||||
</div>
|
||||
) : (
|
||||
<select
|
||||
@@ -93,7 +95,7 @@ export function SchedulingSection({
|
||||
onChange={e => onNodeNameChange(e.target.value)}
|
||||
className="h-8 w-full bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-2"
|
||||
>
|
||||
<option value="">(不指定)</option>
|
||||
<option value="">{t('k8s.scheduling.notSpecified')}</option>
|
||||
{availableNodes.map(n => (
|
||||
<option key={n.name} value={n.name}>
|
||||
{n.name} {n.ip ? `(${n.ip})` : ''}
|
||||
@@ -103,7 +105,7 @@ export function SchedulingSection({
|
||||
)}
|
||||
{nodeName && (
|
||||
<div className="text-[10px] dg-text-muted">
|
||||
已选择: <code className="bg-[var(--dg-bg-tertiary)] px-1 py-0.5 rounded dg-text-green">{nodeName}</code>
|
||||
{t('k8s.scheduling.selected')} <code className="bg-[var(--dg-bg-tertiary)] px-1 py-0.5 rounded dg-text-green">{nodeName}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -112,13 +114,13 @@ export function SchedulingSection({
|
||||
{scheduleMode === 'nodeSelector' && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="dg-text-secondary text-xs">请选择标签</Label>
|
||||
<Label className="dg-text-secondary text-xs">{t('k8s.scheduling.selectLabel')}</Label>
|
||||
<Button size="sm" variant="ghost" className="h-6 text-xs dg-text-secondary" onClick={onAddLabelSelector}>
|
||||
<Plus className="h-3 w-3 mr-1" />添加
|
||||
<Plus className="h-3 w-3 mr-1" />{t('k8s.scheduling.add')}
|
||||
</Button>
|
||||
</div>
|
||||
{labelSelectorKv.length === 0 ? (
|
||||
<p className="text-xs dg-text-muted italic">未配置标签选择器</p>
|
||||
<p className="text-xs dg-text-muted italic">{t('k8s.scheduling.noLabelSelector')}</p>
|
||||
) : (
|
||||
labelSelectorKv.map((n, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
@@ -127,7 +129,7 @@ export function SchedulingSection({
|
||||
onChange={e => onUpdateLabelSelector(i, { key: e.target.value, value: '' })}
|
||||
className="flex-1 h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1.5"
|
||||
>
|
||||
<option value="">选择标签</option>
|
||||
<option value="">{t('k8s.scheduling.selectLabelPlaceholder')}</option>
|
||||
{Object.entries(nodeLabels).map(([k, vals]) => (
|
||||
<option key={k} value={k}>{k} ({vals.length})</option>
|
||||
))}
|
||||
@@ -138,7 +140,7 @@ export function SchedulingSection({
|
||||
className="flex-1 h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1.5"
|
||||
disabled={!n.key}
|
||||
>
|
||||
<option value="">选择值</option>
|
||||
<option value="">{t('k8s.scheduling.selectValuePlaceholder')}</option>
|
||||
{(nodeLabels[n.key] || []).map(v => (
|
||||
<option key={v} value={v}>{v}</option>
|
||||
))}
|
||||
@@ -161,14 +163,14 @@ export function SchedulingSection({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="dg-text-secondary text-xs">
|
||||
请选择标签 <span className="dg-text-muted">(生成 requiredDuringScheduling 规则)</span>
|
||||
{t('k8s.scheduling.selectLabelAffinity')} <span className="dg-text-muted">{t('k8s.scheduling.affinityRuleHint')}</span>
|
||||
</Label>
|
||||
<Button size="sm" variant="ghost" className="h-6 text-xs dg-text-secondary" onClick={onAddLabelSelector}>
|
||||
<Plus className="h-3 w-3 mr-1" />添加
|
||||
<Plus className="h-3 w-3 mr-1" />{t('k8s.scheduling.add')}
|
||||
</Button>
|
||||
</div>
|
||||
{labelSelectorKv.length === 0 ? (
|
||||
<p className="text-xs dg-text-muted italic">未配置亲和性规则</p>
|
||||
<p className="text-xs dg-text-muted italic">{t('k8s.scheduling.noAffinityRule')}</p>
|
||||
) : (
|
||||
labelSelectorKv.map((n, i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
@@ -177,7 +179,7 @@ export function SchedulingSection({
|
||||
onChange={e => onUpdateLabelSelector(i, { key: e.target.value, value: '' })}
|
||||
className="flex-1 h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1.5"
|
||||
>
|
||||
<option value="">选择标签</option>
|
||||
<option value="">{t('k8s.scheduling.selectLabelPlaceholder')}</option>
|
||||
{Object.entries(nodeLabels).map(([k, vals]) => (
|
||||
<option key={k} value={k}>{k} ({vals.length})</option>
|
||||
))}
|
||||
@@ -188,7 +190,7 @@ export function SchedulingSection({
|
||||
className="flex-1 h-7 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded text-xs text-[var(--dg-text-secondary)] px-1.5"
|
||||
disabled={!n.key}
|
||||
>
|
||||
<option value="">选择值</option>
|
||||
<option value="">{t('k8s.scheduling.selectValuePlaceholder')}</option>
|
||||
{(nodeLabels[n.key] || []).map(v => (
|
||||
<option key={v} value={v}>{v}</option>
|
||||
))}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// deployment-view/EventsTab.tsx — 事件 Tab // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EventTable } from '../EventTable';
|
||||
import type { EventItem } from './types';
|
||||
|
||||
export function EventsTab({ events, loading }: { events: EventItem[]; loading: string }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<EventTable
|
||||
events={events}
|
||||
loading={loading}
|
||||
emptyText="暂无事件"
|
||||
emptyText={t('k8s.empty.events')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// deployment-view/HPACronHPATab.tsx — 容器伸缩 Tab (HPA + CronHPA) // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -9,6 +10,7 @@ import { Activity, Clock, Loader2 } from 'lucide-react';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
export function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: JsonData[]; cronHpas: JsonData[]; loading: string; replicas: number }) {
|
||||
const { t } = useTranslation();
|
||||
if (loading === 'scale') {
|
||||
return <div className="flex items-center justify-center py-12"><Loader2 className="h-6 w-6 animate-spin text-[var(--dg-text-muted)]" /></div>;
|
||||
}
|
||||
@@ -17,29 +19,29 @@ export function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: Jso
|
||||
<div className="space-y-4">
|
||||
{/* 当前副本数提示 */}
|
||||
<div className="flex items-center gap-2 px-2">
|
||||
<span className="text-sm text-[var(--dg-text-muted)]">当前副本:</span>
|
||||
<span className="text-sm text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.currentReplicas')}</span>
|
||||
<span className="text-sm font-bold text-[var(--dg-text-primary)]">{replicas}</span>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">(可通过 Tab 栏右侧 +/- 手动调整)</span>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.scaleHint')}</span>
|
||||
</div>
|
||||
|
||||
{/* HPA 策略 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-2"><CardTitle className="text-sm flex items-center gap-2"><Activity className="h-4 w-4 text-[var(--dg-orange)]" />HPA — 水平自动伸缩</CardTitle></CardHeader>
|
||||
<CardHeader className="pb-2"><CardTitle className="text-sm flex items-center gap-2"><Activity className="h-4 w-4 text-[var(--dg-orange)]" />{t('k8s.hpaCronHpa.hpaTitle')}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
{hpas.length === 0 ? (
|
||||
<div className="text-sm text-[var(--dg-text-muted)] py-4">
|
||||
未关联 HPA — <Button variant="link" className="h-auto p-0 text-[var(--dg-blue)] text-sm">创建 HPA</Button>
|
||||
{t('k8s.hpaCronHpa.noHpaLinked')}<Button variant="link" className="h-auto p-0 text-[var(--dg-blue)] text-sm">{t('k8s.hpaCronHpa.createHpa')}</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableHead className="text-[var(--dg-text-muted)]">名称</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">最小/最大</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">指标</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">当前负载</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">状态</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.name')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.col.minMax')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.col.metrics')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.col.currentLoad')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.col.status')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -65,7 +67,7 @@ export function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: Jso
|
||||
<TableCell className="text-sm font-mono text-[var(--dg-text-secondary)]">{curR}</TableCell>
|
||||
<TableCell>
|
||||
<Badge className={ok ? 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]' : 'bg-[var(--dg-red)]/20 text-[var(--dg-red)]'}>
|
||||
{ok ? '正常' : '异常'}
|
||||
{ok ? t('k8s.hpaCronHpa.status.normal') : t('k8s.hpaCronHpa.status.abnormal')}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -80,22 +82,22 @@ export function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: Jso
|
||||
|
||||
{/* CronHPA 策略 */}
|
||||
<Card className="dg-card">
|
||||
<CardHeader className="pb-2"><CardTitle className="text-sm flex items-center gap-2"><Clock className="h-4 w-4 text-[var(--dg-blue)]" />CronHPA — 定时伸缩</CardTitle></CardHeader>
|
||||
<CardHeader className="pb-2"><CardTitle className="text-sm flex items-center gap-2"><Clock className="h-4 w-4 text-[var(--dg-blue)]" />{t('k8s.hpaCronHpa.cronHpaTitle')}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
{cronHpas.length === 0 ? (
|
||||
<div className="text-sm text-[var(--dg-text-muted)] py-4">
|
||||
未关联 CronHPA — <Button variant="link" className="h-auto p-0 text-[var(--dg-blue)] text-sm">创建 CronHPA</Button>
|
||||
{t('k8s.hpaCronHpa.noCronHpaLinked')}<Button variant="link" className="h-auto p-0 text-[var(--dg-blue)] text-sm">{t('k8s.hpaCronHpa.createCronHpa')}</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableHead className="text-[var(--dg-text-muted)]">名称</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">目标副本</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">调度规则</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">时区</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">状态</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.name')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.cronCol.targetReplicas')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.cronCol.scheduleRule')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.cronCol.timezone')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.hpaCronHpa.cronCol.status')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -111,7 +113,7 @@ export function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: Jso
|
||||
<TableCell className="text-xs font-mono text-[var(--dg-text-muted)]">{jobs.map((j: JsonData) => j.schedule || '?').join(', ')}</TableCell>
|
||||
<TableCell className="text-xs text-[var(--dg-text-muted)]">{tz}</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-[var(--dg-green)]/20 text-[var(--dg-green)]">已启用</Badge>
|
||||
<Badge className="bg-[var(--dg-green)]/20 text-[var(--dg-green)]">{t('k8s.label.enabled')}</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -124,4 +126,4 @@ export function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: Jso
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// deployment-view/HistoryTab.tsx — 历史版本 Tab // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
@@ -9,8 +10,9 @@ import { formatK8sTime } from '../k8s-yaml-utils';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
export function HistoryTab({ revisions, rollbackLoading, onRollback }: { revisions: JsonData[]; rollbackLoading: number | null; onRollback: (rev: JsonData) => void }) {
|
||||
const { t } = useTranslation();
|
||||
if (revisions.length === 0) {
|
||||
return <div className="text-center text-[var(--dg-text-muted)] py-12">暂无历史版本</div>;
|
||||
return <div className="text-center text-[var(--dg-text-muted)] py-12">{t('k8s.history.empty')}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
@@ -18,10 +20,10 @@ export function HistoryTab({ revisions, rollbackLoading, onRollback }: { revisio
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-24">Revision</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">镜像</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-24">状态</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-40">更新时间</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-24">操作</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.image')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-24">{t('k8s.column.status')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-40">{t('k8s.history.updateTime')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)] w-24">{t('common.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -34,14 +36,14 @@ export function HistoryTab({ revisions, rollbackLoading, onRollback }: { revisio
|
||||
<TableCell className="text-xs font-mono text-[var(--dg-text-muted)] max-w-[300px] truncate">{rev.image || rev.images?.[0] || '-'}</TableCell>
|
||||
<TableCell>
|
||||
{isLatest
|
||||
? <Badge className="bg-[var(--dg-green)]/20 text-[var(--dg-green)] border-[var(--dg-green)]/30">当前</Badge>
|
||||
: <Badge variant="outline" className="text-[var(--dg-text-muted)]">历史</Badge>}
|
||||
? <Badge className="bg-[var(--dg-green)]/20 text-[var(--dg-green)] border-[var(--dg-green)]/30">{t('k8s.history.current')}</Badge>
|
||||
: <Badge variant="outline" className="text-[var(--dg-text-muted)]">{t('k8s.history.historical')}</Badge>}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-[var(--dg-text-muted)]">{formatK8sTime(rev.created_at || rev.time)}</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="sm" className="h-7 dg-text-yellow" disabled={isLatest || rollbackLoading !== null} onClick={() => onRollback(rev)}>
|
||||
{rollbackLoading === revision ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <RotateCcw className="h-3 w-3 mr-1" />}
|
||||
回滚
|
||||
{t('k8s.history.rollback')}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -51,4 +53,4 @@ export function HistoryTab({ revisions, rollbackLoading, onRollback }: { revisio
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// deployment-view/PodsTab.tsx — 容器组 Tab // Logan.Li
|
||||
// v2: 虚拟滚动,支持 1000+ Pod 场景流畅渲染
|
||||
import { useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -27,6 +28,7 @@ export function PodsTab({
|
||||
onDeletePod,
|
||||
expandedPod, setExpandedPod,
|
||||
}: DeploymentViewProps & { expandedPod: string | null; setExpandedPod: (v: string | null) => void }) {
|
||||
const { t } = useTranslation();
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
@@ -37,7 +39,7 @@ export function PodsTab({
|
||||
});
|
||||
|
||||
if (pods.length === 0) {
|
||||
return <div className="text-center text-[var(--dg-text-muted)] py-12">暂无容器组</div>;
|
||||
return <div className="text-center text-[var(--dg-text-muted)] py-12">{t('k8s.pods.empty')}</div>;
|
||||
}
|
||||
|
||||
const virtualRows = virtualizer.getVirtualItems();
|
||||
@@ -52,13 +54,13 @@ export function PodsTab({
|
||||
style={{ gridTemplateColumns: GRID_COLS }}
|
||||
>
|
||||
<span />
|
||||
<span>名称</span>
|
||||
<span>状态</span>
|
||||
<span>节点</span>
|
||||
<span>{t('k8s.pods.col.name')}</span>
|
||||
<span>{t('k8s.pods.col.status')}</span>
|
||||
<span>{t('k8s.pods.col.node')}</span>
|
||||
<span>IP</span>
|
||||
<span className="text-center">重启</span>
|
||||
<span className="text-center">{t('k8s.pods.col.restart')}</span>
|
||||
<span>Age</span>
|
||||
<span className="text-right">操作</span>
|
||||
<span className="text-right">{t('k8s.pods.col.actions')}</span>
|
||||
</div>
|
||||
|
||||
{/* 虚拟滚动列表 */}
|
||||
@@ -134,10 +136,10 @@ export function PodsTab({
|
||||
</span>
|
||||
|
||||
<div className="flex items-center justify-end gap-0.5 shrink-0 w-[88px]">
|
||||
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 dg-text-green" title="终端" onClick={() => onShowPodTerminal(pod)}>
|
||||
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 dg-text-green" title={t('k8s.pods.action.terminal')} onClick={() => onShowPodTerminal(pod)}>
|
||||
<Terminal className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 dg-text-yellow" title="文件" onClick={() => onShowPodFileBrowser(pod)}>
|
||||
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 dg-text-yellow" title={t('k8s.pods.action.file')} onClick={() => onShowPodFileBrowser(pod)}>
|
||||
<HardDrive className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
@@ -146,21 +148,21 @@ export function PodsTab({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0 shrink-0 text-[var(--dg-text-muted)] hover:text-[var(--dg-blue)]"
|
||||
title="更多"
|
||||
aria-label="更多"
|
||||
title={t('k8s.pods.action.more')}
|
||||
aria-label={t('k8s.pods.action.more')}
|
||||
>
|
||||
<MoreVertical className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<DropdownMenuItem onClick={() => onShowPodLogs(pod)} className="text-[var(--dg-text-primary)]">
|
||||
<FileText className="h-3.5 w-3.5 mr-2" /> 日志
|
||||
<FileText className="h-3.5 w-3.5 mr-2" /> {t('k8s.pods.action.logs')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onShowPodYaml(pod)} className="text-[var(--dg-text-primary)]">
|
||||
<FileCode className="h-3.5 w-3.5 mr-2" /> YAML
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onDeletePod?.(pod)} className="dg-text-red focus:dg-text-red">
|
||||
<Trash2 className="h-3.5 w-3.5 mr-2" /> 删除
|
||||
<Trash2 className="h-3.5 w-3.5 mr-2" /> {t('k8s.pods.action.delete')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -173,7 +175,7 @@ export function PodsTab({
|
||||
<PodConditionsRow pod={pod} />
|
||||
{containers && containers.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mb-2">容器</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mb-2">{t('k8s.pods.containers')}</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{containers.map((c, idx) => (
|
||||
<div key={idx} className="rounded-md border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-2.5 text-xs">
|
||||
@@ -187,7 +189,7 @@ export function PodsTab({
|
||||
{c.resources && (
|
||||
<div className="mt-1.5 flex gap-4 text-[var(--dg-text-muted)]">
|
||||
<span>CPU {String((c.resources as Record<string, Record<string, string>>)?.requests?.cpu ?? '0')} / {String((c.resources as Record<string, Record<string, string>>)?.limits?.cpu ?? '—')}</span>
|
||||
<span>内存 {String((c.resources as Record<string, Record<string, string>>)?.requests?.memory ?? '0')} / {String((c.resources as Record<string, Record<string, string>>)?.limits?.memory ?? '—')}</span>
|
||||
<span>{t('k8s.pods.memory')} {String((c.resources as Record<string, Record<string, string>>)?.requests?.memory ?? '0')} / {String((c.resources as Record<string, Record<string, string>>)?.limits?.memory ?? '—')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -206,7 +208,7 @@ export function PodsTab({
|
||||
{/* 行数统计 */}
|
||||
{pods.length > 0 && (
|
||||
<div className="text-center py-2 text-xs text-[var(--dg-text-muted)] border-t border-[var(--dg-border)]">
|
||||
共 {pods.length} 个容器组 {pods.length > 200 && '· 虚拟滚动渲染'}
|
||||
{t('k8s.pods.totalCount', { count: pods.length })} {pods.length > 200 && `· ${t('k8s.pods.virtualScroll')}`}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// deployment-view/ServicesTab.tsx — 访问方式 Tab // Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
||||
@@ -8,6 +9,7 @@ import type { SvcItem, IngItem } from './types';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
export function ServicesTab({ services, ingresses, loading }: { services: SvcItem[]; ingresses: IngItem[]; loading: string; namespace: string }) {
|
||||
const { t } = useTranslation();
|
||||
if (loading === 'services') {
|
||||
return <div className="flex items-center justify-center py-12"><Loader2 className="h-6 w-6 animate-spin text-[var(--dg-text-muted)]" /></div>;
|
||||
}
|
||||
@@ -16,20 +18,20 @@ export function ServicesTab({ services, ingresses, loading }: { services: SvcIte
|
||||
{/* Services */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
|
||||
<Network className="h-4 w-4 text-[var(--dg-blue)]" />关联 Service
|
||||
<Network className="h-4 w-4 text-[var(--dg-blue)]" />{t('k8s.services.relatedService')}
|
||||
</h3>
|
||||
{services.length === 0 ? (
|
||||
<p className="text-sm text-[var(--dg-text-muted)] py-4">暂无匹配的 Service(Deployment 选择器未关联任何 Service)</p>
|
||||
<p className="text-sm text-[var(--dg-text-muted)] py-4">{t('k8s.services.emptyService')}</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableHead className="text-[var(--dg-text-muted)]">名称</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">类型</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.name')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.type')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">ClusterIP</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">端口</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">Selector</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.ports')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.selector')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -55,19 +57,19 @@ export function ServicesTab({ services, ingresses, loading }: { services: SvcIte
|
||||
{/* Ingresses */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
|
||||
<ExternalLink className="h-4 w-4 text-[var(--dg-purple)]" />关联 Ingress
|
||||
<ExternalLink className="h-4 w-4 text-[var(--dg-purple)]" />{t('k8s.services.relatedIngress')}
|
||||
</h3>
|
||||
{ingresses.length === 0 ? (
|
||||
<p className="text-sm text-[var(--dg-text-muted)] py-4">暂无匹配的 Ingress</p>
|
||||
<p className="text-sm text-[var(--dg-text-muted)] py-4">{t('k8s.services.emptyIngress')}</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableHead className="text-[var(--dg-text-muted)]">名称</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.name')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">Class</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">Host</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">路径</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-muted)]">{t('k8s.column.path')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -88,4 +90,4 @@ export function ServicesTab({ services, ingresses, loading }: { services: SvcIte
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// ActionModals.tsx — 调整镜像 / 伸缩 / 删除 弹窗
|
||||
// 作者: Logan.Li
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { AlertTriangle, Loader2, Minus, Plus, Trash2 } from 'lucide-react';
|
||||
@@ -39,6 +40,7 @@ export function ActionModals({
|
||||
onScaleTo,
|
||||
onDelete,
|
||||
}: ActionModalsProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
{/* ============ 弹窗:调整镜像版本 ============ */}
|
||||
@@ -55,9 +57,9 @@ export function ActionModals({
|
||||
|
||||
{/* ============ 弹窗:伸缩 ============ */}
|
||||
{modal?.kind === 'scale' && (
|
||||
<ModalShell title="伸缩 (Scale)" onClose={onClose}>
|
||||
<ModalShell title={t('k8s.actionModal.scaleTitle')} onClose={onClose}>
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs dg-text-muted">当前副本: {deployment.replicas || 0}</p>
|
||||
<p className="text-xs dg-text-muted">{t('k8s.actionModal.currentReplicas')} {deployment.replicas || 0}</p>
|
||||
<div className="flex items-center justify-center gap-3 py-3">
|
||||
<Button variant="outline" size="icon" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]"
|
||||
disabled={actionLoading === 'scale' || scaleValue <= 0}
|
||||
@@ -74,9 +76,9 @@ export function ActionModals({
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onClose}>取消</Button>
|
||||
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onClose}>{t('common.cancel')}</Button>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700" disabled={actionLoading === 'scale'} onClick={onScaleTo}>
|
||||
{actionLoading === 'scale' ? <Loader2 className="h-4 w-4 animate-spin" /> : '确认'}
|
||||
{actionLoading === 'scale' ? <Loader2 className="h-4 w-4 animate-spin" /> : t('common.confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,21 +87,21 @@ export function ActionModals({
|
||||
|
||||
{/* ============ 弹窗:删除 ============ */}
|
||||
{modal?.kind === 'delete' && (
|
||||
<ModalShell title="删除 Deployment" onClose={onClose}>
|
||||
<ModalShell title={t('k8s.actionModal.deleteTitle')} onClose={onClose}>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 dg-text-red">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
<span className="font-semibold">此操作不可撤销</span>
|
||||
<span className="font-semibold">{t('k8s.actionModal.irreversible')}</span>
|
||||
</div>
|
||||
<p className="text-sm dg-text-secondary">
|
||||
确认删除 Deployment <code className="text-[var(--dg-text-primary)] font-mono">{namespace}/{deploymentName}</code>?
|
||||
其管理的所有 Pod 将被一并回收。
|
||||
{t('k8s.actionModal.deleteConfirm', { namespace, name: deploymentName })}
|
||||
{t('k8s.actionModal.deleteConfirmDesc')}
|
||||
</p>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onClose}>取消</Button>
|
||||
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onClose}>{t('common.cancel')}</Button>
|
||||
<Button variant="destructive" disabled={actionLoading === 'delete'} onClick={onDelete}>
|
||||
{actionLoading === 'delete' ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : <Trash2 className="h-4 w-4 mr-1" />}
|
||||
确认删除
|
||||
{t('k8s.actionModal.confirmDelete')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user