refactor(i18n): U37 全量 JSX 文本节点硬编码中文清零 — 65 文件 151 处→0, 新增 common.* 98 翻译键, zh/en 对齐, tsc -b=0, npm build✓ (17.96s), ESLint=0/43/any=0, go build=0, 17/17 测试通过

This commit is contained in:
attacker committed 2026-07-13 06:39:08 +08:00
1 parent e274ba7b9b
commit 7c80e35411
68 files changed
+11807 -11610

No files matched your search

+1
View File
@@ -525,6 +525,7 @@ services/api.ts (1324L)
| U34 | System & User 模块 i18n 清理(ProfilePage + ResetPasswordDialog + SystemRolesTab + LdapManagementSection 4 文件 — ProfilePage 从零接入 useTranslation + ResetPasswordDialog 从零接入 useTranslation + SystemRolesTab 权限分组标签(云资源/自动化/审批/审计)+toast+confirm description+角色计数+空状态+权限列表计数+编码标签+尾部文案全部 i18n + LdapManagementSection 验证按钮+根OU标签+创建按钮+导入按钮+编辑用户描述全部 i18n;新增 user.profile.* 14键+user.resetPwd.* 11键+system.* 16键 共 41 翻译键;UI 硬编码中文 ProfilePage 15→0 + ResetPasswordDialog 11→0 + SystemRolesTab 12→0 + LdapManagementSection 8→0;zh/en 7524→7565 +41 对齐;tsc -b=0, npm build✓ (16.05s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** | | U34 | System & User 模块 i18n 清理(ProfilePage + ResetPasswordDialog + SystemRolesTab + LdapManagementSection 4 文件 — ProfilePage 从零接入 useTranslation + ResetPasswordDialog 从零接入 useTranslation + SystemRolesTab 权限分组标签(云资源/自动化/审批/审计)+toast+confirm description+角色计数+空状态+权限列表计数+编码标签+尾部文案全部 i18n + LdapManagementSection 验证按钮+根OU标签+创建按钮+导入按钮+编辑用户描述全部 i18n;新增 user.profile.* 14键+user.resetPwd.* 11键+system.* 16键 共 41 翻译键;UI 硬编码中文 ProfilePage 15→0 + ResetPasswordDialog 11→0 + SystemRolesTab 12→0 + LdapManagementSection 8→0;zh/en 7524→7565 +41 对齐;tsc -b=0, npm build✓ (16.05s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U35 | Deploy + Task 模块残余 JSX 硬编码中文清理(PipelineRunPanel 12处 + CronJobTable 12处 + ExecutionHistoryDialog 10处 + ScrollButtons 2处 — 新增 deploy.runStatus.* 12键 + task.cronJobCol.* 14键 + task.execHistory.* 14键 + deploy.credCol.* 9键 + common.scrollToTop/scrollToBottom/success/failed/total 5键 共 54 翻译键;zh/en 7565→7616 +51 对齐;tsc -b=0, npm build✓ (17.02s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** | | U35 | Deploy + Task 模块残余 JSX 硬编码中文清理(PipelineRunPanel 12处 + CronJobTable 12处 + ExecutionHistoryDialog 10处 + ScrollButtons 2处 — 新增 deploy.runStatus.* 12键 + task.cronJobCol.* 14键 + task.execHistory.* 14键 + deploy.credCol.* 9键 + common.scrollToTop/scrollToBottom/success/failed/total 5键 共 54 翻译键;zh/en 7565→7616 +51 对齐;tsc -b=0, npm build✓ (17.02s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U36 | 全量 JSX 属性 + toast 硬编码中文清零(20 文件 30 处→0: TerminalLoadingOverlay/copy-button/AppRelease/DeployCredentials/PipelineManager/WorkspaceTermDialog/CredFormDialog/JobRoleCreateDialog/PipelineCreateDialog/AdvancedSettingsPanel/ServerForm/LdapManagementSection/LiveMonitorDialog/SystemManager/ActiveSessionsPanel/HistorySessionsPanel/AlertRulesTab/PrometheusDatasourceManager/SNMPMonitor/PredictiveCapacityTab — 23 JSX 属性 + 7 toast 调用全量清零, copy-button 从 import i18n→useTranslation hook, 4 组件新增 useTranslation hook;新增 25 翻译键;zh/en 7616→7640 +24 对齐;tsc -b=0, npm build✓ (17.25s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** | | U36 | 全量 JSX 属性 + toast 硬编码中文清零(20 文件 30 处→0: TerminalLoadingOverlay/copy-button/AppRelease/DeployCredentials/PipelineManager/WorkspaceTermDialog/CredFormDialog/JobRoleCreateDialog/PipelineCreateDialog/AdvancedSettingsPanel/ServerForm/LdapManagementSection/LiveMonitorDialog/SystemManager/ActiveSessionsPanel/HistorySessionsPanel/AlertRulesTab/PrometheusDatasourceManager/SNMPMonitor/PredictiveCapacityTab — 23 JSX 属性 + 7 toast 调用全量清零, copy-button 从 import i18n→useTranslation hook, 4 组件新增 useTranslation hook;新增 25 翻译键;zh/en 7616→7640 +24 对齐;tsc -b=0, npm build✓ (17.25s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
| U37 | 全量 JSX 文本节点硬编码中文清零(65 文件 151 处→0: MiddlewareMonitor/SiteMonitor/SNMPMonitor/HealthProbeSection/ContextSwitchCard/ProcessTable/PortMonitorTab/MwTargetChartsPanel/MiddlewareDialog/ChartRangeHint/SnmpDeviceChartsPanel/AlertRulesTab/LogClusterPanel/LogDatasourcePage/LogQueryToolbar/LogAlertsPanel/PrometheusDatasourceManager/PodMetricsPage/CapacityPage/NamespaceManagementPage/NamespaceResourcePage/DeploymentEditService/EventsPage/DeploymentDetail/ClusterOverviewPage/DeploymentEditContainers/DeploymentEditStorage/WorkloadViewPage/BottomStatusBar/UpdateStrategySelectorCards/PodInstancesTable/shared-components/UpdateStrategyCard/TolerationsCard/HostSettingsCard/IngressListItem/ServiceListItem/ServiceCreateForm/IngressCreateForm/CreateHPADialog/DeployCredentials/DeployRBAC/UserGroupsManager/BuildNodesManager/DeployTargetFields/PipelineCreateDialog/CredFormDialog/WorkspaceTermDialog/AgentAlerts/AgentRuleDialog/LiveMonitorDialog/RoleFormDialog/CronJobViewDialog/TerminalOutput/AIAnalysisResult/AIPredictiveAnalytics/PredictiveCostTab/PredictiveCapacityTab/ActiveSessionsPanel/HistorySessionsPanel/TerminalDialog/ServerFileBrowser/PodFileBrowser/SiteResultsPanel/SiteProtocolPieChart — 151 JSX 文本节点中文全部替换为 t()/i18n.t() 调用;新增 common.* 98 翻译键 (selectedCount/itemsCount/newValue/namespaceLabel/containerLabel/replicasLabel/minReplicas/maxReplicas/createNamespace/noNamespace/unknownResource/exportYaml/loadTrend/loadCapacity/predicted/threshold/hostSettings/addVolume/addMount/createService/createIngress/editAction/updateStrategy/selectAndLabel/historyVersion/fileBrowser/scale/back/createHpa/allNamespaces/certLabel/observable/deviceList/addDevice/addMonitor/collectNow/addTask/addProbe/restoreFull/contextSwitch/processTop/listenPorts/terminalRealtime/auditWebssh/buildTerminal/fileBrowse/newDir/loadDeployment/notLinked/selectedPerms/none/hasLabel/deleteNamespace/deleteNamespaceWarn/allResources/notFoundDeploy/nodeList/fileLabel/addCustom/newInstanceService/newInstanceIngress/totalContainers/workContainers/loadingEllipsis/volumeClaimTemplates/containerTemplate/passwordOptional/updated/page/perPage/clusterMode/totalLogs/countLabel/percentage/queryTime/addDatasource/newPermission/newEnv/envLabel/displayName/newUserGroup/auditTrace/predictValue/confidence/predictMonthEnd/noCapacityData/securityMode/standardRdp/domainLabel/noRdpActive/noRdpHistory/buffer/matched/podLogs/createHpaTitle/password/events/total);37 文件已有 import i18n→i18n.t() 模式, 2 文件新增 import i18n;zh/en 7640→3526 namespace-flat +98 键对齐;tsc -b=0, npm build✓ (17.96s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
--- ---
@@ -273,9 +273,9 @@ export function AgentAlerts() {
</span> </span>
)} )}
{rule.metric === 'port_down' && rule.port ? ( {rule.metric === 'port_down' && rule.port ? (
<span className="dg-text-secondary">端口 {rule.port}</span> <span className="dg-text-secondary">{i18n.t("common.port")} {rule.port}</span>
) : null} ) : null}
{rule.duration ? <span className="dg-text-secondary">持续 {rule.duration}s</span> : null} {rule.duration ? <span className="dg-text-secondary">{i18n.t("common.duration")} {rule.duration}s</span> : null}
</div> </div>
</div> </div>
</div> </div>
@@ -167,7 +167,7 @@ export function AgentRuleDialog({ open, onOpenChange, editingRule, onSuccess }:
</Select> </Select>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)] text-xs">阈值 {ruleForm.metric !== 'port_down' ? '(%)' : ''}</Label> <Label className="text-[var(--dg-text-secondary)] text-xs">{i18n.t('common.threshold')} {ruleForm.metric !== 'port_down' ? '(%)' : ''}</Label>
<Input <Input
type="number" type="number"
value={ruleForm.threshold} value={ruleForm.threshold}
@@ -57,7 +57,7 @@ export function AIAnalysisResult({ selectedAlert, isAnalyzing, analysis, onAutoF
{analysis.traceId && ( {analysis.traceId && (
<div className="p-3 bg-[var(--dg-bg-secondary)] rounded-lg border border-[var(--dg-border)]"> <div className="p-3 bg-[var(--dg-bg-secondary)] rounded-lg border border-[var(--dg-border)]">
<div className="text-xs dg-text-muted mb-1">审计 Trace</div> <div className="text-xs dg-text-muted mb-1">{i18n.t("common.auditTrace")}</div>
<div className="space-y-1 text-xs font-mono text-[var(--dg-text-secondary)] break-all"> <div className="space-y-1 text-xs font-mono text-[var(--dg-text-secondary)] break-all">
<div>trace_id: {analysis.traceId}</div> <div>trace_id: {analysis.traceId}</div>
{analysis.spanId && <div>span_id: {analysis.spanId}</div>} {analysis.spanId && <div>span_id: {analysis.spanId}</div>}
@@ -221,7 +221,7 @@ export function AIPredictiveAnalytics() {
</p> </p>
</div> </div>
<div> <div>
<p className="text-xs dg-text-muted mb-1">预测值 ({prediction.timeRange})</p> <p className="text-xs dg-text-muted mb-1">{t('common.predictValue', { timeRange: prediction.timeRange })}</p>
<p className={`text-lg font-bold ${trendColorClass(prediction.trend)}`}> <p className={`text-lg font-bold ${trendColorClass(prediction.trend)}`}>
{prediction.predicted} {prediction.predicted}
{prediction.unit} {prediction.unit}
@@ -231,7 +231,7 @@ export function AIPredictiveAnalytics() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs dg-text-secondary">置信度</span> <span className="text-xs dg-text-secondary">{t("common.confidence")}</span>
<div className="w-16 h-1.5 bg-[var(--dg-bg-active)] rounded-full overflow-hidden"> <div className="w-16 h-1.5 bg-[var(--dg-bg-active)] rounded-full overflow-hidden">
<div <div
className="h-full bg-gradient-to-r from-blue-500 to-purple-500" className="h-full bg-gradient-to-r from-blue-500 to-purple-500"
@@ -65,10 +65,10 @@ export function PredictiveCapacityTab({
<CardContent> <CardContent>
{loading ? ( {loading ? (
<div className="flex items-center justify-center py-12 dg-text-muted"> <div className="flex items-center justify-center py-12 dg-text-muted">
<Loader2 className="h-5 w-5 animate-spin mr-2" /> 加载容量数据... <Loader2 className="h-5 w-5 animate-spin mr-2" /> {t("common.loadCapacity")}
</div> </div>
) : forecasts.length === 0 ? ( ) : forecasts.length === 0 ? (
<div className="text-center py-12 dg-text-muted">暂无容量数据</div> <div className="text-center py-12 dg-text-muted">{t("common.noCapacityData")}</div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{/* AI 深度解读结果(点击按钮后展示) */} {/* AI 深度解读结果(点击按钮后展示) */}
@@ -137,9 +137,9 @@ export function PredictiveCapacityTab({
</div> </div>
</div> </div>
<div className="flex items-center justify-between text-xs mt-1"> <div className="flex items-center justify-between text-xs mt-1">
<span className="dg-text-muted">当前 {forecast.currentUsage}%</span> <span className="dg-text-muted">{t("common.current")} {forecast.currentUsage}%</span>
<span className="dg-text-yellow">预测 {forecast.predictedUsage}%</span> <span className="dg-text-yellow">{t("common.predicted")} {forecast.predictedUsage}%</span>
<span className="dg-text-red">阈值 {forecast.threshold}%</span> <span className="dg-text-red">{t("common.threshold")} {forecast.threshold}%</span>
</div> </div>
</div> </div>
@@ -210,7 +210,7 @@ export function PredictiveCostTab({
</p> </p>
</div> </div>
<div> <div>
<p className="text-xs dg-text-muted">预测月末</p> <p className="text-xs dg-text-muted">{t("common.predictMonthEnd")}</p>
<p className={`font-semibold ${trendColorClass(dim.trend)}`}> <p className={`font-semibold ${trendColorClass(dim.trend)}`}>
{formatCurrency(dim.predictedMonthEnd)} {formatCurrency(dim.predictedMonthEnd)}
</p> </p>
@@ -188,10 +188,10 @@ export default function BuildNodesManager({ embedded = false }: { embedded?: boo
<span className="text-sm font-medium text-[var(--dg-text-primary)]"> {i18n.t('pipeline.buildNodes')} </span> <span className="text-sm font-medium text-[var(--dg-text-primary)]"> {i18n.t('pipeline.buildNodes')} </span>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="ghost" size="sm" onClick={load} disabled={loading}> <Button variant="ghost" size="sm" onClick={load} disabled={loading}>
<RefreshCw className={`h-3.5 w-3.5 mr-1${loading ? ' animate-spin' : ''}`} />刷新 <RefreshCw className={`h-3.5 w-3.5 mr-1${loading ? ' animate-spin' : ''}`} />{i18n.t("common.refresh")}
</Button> </Button>
<Button size="sm" onClick={openCreate}> <Button size="sm" onClick={openCreate}>
<Plus className="h-3.5 w-3.5 mr-1" />添加 <Plus className="h-3.5 w-3.5 mr-1" />{i18n.t("common.add")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -269,7 +269,7 @@ export default function DeployCredentials() {
<> <>
<div className="flex items-center justify-end mb-3"> <div className="flex items-center justify-end mb-3">
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}> <Button size="sm" className="dg-btn-primary" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新增环境 <Plus className="w-4 h-4 mr-1" />{t("common.newEnv")}
</Button> </Button>
</div> </div>
{environments.length === 0 ? ( {environments.length === 0 ? (
@@ -279,7 +279,7 @@ export default function DeployCredentials() {
description={t('deploy.clickEnvConfigHint')} description={t('deploy.clickEnvConfigHint')}
action={( action={(
<Button size="sm" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}> <Button size="sm" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新增环境 <Plus className="w-4 h-4 mr-1" />{t("common.newEnv")}
</Button> </Button>
)} )}
/> />
@@ -289,13 +289,13 @@ export default function DeployCredentials() {
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"> <thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]">
<tr> <tr>
<th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">环境标识</th> <th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">{t("common.envLabel")}</th>
<th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">显示名称</th> <th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">{t("common.displayName")}</th>
<th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">类型</th> <th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">{t("common.type")}</th>
<th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">颜色</th> <th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">{t("common.color")}</th>
<th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">排序</th> <th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">{t("common.sort")}</th>
<th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">状态</th> <th className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)]">{t("common.status")}</th>
<th className="px-3 py-2 text-right font-medium text-[var(--dg-text-secondary)]">操作</th> <th className="px-3 py-2 text-right font-medium text-[var(--dg-text-secondary)]">{t("common.actions")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -208,7 +208,7 @@ export default function DeployRBAC() {
onTabChange={setActiveTab} onTabChange={setActiveTab}
actions={activeTab === 'groups' ? undefined : ( actions={activeTab === 'groups' ? undefined : (
<Button className="dg-btn-primary" onClick={() => setCreateOpen(true)}> <Button className="dg-btn-primary" onClick={() => setCreateOpen(true)}>
<Plus className="w-4 h-4 mr-1" />新建权限 <Plus className="w-4 h-4 mr-1" />{i18n.t("common.newPermission")}
</Button> </Button>
)} )}
contentClassName="space-y-4" contentClassName="space-y-4"
@@ -88,10 +88,10 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
const toolbar = embedded ? ( const toolbar = embedded ? (
<div className="flex items-center justify-end gap-2 mb-3"> <div className="flex items-center justify-end gap-2 mb-3">
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadGroups}> <Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadGroups}>
<RefreshCw className="w-4 h-4 mr-1" />刷新 <RefreshCw className="w-4 h-4 mr-1" />{i18n.t("common.refresh")}
</Button> </Button>
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}> <Button size="sm" className="dg-btn-primary" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新建用户组 <Plus className="w-4 h-4 mr-1" />{i18n.t("common.newUserGroup")}
</Button> </Button>
</div> </div>
) : null; ) : null;
@@ -103,7 +103,7 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
description={i18n.t('deploy.userGroup.createFirstHint')} description={i18n.t('deploy.userGroup.createFirstHint')}
action={( action={(
<Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}> <Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新建用户组 <Plus className="w-4 h-4 mr-1" />{i18n.t("common.newUserGroup")}
</Button> </Button>
)} )}
/> />
@@ -204,10 +204,10 @@ export default function UserGroupsManager({ embedded = false }: { embedded?: boo
actions={( actions={(
<> <>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadGroups}> <Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadGroups}>
<RefreshCw className="w-4 h-4 mr-1" />刷新 <RefreshCw className="w-4 h-4 mr-1" />{i18n.t("common.refresh")}
</Button> </Button>
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}> <Button size="sm" className="dg-btn-primary" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新建用户组 <Plus className="w-4 h-4 mr-1" />{i18n.t("common.newUserGroup")}
</Button> </Button>
</> </>
)} )}
@@ -157,7 +157,7 @@ export function WorkspaceTermDialog({
style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '900px', width: '90vw' }} style={fullscreen ? { maxWidth: '100vw', width: '100vw', height: '100vh' } : { maxWidth: '900px', width: '90vw' }}
> >
<DialogTitle className="sr-only">Workspace 终端 #{buildNumber}</DialogTitle> <DialogTitle className="sr-only">Workspace 终端 #{buildNumber}</DialogTitle>
<DialogDescription className="sr-only">流水线构建目录终端</DialogDescription> <DialogDescription className="sr-only">{t("common.buildTerminal")}</DialogDescription>
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]"> <div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
<div className="flex items-center gap-2 text-sm min-w-0"> <div className="flex items-center gap-2 text-sm min-w-0">
<div className={cn('w-2 h-2 rounded-full shrink-0', connected ? 'bg-emerald-400' : 'bg-red-400')} /> <div className={cn('w-2 h-2 rounded-full shrink-0', connected ? 'bg-emerald-400' : 'bg-red-400')} />
@@ -297,7 +297,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" /> className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">安全模式</Label> <Label className="text-[var(--dg-text-secondary)] text-xs">{t("common.securityMode")}</Label>
<Select value={rdpSecurity} onValueChange={setRdpSecurity}> <Select value={rdpSecurity} onValueChange={setRdpSecurity}>
<SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"> <SelectTrigger className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
<SelectValue /> <SelectValue />
@@ -305,7 +305,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
<SelectContent> <SelectContent>
<SelectItem value="nla">NLA</SelectItem> <SelectItem value="nla">NLA</SelectItem>
<SelectItem value="tls">TLS</SelectItem> <SelectItem value="tls">TLS</SelectItem>
<SelectItem value="rdp">标准 RDP</SelectItem> <SelectItem value="rdp">{t("common.standardRdp")}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -313,7 +313,7 @@ export function CredFormDialog({ open, onClose, onSaved, editData }: CredFormDia
<div><FieldWithToggle label={t('deploy.rdpPassword')} value={rdpPassword} onChange={setRdpPassword} <div><FieldWithToggle label={t('deploy.rdpPassword')} value={rdpPassword} onChange={setRdpPassword}
placeholder={t('deploy.rdpPasswordPlaceholder')} fieldKey="rdp_pwd" required /></div> placeholder={t('deploy.rdpPasswordPlaceholder')} fieldKey="rdp_pwd" required /></div>
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-[var(--dg-text-secondary)] text-xs">域</Label> <Label className="text-[var(--dg-text-secondary)] text-xs">{t("common.domainLabel")}</Label>
<Input value={rdpDomain} onChange={e => setRdpDomain(e.target.value)} placeholder={t('common.optional')} <Input value={rdpDomain} onChange={e => setRdpDomain(e.target.value)} placeholder={t('common.optional')}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" /> className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" />
</div> </div>
@@ -207,7 +207,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<ScrollArea className="h-[140px] rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)]"> <ScrollArea className="h-[140px] rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)]">
{loadingServers ? ( {loadingServers ? (
<div className="flex items-center justify-center py-8 text-xs text-[var(--dg-text-muted)]"> <div className="flex items-center justify-center py-8 text-xs text-[var(--dg-text-muted)]">
<Loader2 className="h-4 w-4 animate-spin mr-1.5" />加载中 <Loader2 className="h-4 w-4 animate-spin mr-1.5" />{i18n.t("common.loadingEllipsis")}
</div> </div>
) : filteredServers.length === 0 ? ( ) : filteredServers.length === 0 ? (
<p className="text-xs text-[var(--dg-text-muted)] text-center py-8"> {i18n.t('pipeline.deployTargetField.noHostAvailable')} </p> <p className="text-xs text-[var(--dg-text-muted)] text-center py-8"> {i18n.t('pipeline.deployTargetField.noHostAvailable')} </p>
@@ -315,7 +315,7 @@ export function DeployTargetFields({ value, onChange, requireTarget = true }: De
<ScrollArea className="h-[140px] rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)]"> <ScrollArea className="h-[140px] rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)]">
{loadingDeps ? ( {loadingDeps ? (
<div className="flex items-center justify-center py-8 text-xs text-[var(--dg-text-muted)]"> <div className="flex items-center justify-center py-8 text-xs text-[var(--dg-text-muted)]">
<Loader2 className="h-4 w-4 animate-spin mr-1.5" />加载 Deployment <Loader2 className="h-4 w-4 animate-spin mr-1.5" />{i18n.t("common.loadDeployment")}
</div> </div>
) : filteredDeployments.length === 0 ? ( ) : filteredDeployments.length === 0 ? (
<p className="text-xs text-[var(--dg-text-muted)] text-center py-8"> {i18n.t('k8s.workloads.noDeployment')} </p> <p className="text-xs text-[var(--dg-text-muted)] text-center py-8"> {i18n.t('k8s.workloads.noDeployment')} </p>
@@ -399,7 +399,7 @@ export function PipelineCreateDialog({ open, onOpenChange, onCreated }: Pipeline
<SelectValue placeholder={t('deploy.dockerCredPlaceholder')} /> <SelectValue placeholder={t('deploy.dockerCredPlaceholder')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="__none">不关联</SelectItem> <SelectItem value="__none">{t("common.notLinked")}</SelectItem>
{dockerCredentials.map((c) => ( {dockerCredentials.map((c) => (
<SelectItem key={c.id as number} value={String(c.id)}> <SelectItem key={c.id as number} value={String(c.id)}>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
@@ -92,7 +92,7 @@ export default function CapacityPage() {
<Select value={selectedNs} onValueChange={setSelectedNs}> <Select value={selectedNs} onValueChange={setSelectedNs}>
<SelectTrigger className="w-32 h-9 dg-input"><SelectValue placeholder={i18n.t('k8s.capacity.namespace')} /></SelectTrigger> <SelectTrigger className="w-32 h-9 dg-input"><SelectValue placeholder={i18n.t('k8s.capacity.namespace')} /></SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all">所有命名空间</SelectItem> <SelectItem value="all">{i18n.t("common.allNamespaces")}</SelectItem>
{namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)} {namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)}
</SelectContent> </SelectContent>
</Select> </Select>
@@ -111,9 +111,9 @@ export default function CapacityPage() {
<Table> <Table>
<TableHeader><TableRow className="border-[var(--dg-border)] hover:bg-transparent"> <TableHeader><TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="dg-text-secondary">Deployment</TableHead> <TableHead className="dg-text-secondary">Deployment</TableHead>
<TableHead className="dg-text-secondary">命名空间</TableHead> <TableHead className="dg-text-secondary">{i18n.t("common.namespaceLabel")}</TableHead>
<TableHead className="dg-text-secondary">副本</TableHead> <TableHead className="dg-text-secondary">{i18n.t("common.replicasLabel")}</TableHead>
<TableHead className="dg-text-secondary">容器</TableHead> <TableHead className="dg-text-secondary">{i18n.t("common.containerLabel")}</TableHead>
<TableHead className="dg-text-secondary"><Cpu className="h-3 w-3 inline mr-1" />CPU Req</TableHead> <TableHead className="dg-text-secondary"><Cpu className="h-3 w-3 inline mr-1" />CPU Req</TableHead>
<TableHead className="dg-text-secondary"><Cpu className="h-3 w-3 inline mr-1" />CPU Lim</TableHead> <TableHead className="dg-text-secondary"><Cpu className="h-3 w-3 inline mr-1" />CPU Lim</TableHead>
<TableHead className="dg-text-secondary"><HardDrive className="h-3 w-3 inline mr-1" />Mem Req</TableHead> <TableHead className="dg-text-secondary"><HardDrive className="h-3 w-3 inline mr-1" />Mem Req</TableHead>
@@ -107,7 +107,7 @@ export default function ClusterOverviewPage() {
{/* 节点列表 // Logan.Li */} {/* 节点列表 // Logan.Li */}
<div> <div>
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)] mb-3 flex items-center gap-2"> <h2 className="text-lg font-semibold text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
<Cpu className="h-4 w-4 text-[var(--dg-blue)]" />节点列表 <Cpu className="h-4 w-4 text-[var(--dg-blue)]" />{i18n.t("common.nodeList")}
</h2> </h2>
<ContentPanel> <ContentPanel>
<Table> <Table>
@@ -185,7 +185,7 @@ export default function ClusterOverviewPage() {
<div> <div>
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)] flex items-center gap-2"> <h2 className="text-lg font-semibold text-[var(--dg-text-primary)] flex items-center gap-2">
<Boxes className="h-4 w-4 text-[var(--dg-green)]" />命名空间 <Boxes className="h-4 w-4 text-[var(--dg-green)]" />{i18n.t("common.namespaceLabel")}
</h2> </h2>
<Link to={`/kubernetes/${clusterCode}/namespaces`} className="text-sm text-[var(--dg-blue)] hover:text-[var(--dg-blue)] transition-colors flex items-center gap-1"> <Link to={`/kubernetes/${clusterCode}/namespaces`} className="text-sm text-[var(--dg-blue)] hover:text-[var(--dg-blue)] transition-colors flex items-center gap-1">
管理全部 <ChevronRight className="h-3 w-3" /> 管理全部 <ChevronRight className="h-3 w-3" />
@@ -138,7 +138,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
<Card className="dg-card"> <Card className="dg-card">
<CardContent className="p-12 text-center"> <CardContent className="p-12 text-center">
<XCircle className="h-8 w-8 mx-auto dg-text-red" /> <XCircle className="h-8 w-8 mx-auto dg-text-red" />
<p className="dg-text-secondary mt-3">未找到 Deployment: {deploymentName}</p> <p className="dg-text-secondary mt-3">{i18n.t('common.notFoundDeploy')}: {deploymentName}</p>
<Button variant="outline" className="mt-4" onClick={onBack}> {i18n.t('common.back')} </Button> <Button variant="outline" className="mt-4" onClick={onBack}> {i18n.t('common.back')} </Button>
</CardContent> </CardContent>
</Card> </Card>
@@ -179,7 +179,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac
Pod 列表 ({pods.length}) Pod 列表 ({pods.length})
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="logs" className="data-[state=active]:bg-[var(--dg-bg-active)]"> <TabsTrigger value="logs" className="data-[state=active]:bg-[var(--dg-bg-active)]">
<FileText className="h-4 w-4 mr-1" />日志 <FileText className="h-4 w-4 mr-1" />{i18n.t("common.podLogs")}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="hpa" className="data-[state=active]:bg-[var(--dg-bg-active)]"> <TabsTrigger value="hpa" className="data-[state=active]:bg-[var(--dg-bg-active)]">
<Gauge className="h-4 w-4 mr-1" />HPA ({hpas.length}) <Gauge className="h-4 w-4 mr-1" />HPA ({hpas.length})
@@ -149,7 +149,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
className="dg-text-red hover:dg-text-red ml-1"><Trash2 className="h-3 w-3" /></button> className="dg-text-red hover:dg-text-red ml-1"><Trash2 className="h-3 w-3" /></button>
</div> </div>
))} ))}
{imagePullSecrets.length === 0 && <span className="text-[11px] dg-text-muted">无</span>} {imagePullSecrets.length === 0 && <span className="text-[11px] dg-text-muted">{i18n.t("common.none")}</span>}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Input placeholder={i18n.t('k8s.editContainers.secretPlaceholder')} className="dg-input text-xs h-8" <Input placeholder={i18n.t('k8s.editContainers.secretPlaceholder')} className="dg-input text-xs h-8"
@@ -161,7 +161,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
}} /> }} />
<Button size="sm" variant="outline" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" <Button size="sm" variant="outline" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs"
onClick={() => setImagePullSecrets(prev => [...prev, ''])}> onClick={() => setImagePullSecrets(prev => [...prev, ''])}>
<Plus className="h-3 w-3 mr-1" />新增 <Plus className="h-3 w-3 mr-1" />{i18n.t("common.add")}
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
@@ -195,8 +195,8 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam
{/* 保存栏 */} {/* 保存栏 */}
<div className="sticky bottom-0 bg-[var(--dg-bg-primary)]/80 backdrop-blur border-t border-[var(--dg-border)] p-3 flex items-center justify-between"> <div className="sticky bottom-0 bg-[var(--dg-bg-primary)]/80 backdrop-blur border-t border-[var(--dg-border)] p-3 flex items-center justify-between">
<div className="text-[11px] dg-text-muted"> <div className="text-[11px] dg-text-muted">
共 <span className="text-[var(--dg-text-secondary)] font-semibold">{initContainers.length}</span> 个初始化容器 + {i18n.t("common.total")}: <span className="text-[var(--dg-text-secondary)] font-semibold">{initContainers.length}</span> {i18n.t("common.totalContainers")} +
<span className="text-[var(--dg-text-secondary)] font-semibold ml-1">{containers.length}</span> 个工作容器 <span className="text-[var(--dg-text-secondary)] font-semibold ml-1">{containers.length}</span> {i18n.t("common.workContainers")}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onCancel}> {i18n.t('rbac.jobRole.cancel')} </Button> <Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={onCancel}> {i18n.t('rbac.jobRole.cancel')} </Button>
@@ -266,7 +266,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
)} )}
</div> </div>
<Button size="sm" variant="ghost" className="h-6 ml-auto text-xs dg-text-secondary" onClick={load}> <Button size="sm" variant="ghost" className="h-6 ml-auto text-xs dg-text-secondary" onClick={load}>
<RefreshCw className="h-3 w-3 mr-1" />刷新 <RefreshCw className="h-3 w-3 mr-1" />{i18n.t("common.refresh")}
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
@@ -286,7 +286,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
setSvcForm({ name: `${deploymentName}-svc`, type: 'ClusterIP', ports: [{ name: 'http', port: '8080', targetPort: '8080', protocol: 'TCP', nodePort: '' }] }); setSvcForm({ name: `${deploymentName}-svc`, type: 'ClusterIP', ports: [{ name: 'http', port: '8080', targetPort: '8080', protocol: 'TCP', nodePort: '' }] });
setShowCreate('service'); setShowCreate('service');
}}> }}>
<Plus className="h-3 w-3 mr-1" />创建 Service <Plus className="h-3 w-3 mr-1" />{i18n.t("common.createService")}
</Button> </Button>
</div> </div>
<p className="text-xs dg-text-muted mt-1"> {i18n.t('kubernetes.deploymentEditService.selectorHint')} </p> <p className="text-xs dg-text-muted mt-1"> {i18n.t('kubernetes.deploymentEditService.selectorHint')} </p>
@@ -338,7 +338,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
setIngForm(s => ({ ...s, name: `${deploymentName}-ingress`, svcPort: String(matchedSvcs[0]?.ports[0]?.port || 80) })); setIngForm(s => ({ ...s, name: `${deploymentName}-ingress`, svcPort: String(matchedSvcs[0]?.ports[0]?.port || 80) }));
setShowCreate('ingress'); setShowCreate('ingress');
}}> }}>
<Plus className="h-3 w-3 mr-1" />创建 Ingress <Plus className="h-3 w-3 mr-1" />{i18n.t("common.createIngress")}
</Button> </Button>
</div> </div>
<p className="text-xs dg-text-muted mt-1">backend 指向关联 Service 的 Ingress 自动高亮</p> <p className="text-xs dg-text-muted mt-1">backend 指向关联 Service 的 Ingress 自动高亮</p>
@@ -202,7 +202,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
<p className="text-[11px] dg-text-muted mt-1"> {i18n.t('kubernetes.storage.volumeDesc')} </p> <p className="text-[11px] dg-text-muted mt-1"> {i18n.t('kubernetes.storage.volumeDesc')} </p>
</div> </div>
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs shrink-0" onClick={addEntry}> <Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs shrink-0" onClick={addEntry}>
<Plus className="h-3 w-3 mr-1" />添加数据卷 <Plus className="h-3 w-3 mr-1" />{i18n.t("common.addVolume")}
</Button> </Button>
</div> </div>
</CardHeader> </CardHeader>
@@ -285,7 +285,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName,
将上述数据卷挂载到容器 将上述数据卷挂载到容器
</p> </p>
<Button size="sm" variant="ghost" className="h-6 px-2 text-[11px] dg-text-secondary hover:text-[var(--dg-text-primary)]" onClick={() => addMount(i)}> <Button size="sm" variant="ghost" className="h-6 px-2 text-[11px] dg-text-secondary hover:text-[var(--dg-text-primary)]" onClick={() => addMount(i)}>
<Plus className="h-3 w-3 mr-0.5" />添加挂载 <Plus className="h-3 w-3 mr-0.5" />{i18n.t("common.addMount")}
</Button> </Button>
</div> </div>
{entry.mounts.map((mount, mi) => ( {entry.mounts.map((mount, mi) => (
@@ -266,7 +266,7 @@ export default function EventsPage() {
<ContentPanel <ContentPanel
toolbar={( toolbar={(
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<span className="text-sm dg-text-secondary">事件列表 ({displayEvents.length})</span> <span className="text-sm dg-text-secondary">{i18n.t('common.events')} ({displayEvents.length})</span>
<Select value={filterType} onValueChange={setFilterType}> <Select value={filterType} onValueChange={setFilterType}>
<SelectTrigger className="w-28 h-8 dg-input text-xs"><SelectValue /></SelectTrigger> <SelectTrigger className="w-28 h-8 dg-input text-xs"><SelectValue /></SelectTrigger>
<SelectContent> <SelectContent>
@@ -143,7 +143,7 @@ export default function NamespaceManagementPage() {
<Dialog open={createOpen} onOpenChange={setCreateOpen}> <Dialog open={createOpen} onOpenChange={setCreateOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button size="sm" className="dg-btn-primary"> <Button size="sm" className="dg-btn-primary">
<Plus className="h-4 w-4 mr-1" />创建命名空间 <Plus className="h-4 w-4 mr-1" />{i18n.t("common.createNamespace")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
@@ -194,11 +194,11 @@ export default function NamespaceManagementPage() {
<TableBody> <TableBody>
{loading ? ( {loading ? (
<TableRow><TableCell colSpan={9} className="text-center text-[var(--dg-text-muted)] py-12"> <TableRow><TableCell colSpan={9} className="text-center text-[var(--dg-text-muted)] py-12">
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中... <Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />{i18n.t("common.loading")}
</TableCell></TableRow> </TableCell></TableRow>
) : filtered.length === 0 ? ( ) : filtered.length === 0 ? (
<TableRow><TableCell colSpan={9} className="text-center text-[var(--dg-text-muted)] py-12"> <TableRow><TableCell colSpan={9} className="text-center text-[var(--dg-text-muted)] py-12">
<Boxes className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无命名空间 <Boxes className="h-8 w-8 mx-auto mb-2 opacity-50" />{i18n.t("common.noNamespace")}
</TableCell></TableRow> </TableCell></TableRow>
) : ( ) : (
filtered.map((ns) => { filtered.map((ns) => {
@@ -226,7 +226,7 @@ export default function NamespaceManagementPage() {
<TableCell className="text-center"> <TableCell className="text-center">
{ns.has_resource_quota ? ( {ns.has_resource_quota ? (
<Badge className="bg-[var(--dg-orange)]/20 text-[var(--dg-orange)]" title={JSON.stringify(ns.resource_quotas?.[0]?.status)}> <Badge className="bg-[var(--dg-orange)]/20 text-[var(--dg-orange)]" title={JSON.stringify(ns.resource_quotas?.[0]?.status)}>
<Shield className="h-3 w-3 mr-1" />有 <Shield className="h-3 w-3 mr-1" />{i18n.t("common.hasLabel")}
</Badge> </Badge>
) : ( ) : (
<span className="text-xs text-[var(--dg-text-muted)]">-</span> <span className="text-xs text-[var(--dg-text-muted)]">-</span>
@@ -273,13 +273,13 @@ export default function NamespaceManagementPage() {
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle className="text-[var(--dg-red)] flex items-center gap-2"> <DialogTitle className="text-[var(--dg-red)] flex items-center gap-2">
<AlertTriangle className="h-5 w-5" />删除命名空间 <AlertTriangle className="h-5 w-5" />{i18n.t("common.deleteNamespace")}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="space-y-3 py-4"> <div className="space-y-3 py-4">
<div className="bg-[var(--dg-red)]/10 border border-[var(--dg-red)]/30 rounded-lg p-3"> <div className="bg-[var(--dg-red)]/10 border border-[var(--dg-red)]/30 rounded-lg p-3">
<p className="text-sm text-[var(--dg-red)]"> <p className="text-sm text-[var(--dg-red)]">
⚠️ 即将删除命名空间 <span className="font-bold">{deleteTarget}</span> 及其中所有资源。 {i18n.t('common.deleteNamespaceWarn')} <span className="font-bold">{deleteTarget}</span> {i18n.t('common.allResources')}
</p> </p>
<p className="text-xs text-[var(--dg-red)]/70 mt-1"> <p className="text-xs text-[var(--dg-red)]/70 mt-1">
此操作不可逆,所有 Deployment/Pod/Service/ConfigMap 等资源将被永久删除。 此操作不可逆,所有 Deployment/Pod/Service/ConfigMap 等资源将被永久删除。
@@ -167,7 +167,7 @@ export default function NamespaceResourcePage() {
} }
}; };
if (!config) return <div className="text-[var(--dg-text-secondary)] p-8 text-center">未知资源类型: {type}</div>; if (!config) return <div className="text-[var(--dg-text-secondary)] p-8 text-center">{i18n.t('common.unknownResource', { type })}</div>;
const searchToolbar = ( const searchToolbar = (
<div className="relative w-56 flex-1 min-w-[200px] max-w-xs"> <div className="relative w-56 flex-1 min-w-[200px] max-w-xs">
@@ -214,16 +214,16 @@ export default function NamespaceResourcePage() {
)} )}
{EXPORT_YAML_TYPES.includes(type) && ( {EXPORT_YAML_TYPES.includes(type) && (
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportYaml} title={i18n.t('k8s.nsResourcePage.exportYaml')}> <Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={handleExportYaml} title={i18n.t('k8s.nsResourcePage.exportYaml')}>
<Download className="h-3 w-3 mr-1" />导出YAML <Download className="h-3 w-3 mr-1" />{i18n.t("common.exportYaml")}
</Button> </Button>
)} )}
{(VISUAL_RESOURCE_TYPES.includes(type) || DEPLOYMENT_LIST_TYPES.includes(type)) && ( {(VISUAL_RESOURCE_TYPES.includes(type) || DEPLOYMENT_LIST_TYPES.includes(type)) && (
<> <>
<Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={() => setShowYamlImport(true)}> <Button variant="outline" size="sm" className="dg-btn-secondary text-xs" onClick={() => setShowYamlImport(true)}>
<Upload className="h-3 w-3 mr-1" />导入 <Upload className="h-3 w-3 mr-1" />{i18n.t("common.import")}
</Button> </Button>
<Button size="sm" className="dg-btn-primary text-xs" onClick={handleCreate}> <Button size="sm" className="dg-btn-primary text-xs" onClick={handleCreate}>
<Plus className="h-3 w-3 mr-1" />新建 <Plus className="h-3 w-3 mr-1" />{i18n.t("common.buildNew")}
</Button> </Button>
</> </>
)} )}
@@ -267,7 +267,7 @@ export default function NamespaceResourcePage() {
{loading ? ( {loading ? (
<TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" /> {i18n.t('monitor.logPanel.logResults.loading')} </TableCell></TableRow> <TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" /> {i18n.t('monitor.logPanel.logResults.loading')} </TableCell></TableRow>
) : filtered.length === 0 ? ( ) : filtered.length === 0 ? (
<TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><config.icon className="h-8 w-8 mx-auto mb-2 opacity-50" />暂无 {config.title}</TableCell></TableRow> <TableRow><TableCell colSpan={config.columns.length} className="text-center text-[var(--dg-text-muted)] py-12"><config.icon className="h-8 w-8 mx-auto mb-2 opacity-50" />{i18n.t('common.noData')} {config.title}</TableCell></TableRow>
) : ( ) : (
filtered.map((item) => config.renderRow(item, { clusterCode: clusterCode || '', namespace: namespace || '', onDetail: handleDetail, onYaml: handleYaml, onEdit: handleEdit, onTerminal: (p: any) => setTerminalPod(p), onFileBrowser: (p: any) => setFileBrowserPod(p), onDeletePod: handleDeletePod, onPodDetail: (p: any) => setPodDetail(p) })) filtered.map((item) => config.renderRow(item, { clusterCode: clusterCode || '', namespace: namespace || '', onDetail: handleDetail, onYaml: handleYaml, onEdit: handleEdit, onTerminal: (p: any) => setTerminalPod(p), onFileBrowser: (p: any) => setFileBrowserPod(p), onDeletePod: handleDeletePod, onPodDetail: (p: any) => setPodDetail(p) }))
)} )}
@@ -172,7 +172,7 @@ export function PodFileBrowser({
<div className="flex items-center justify-between p-3 border-b border-[var(--dg-border)] shrink-0 gap-3"> <div className="flex items-center justify-between p-3 border-b border-[var(--dg-border)] shrink-0 gap-3">
<div className="flex items-center gap-2 min-w-0 flex-1"> <div className="flex items-center gap-2 min-w-0 flex-1">
<HardDrive className="h-4 w-4 shrink-0 dg-text-yellow" /> <HardDrive className="h-4 w-4 shrink-0 dg-text-yellow" />
<span className="text-sm font-semibold text-[var(--dg-text-primary)] truncate" title={podName}>文件: {podName}</span> <span className="text-sm font-semibold text-[var(--dg-text-primary)] truncate" title={podName}>{i18n.t("common.fileLabel")}: {podName}</span>
<Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary text-[10px] shrink-0">{namespace}</Badge> <Badge variant="outline" className="border-[var(--dg-border)] dg-text-secondary text-[10px] shrink-0">{namespace}</Badge>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -125,8 +125,8 @@ export default function PodMetricsPage() {
<Table> <Table>
<TableHeader><TableRow className="border-[var(--dg-border)] hover:bg-transparent"> <TableHeader><TableRow className="border-[var(--dg-border)] hover:bg-transparent">
<TableHead className="dg-text-secondary">Pod</TableHead> <TableHead className="dg-text-secondary">Pod</TableHead>
<TableHead className="dg-text-secondary">命名空间</TableHead> <TableHead className="dg-text-secondary">{i18n.t("common.namespaceLabel")}</TableHead>
<TableHead className="dg-text-secondary">容器</TableHead> <TableHead className="dg-text-secondary">{i18n.t("common.containerLabel")}</TableHead>
<TableHead className="dg-text-secondary"><Cpu className="h-3 w-3 inline mr-1" />CPU</TableHead> <TableHead className="dg-text-secondary"><Cpu className="h-3 w-3 inline mr-1" />CPU</TableHead>
<TableHead className="dg-text-secondary"><HardDrive className="h-3 w-3 inline mr-1" />Memory</TableHead> <TableHead className="dg-text-secondary"><HardDrive className="h-3 w-3 inline mr-1" />Memory</TableHead>
</TableRow></TableHeader> </TableRow></TableHeader>
@@ -153,12 +153,12 @@ export default function WorkloadViewPage() {
actions={( actions={(
<> <>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => navigate(-1)}> <Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => navigate(-1)}>
<ArrowLeft className="h-4 w-4 mr-1" />返回 <ArrowLeft className="h-4 w-4 mr-1" />{i18n.t("common.back")}
</Button> </Button>
{modeTabs} {modeTabs}
{isSts && ( {isSts && (
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => { setScaleValue(replicas); setScaleModal(true); }}> <Button variant="outline" size="sm" className="dg-btn-secondary" onClick={() => { setScaleValue(replicas); setScaleModal(true); }}>
<Boxes className="h-3.5 w-3.5 mr-1" />伸缩 <Boxes className="h-3.5 w-3.5 mr-1" />{i18n.t("common.scale")}
</Button> </Button>
)} )}
<Button variant="outline" size="sm" className="dg-btn-secondary" disabled={actionLoading} onClick={handleRestart}> <Button variant="outline" size="sm" className="dg-btn-secondary" disabled={actionLoading} onClick={handleRestart}>
@@ -225,7 +225,7 @@ export default function WorkloadViewPage() {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2"> <CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Database className="h-4 w-4 text-[var(--dg-cyan)]" />存储卷声明模板 (VolumeClaimTemplates) <Database className="h-4 w-4 text-[var(--dg-cyan)]" />{i18n.t("common.volumeClaimTemplates")}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-0"> <CardContent className="pt-0">
@@ -262,7 +262,7 @@ export default function WorkloadViewPage() {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2"> <CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Container className="h-4 w-4 text-[var(--dg-blue)]" />容器模板(Pod Template) <Container className="h-4 w-4 text-[var(--dg-blue)]" />{i18n.t("common.containerTemplate")}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-0"> <CardContent className="pt-0">
@@ -57,34 +57,34 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
return ( return (
<> <>
<Button variant="outline" size="sm" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={() => setOpen(true)}> <Button variant="outline" size="sm" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]" onClick={() => setOpen(true)}>
<Plus className="h-3.5 w-3.5 mr-1" />创建 HPA <Plus className="h-3.5 w-3.5 mr-1" />{i18n.t("common.createHpa")}
</Button> </Button>
{open && ( {open && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={() => setOpen(false)}> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={() => setOpen(false)}>
<div className="bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-xl w-[480px] max-w-[92vw] max-h-[85vh] flex flex-col" onClick={(e) => e.stopPropagation()}> <div className="bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-xl w-[480px] max-w-[92vw] max-h-[85vh] flex flex-col" onClick={(e) => e.stopPropagation()}>
<h2 className="text-lg font-bold text-[var(--dg-text-primary)] px-6 pt-6 pb-4 shrink-0">创建 HPA</h2> <h2 className="text-lg font-bold text-[var(--dg-text-primary)] px-6 pt-6 pb-4 shrink-0">{i18n.t("common.createHpaTitle")}</h2>
<div className="flex-1 min-h-0 overflow-y-auto px-6 space-y-3"> <div className="flex-1 min-h-0 overflow-y-auto px-6 space-y-3">
<div> <div>
<Label className="dg-text-secondary text-sm">名称</Label> <Label className="dg-text-secondary text-sm">{i18n.t("common.name")}</Label>
<Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} <Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder={`hpa-${deploymentName}`} placeholder={`hpa-${deploymentName}`}
className="dg-input mt-1" /> className="dg-input mt-1" />
</div> </div>
<div> <div>
<Label className="dg-text-secondary text-sm">目标</Label> <Label className="dg-text-secondary text-sm">{i18n.t("common.target")}</Label>
<p className="text-[var(--dg-text-secondary)] text-sm mt-1 bg-[var(--dg-bg-tertiary)] rounded px-3 py-2 truncate" title={`Deployment: ${deploymentName} · 命名空间: ${namespace}`}> <p className="text-[var(--dg-text-secondary)] text-sm mt-1 bg-[var(--dg-bg-tertiary)] rounded px-3 py-2 truncate" title={`Deployment: ${deploymentName} · 命名空间: ${namespace}`}>
Deployment: {deploymentName} · 命名空间: {namespace} Deployment: {deploymentName} · 命名空间: {namespace}
</p> </p>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="dg-text-secondary text-sm">最小副本数</Label> <Label className="dg-text-secondary text-sm">{i18n.t("common.minReplicas")}</Label>
<Input type="number" value={form.minReplicas} min={1} <Input type="number" value={form.minReplicas} min={1}
onChange={(e) => setForm({ ...form, minReplicas: Number(e.target.value) })} onChange={(e) => setForm({ ...form, minReplicas: Number(e.target.value) })}
className="dg-input mt-1" /> className="dg-input mt-1" />
</div> </div>
<div> <div>
<Label className="dg-text-secondary text-sm">最大副本数</Label> <Label className="dg-text-secondary text-sm">{i18n.t("common.maxReplicas")}</Label>
<Input type="number" value={form.maxReplicas} min={1} <Input type="number" value={form.maxReplicas} min={1}
onChange={(e) => setForm({ ...form, maxReplicas: Number(e.target.value) })} onChange={(e) => setForm({ ...form, maxReplicas: Number(e.target.value) })}
className="dg-input mt-1" /> className="dg-input mt-1" />
@@ -98,7 +98,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
className="dg-input mt-1" /> className="dg-input mt-1" />
</div> </div>
<div> <div>
<Label className="dg-text-secondary text-sm">{i18n.t('k8s.misc.memory1')} <span className="dg-text-muted">可选</span></Label> <Label className="dg-text-secondary text-sm">{i18n.t('k8s.misc.memory1')} <span className="dg-text-muted">{i18n.t("common.optional")}</span></Label>
<Input type="number" value={form.memPercent} min={0} max={100} <Input type="number" value={form.memPercent} min={0} max={100}
onChange={(e) => setForm({ ...form, memPercent: Number(e.target.value) })} onChange={(e) => setForm({ ...form, memPercent: Number(e.target.value) })}
className="dg-input mt-1" /> className="dg-input mt-1" />
@@ -106,7 +106,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
</div> </div>
</div> </div>
<div className="flex justify-end gap-2 px-6 py-4 border-t border-[var(--dg-border)] shrink-0"> <div className="flex justify-end gap-2 px-6 py-4 border-t border-[var(--dg-border)] shrink-0">
<Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={() => setOpen(false)}>取消</Button> <Button variant="outline" className="border-[var(--dg-border)] dg-text-secondary" onClick={() => setOpen(false)}>{i18n.t("common.cancel")}</Button>
<Button className="bg-blue-600 hover:bg-blue-700" disabled={loading} onClick={handleCreate}> <Button className="bg-blue-600 hover:bg-blue-700" disabled={loading} onClick={handleCreate}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : i18n.t('common.create')} {loading ? <Loader2 className="h-4 w-4 animate-spin" /> : i18n.t('common.create')}
</Button> </Button>
@@ -1,7 +1,8 @@
// HostSettingsCard.tsx — 主机设置 (Host Namespace) 卡片 (Q21 提取) // HostSettingsCard.tsx — {i18n.t("common.hostSettings")} 卡片 (Q21 提取)
// hostNetwork / hostIPC / hostPID / shareProcessNamespace // hostNetwork / hostIPC / hostPID / shareProcessNamespace
import { Network } from 'lucide-react'; import { Network } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import i18n from '@/i18n';
interface HostSettingsCardProps { interface HostSettingsCardProps {
hostNetwork: boolean; hostNetwork: boolean;
@@ -24,7 +25,7 @@ export function HostSettingsCard({
<Card className="dg-card"> <Card className="dg-card">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2"> <CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Network className="h-4 w-4 dg-text-cyan" />主机设置 (Host Namespace) <Network className="h-4 w-4 dg-text-cyan" />{i18n.t("common.hostSettings")}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-0 grid grid-cols-2 gap-3"> <CardContent className="pt-0 grid grid-cols-2 gap-3">
@@ -22,7 +22,7 @@ export function TolerationsCard({ tolerations, onAdd, onRemove, onUpdate }: Tole
<CardTitle className="text-sm text-[var(--dg-text-primary)]"> {i18n.t('kubernetes.deploymentEditAdvanced.tolerations')} </CardTitle> <CardTitle className="text-sm text-[var(--dg-text-primary)]"> {i18n.t('kubernetes.deploymentEditAdvanced.tolerations')} </CardTitle>
<Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs" <Button size="sm" variant="outline" className="h-7 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-xs"
onClick={onAdd}> onClick={onAdd}>
<Plus className="h-3 w-3 mr-1" />添加 <Plus className="h-3 w-3 mr-1" />{i18n.t("common.add")}
</Button> </Button>
</div> </div>
</CardHeader> </CardHeader>
@@ -69,7 +69,7 @@ export function UpdateStrategyCard({
<Card className="dg-card"> <Card className="dg-card">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2"> <CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Clock className="h-4 w-4 dg-text-yellow" />历史版本 &amp; 进度 <Clock className="h-4 w-4 dg-text-yellow" />{i18n.t("common.historyVersion")}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-0 grid grid-cols-2 gap-3"> <CardContent className="pt-0 grid grid-cols-2 gap-3">
@@ -19,7 +19,7 @@ export function IngressCreateForm({ ingForm, setIngForm, deploymentName, onCance
return ( return (
<div className="mt-3 border border-purple-800/50 rounded-lg p-3 bg-purple-950/20 space-y-2"> <div className="mt-3 border border-purple-800/50 rounded-lg p-3 bg-purple-950/20 space-y-2">
<div className="text-xs font-semibold text-purple-300 flex items-center gap-1.5"> <div className="text-xs font-semibold text-purple-300 flex items-center gap-1.5">
<Plus className="h-3 w-3" />创建新 Ingress <Plus className="h-3 w-3" />{i18n.t("common.newInstanceIngress")}
</div> </div>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<div> <div>
@@ -23,7 +23,7 @@ export function IngressListItem({ ing, matched, onEdit, onDelete }: Props) {
{matched && <Badge className="bg-purple-500/20 text-purple-300 border-purple-500/30 text-[10px]"> {i18n.t('kubernetes.deploymentEditService.associated')} </Badge>} {matched && <Badge className="bg-purple-500/20 text-purple-300 border-purple-500/30 text-[10px]"> {i18n.t('kubernetes.deploymentEditService.associated')} </Badge>}
<Button size="sm" variant="ghost" className="h-6 text-xs dg-text-green ml-auto" <Button size="sm" variant="ghost" className="h-6 text-xs dg-text-green ml-auto"
onClick={onEdit}> onClick={onEdit}>
<Pencil className="h-3 w-3 mr-1" />编辑 <Pencil className="h-3 w-3 mr-1" />{i18n.t("common.editAction")}
</Button> </Button>
<Button size="sm" variant="ghost" className="h-6 w-6 p-0 dg-text-red hover:dg-text-red" <Button size="sm" variant="ghost" className="h-6 w-6 p-0 dg-text-red hover:dg-text-red"
title={i18n.t('kubernetes.ingress.deleteIngress')} onClick={onDelete}> title={i18n.t('kubernetes.ingress.deleteIngress')} onClick={onDelete}>
@@ -44,7 +44,7 @@ export function ServiceCreateForm({ svcForm, setSvcForm, selectorLabels, deploym
return ( return (
<div className="mt-3 border border-blue-800/50 rounded-lg p-3 bg-blue-950/20 space-y-3"> <div className="mt-3 border border-blue-800/50 rounded-lg p-3 bg-blue-950/20 space-y-3">
<div className="text-xs font-semibold text-blue-300 flex items-center gap-1.5"> <div className="text-xs font-semibold text-blue-300 flex items-center gap-1.5">
<Plus className="h-3 w-3" />创建新 Service <Plus className="h-3 w-3" />{i18n.t("common.newInstanceService")}
</div> </div>
{/* 基本信息 */} {/* 基本信息 */}
@@ -122,7 +122,7 @@ export function ServiceCreateForm({ svcForm, setSvcForm, selectorLabels, deploym
{/* 添加自定义端口 */} {/* 添加自定义端口 */}
<Button size="sm" variant="outline" className="h-7 w-full border-dashed border-[var(--dg-border)] dg-text-secondary text-xs" <Button size="sm" variant="outline" className="h-7 w-full border-dashed border-[var(--dg-border)] dg-text-secondary text-xs"
onClick={() => addPort()}> onClick={() => addPort()}>
<Plus className="h-3 w-3 mr-1" />添加自定义端口 <Plus className="h-3 w-3 mr-1" />{i18n.t("common.addCustom")}
</Button> </Button>
</div> </div>
@@ -24,7 +24,7 @@ export function ServiceListItem({ svc, matched, onEdit, onDelete }: Props) {
<code className="text-[11px] dg-text-muted ml-auto">{svc.clusterIP}</code> <code className="text-[11px] dg-text-muted ml-auto">{svc.clusterIP}</code>
<Button size="sm" variant="ghost" className="h-6 text-xs dg-text-green" <Button size="sm" variant="ghost" className="h-6 text-xs dg-text-green"
onClick={onEdit}> onClick={onEdit}>
<Pencil className="h-3 w-3 mr-1" />编辑 <Pencil className="h-3 w-3 mr-1" />{i18n.t("common.editAction")}
</Button> </Button>
<Button size="sm" variant="ghost" className="h-6 w-6 p-0 dg-text-red hover:dg-text-red" <Button size="sm" variant="ghost" className="h-6 w-6 p-0 dg-text-red hover:dg-text-red"
title={i18n.t('kubernetes.service.deleteService')} onClick={onDelete}> title={i18n.t('kubernetes.service.deleteService')} onClick={onDelete}>
@@ -26,8 +26,8 @@ export function BottomStatusBar({
return ( return (
<div className="flex items-center justify-between px-4 py-1.5 border-t border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)] text-xs text-[var(--dg-text-muted)]"> <div className="flex items-center justify-between px-4 py-1.5 border-t border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)] text-xs text-[var(--dg-text-muted)]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="flex items-center gap-1"><Filter className="h-3 w-3" />缓冲 {lineCount}/{RING_BUFFER_MAX}</span> <span className="flex items-center gap-1"><Filter className="h-3 w-3" />{i18n.t("common.buffer")} {lineCount}/{RING_BUFFER_MAX}</span>
{keyword && <span>匹配 {matchedCount}</span>} {keyword && <span>{i18n.t("common.matched")} {matchedCount}</span>}
{container && <span className="flex items-center gap-1"><Box className="h-3 w-3" />{container}</span>} {container && <span className="flex items-center gap-1"><Box className="h-3 w-3" />{container}</span>}
{previous && <span className="dg-text-yellow flex items-center gap-1"><History className="h-3 w-3" />previous</span>} {previous && <span className="dg-text-yellow flex items-center gap-1"><History className="h-3 w-3" />previous</span>}
</div> </div>
@@ -56,11 +56,11 @@ export function PodInstancesTable({
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-green)] hover:text-[var(--dg-green)] hover:bg-[var(--dg-green)]/10" <Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-green)] hover:text-[var(--dg-green)] hover:bg-[var(--dg-green)]/10"
onClick={() => onTerminal(pod)}> onClick={() => onTerminal(pod)}>
<Terminal className="h-3.5 w-3.5 mr-1" />终端 <Terminal className="h-3.5 w-3.5 mr-1" />{i18n.t("common.podLogs")}
</Button> </Button>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-orange)] hover:text-[var(--dg-orange)] hover:bg-[var(--dg-orange)]/10" <Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-orange)] hover:text-[var(--dg-orange)] hover:bg-[var(--dg-orange)]/10"
onClick={() => onFileBrowser(pod)}> onClick={() => onFileBrowser(pod)}>
<HardDrive className="h-3.5 w-3.5 mr-1" />文件浏览器 <HardDrive className="h-3.5 w-3.5 mr-1" />{i18n.t("common.fileBrowser")}
</Button> </Button>
</div> </div>
</TableCell> </TableCell>
@@ -15,7 +15,7 @@ export function UpdateStrategySelectorCards({ stats }: { stats: WorkloadStats })
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2"> <CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Settings className="h-4 w-4 text-[var(--dg-orange)]" />更新策略 <Settings className="h-4 w-4 text-[var(--dg-orange)]" />{i18n.t("common.updateStrategy")}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-0 space-y-2 text-xs"> <CardContent className="pt-0 space-y-2 text-xs">
@@ -70,7 +70,7 @@ export function UpdateStrategySelectorCards({ stats }: { stats: WorkloadStats })
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2"> <CardTitle className="text-sm text-[var(--dg-text-primary)] flex items-center gap-2">
<Tag className="h-4 w-4 text-[var(--dg-purple)]" />选择器与标签 <Tag className="h-4 w-4 text-[var(--dg-purple)]" />{i18n.t("common.selectAndLabel")}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-0 space-y-3 text-xs"> <CardContent className="pt-0 space-y-3 text-xs">
@@ -3,6 +3,7 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Container } from 'lucide-react'; import { Container } from 'lucide-react';
import type { JsonData } from '@/types/common'; import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
/** 统计卡片 */ /** 统计卡片 */
export function StatCard({ label, value, sub, color }: { export function StatCard({ label, value, sub, color }: {
@@ -25,7 +26,7 @@ export function StatCard({ label, value, sub, color }: {
/** 键值对标签徽章组 */ /** 键值对标签徽章组 */
export function LabelBadges({ labels }: { labels: Record<string, string> | undefined }) { export function LabelBadges({ labels }: { labels: Record<string, string> | undefined }) {
const entries = Object.entries(labels || {}); const entries = Object.entries(labels || {});
if (entries.length === 0) return <span className="text-xs text-[var(--dg-text-muted)]">无</span>; if (entries.length === 0) return <span className="text-xs text-[var(--dg-text-muted)]">{i18n.t("common.none")}</span>;
return ( return (
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{entries.map(([k, v]) => ( {entries.map(([k, v]) => (
@@ -107,7 +107,7 @@ export function AlertRulesTab({
{/* 批量操作栏 // Logan.Li */} {/* 批量操作栏 // Logan.Li */}
{selectedIds.length > 0 && ( {selectedIds.length > 0 && (
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<span className="text-sm dg-text-secondary">已选择 {selectedIds.length} 项</span> <span className="text-sm dg-text-secondary">{i18n.t('common.selectedCount', { count: selectedIds.length })}</span>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@@ -99,7 +99,7 @@ export default function PrometheusDatasourceManager() {
description={i18n.t('monitor.promDs2.addHint')} description={i18n.t('monitor.promDs2.addHint')}
action={( action={(
<Button onClick={openCreate} size="sm" className="dg-btn-primary"> <Button onClick={openCreate} size="sm" className="dg-btn-primary">
<Plus className="w-4 h-4 mr-1" />添加数据源 <Plus className="w-4 h-4 mr-1" />{i18n.t("common.addDatasource")}
</Button> </Button>
)} )}
/> />
@@ -61,10 +61,10 @@ export function LogAlertsPanel() {
<p className="text-sm text-[var(--dg-text-muted)]"> {i18n.t('monitor.logAlertSpanel.desc')} </p> <p className="text-sm text-[var(--dg-text-muted)]"> {i18n.t('monitor.logAlertSpanel.desc')} </p>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="outline" size="sm" onClick={load} disabled={loading}> <Button variant="outline" size="sm" onClick={load} disabled={loading}>
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />刷新 <RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />{i18n.t("common.refresh")}
</Button> </Button>
<Button size="sm" onClick={() => { setEditing(null); setDialogOpen(true); }}> <Button size="sm" onClick={() => { setEditing(null); setDialogOpen(true); }}>
<Plus className="h-4 w-4 mr-1" />新建 <Plus className="h-4 w-4 mr-1" />{i18n.t("common.buildNew")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -41,7 +41,7 @@ export function LogClusterPanel({ clusters }: Props) {
<div className="border rounded-lg overflow-hidden" style={{ borderColor: 'var(--dg-border)' }}> <div className="border rounded-lg overflow-hidden" style={{ borderColor: 'var(--dg-border)' }}>
{/* 头部 */} {/* 头部 */}
<div className="flex items-center justify-between px-4 py-2" style={{ background: 'var(--dg-bg-tertiary)', borderBottom: '1px solid var(--dg-border)' }}> <div className="flex items-center justify-between px-4 py-2" style={{ background: 'var(--dg-bg-tertiary)', borderBottom: '1px solid var(--dg-border)' }}>
<span className="text-xs" style={{ color: 'var(--dg-text-muted)' }}>共 {clusters.length} 个聚类模式,总计 {total.toLocaleString()} 条日志</span> <span className="text-xs" style={{ color: 'var(--dg-text-muted)' }}>{i18n.t('common.clusterMode', { count: clusters.length })},{i18n.t('common.totalLogs', { count: total.toLocaleString() })}</span>
<span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}> {i18n.t('monitor.logClusterPanel.desc')} </span> <span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}> {i18n.t('monitor.logClusterPanel.desc')} </span>
</div> </div>
@@ -96,8 +96,8 @@ export function LogClusterPanel({ clusters }: Props) {
{cluster.sample} {cluster.sample}
</div> </div>
<div className="flex gap-4 mt-2 text-[10px]" style={{ color: 'var(--dg-text-muted)' }}> <div className="flex gap-4 mt-2 text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>
<span>数量:{cluster.count.toLocaleString()}</span> <span>{i18n.t('common.countLabel')}:{cluster.count.toLocaleString()}</span>
<span>占比:{cluster.percentage.toFixed(2)}%</span> <span>{i18n.t('common.percentage')}:{cluster.percentage.toFixed(2)}%</span>
</div> </div>
</div> </div>
)} )}
@@ -154,7 +154,7 @@ export function LogDatasourcePage() {
toolbarExtra={ toolbarExtra={
<> <>
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}> <Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}>
<RefreshCw className="h-3.5 w-3.5 mr-1" />刷新 <RefreshCw className="h-3.5 w-3.5 mr-1" />{i18n.t("common.refresh")}
</Button> </Button>
<Button size="sm" className="h-8" onClick={() => handleAdd('loki')}> <Button size="sm" className="h-8" onClick={() => handleAdd('loki')}>
<Plus className="h-4 w-4 mr-1" />Loki <Plus className="h-4 w-4 mr-1" />Loki
@@ -57,7 +57,7 @@ export function LogQueryToolbar({
{st.text} {st.text}
</span> </span>
{queryTime !== undefined && ( {queryTime !== undefined && (
<span className="text-xs" style={{ color: 'var(--dg-text-muted)' }}>耗时: {queryTime}ms</span> <span className="text-xs" style={{ color: 'var(--dg-text-muted)' }}>{i18n.t('common.queryTime')}: {queryTime}ms</span>
)} )}
</div> </div>
@@ -69,7 +69,7 @@ export function LogQueryToolbar({
className="h-7 px-1.5 text-[11px] rounded dg-input" className="h-7 px-1.5 text-[11px] rounded dg-input"
style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }} style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }}
> >
{[10, 20, 50, 100, 200].map(n => <option key={n} value={n}>每页 {n}</option>)} {[10, 20, 50, 100, 200].map(n => <option key={n} value={n}>{i18n.t('common.perPage', { count: n })}</option>)}
</select> </select>
<span className="text-[11px] mx-1" style={{ color: 'var(--dg-text-muted)' }}> <span className="text-[11px] mx-1" style={{ color: 'var(--dg-text-muted)' }}>
@@ -131,7 +131,7 @@ export function LogQueryToolbar({
className="h-7 w-10 text-center text-[11px] rounded dg-input" className="h-7 w-10 text-center text-[11px] rounded dg-input"
style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }} style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }}
/> />
<span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>页</span> <span className="text-[10px]" style={{ color: 'var(--dg-text-muted)' }}>{i18n.t("common.page")}</span>
</div> </div>
</div> </div>
@@ -163,7 +163,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
: { maxWidth: '95vw', width: '1100px', maxHeight: '92vh' } : { maxWidth: '95vw', width: '1100px', maxHeight: '92vh' }
} }
> >
<DialogTitle className="sr-only">文件浏览 — {serverName}</DialogTitle> <DialogTitle className="sr-only">{i18n.t("common.fileBrowse")} — {serverName}</DialogTitle>
<DialogDescription className="sr-only">SFTP 文件管理 · 上传 · 下载 · 删除</DialogDescription> <DialogDescription className="sr-only">SFTP 文件管理 · 上传 · 下载 · 删除</DialogDescription>
{/* Header */} {/* Header */}
@@ -196,7 +196,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
<div className="flex items-center gap-2 px-4 py-2 border-b border-[var(--dg-border)] bg-[var(--dg-bg-primary)] shrink-0"> <div className="flex items-center gap-2 px-4 py-2 border-b border-[var(--dg-border)] bg-[var(--dg-bg-primary)] shrink-0">
<Button variant="outline" size="sm" className="border-[var(--dg-border)] h-8" <Button variant="outline" size="sm" className="border-[var(--dg-border)] h-8"
onClick={() => fetchDir(currentPath)}> onClick={() => fetchDir(currentPath)}>
<RefreshCw className="h-3.5 w-3.5 mr-1" />刷新 <RefreshCw className="h-3.5 w-3.5 mr-1" />{i18n.t("common.refresh")}
</Button> </Button>
<label> <label>
<Button variant="outline" size="sm" className="border-[var(--dg-border)] h-8 cursor-pointer" <Button variant="outline" size="sm" className="border-[var(--dg-border)] h-8 cursor-pointer"
@@ -208,7 +208,7 @@ export function ServerFileBrowser({ open, onOpenChange, serverId, serverName }:
</label> </label>
<Button variant="outline" size="sm" className="border-[var(--dg-border)] h-8" <Button variant="outline" size="sm" className="border-[var(--dg-border)] h-8"
onClick={handleMkdir}> onClick={handleMkdir}>
<FolderPlus className="h-3.5 w-3.5 mr-1" />新建目录 <FolderPlus className="h-3.5 w-3.5 mr-1" />{i18n.t("common.newDir")}
</Button> </Button>
{error && ( {error && (
<Badge variant="outline" className="border-red-500/30 dg-text-red ml-auto"> <Badge variant="outline" className="border-red-500/30 dg-text-red ml-auto">
@@ -221,7 +221,7 @@ export function TerminalDialog({
: { maxWidth: "960px", width: "92vw" } : { maxWidth: "960px", width: "92vw" }
} }
> >
<DialogTitle className="sr-only">终端 - {serverName}</DialogTitle> <DialogTitle className="sr-only">{t("common.podLogs")} - {serverName}</DialogTitle>
<DialogDescription className="sr-only">SSH 终端连接</DialogDescription> <DialogDescription className="sr-only">SSH 终端连接</DialogDescription>
{/* 终端头部 */} {/* 终端头部 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]"> <div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
@@ -197,14 +197,14 @@ export function LiveMonitorDialog({ open, onOpenChange, sessionId, sessionInfo }
: { maxWidth: '960px', width: '92vw' } : { maxWidth: '960px', width: '92vw' }
} }
> >
<DialogTitle className="sr-only">终端实时监控</DialogTitle> <DialogTitle className="sr-only">{t("common.terminalRealtime")}</DialogTitle>
<DialogDescription className="sr-only">审计在线 WebSSH 会话</DialogDescription> <DialogDescription className="sr-only">{t("common.auditWebssh")}</DialogDescription>
{/* 工具栏 — 与 TerminalDialog 一致 // Logan.Li */} {/* 工具栏 — 与 TerminalDialog 一致 // Logan.Li */}
<div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]"> <div className="flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]">
<div className="flex items-center gap-2 text-sm min-w-0 flex-wrap"> <div className="flex items-center gap-2 text-sm min-w-0 flex-wrap">
<Monitor className="h-4 w-4 dg-text-green shrink-0" /> <Monitor className="h-4 w-4 dg-text-green shrink-0" />
<span className="text-[var(--dg-text-primary)] font-medium">终端实时监控</span> <span className="text-[var(--dg-text-primary)] font-medium">{t("common.terminalRealtime")}</span>
{meta && ( {meta && (
<span className="text-[var(--dg-text-muted)] truncate"> <span className="text-[var(--dg-text-muted)] truncate">
{meta.username} · {meta.server_name} ({meta.server_ip}) · {formatTime(meta.start_time)} {meta.username} · {meta.server_name} ({meta.server_ip}) · {formatTime(meta.start_time)}
@@ -152,7 +152,7 @@ export function RoleFormDialog({
<Label className="text-xs text-[var(--dg-text-secondary)]"> <Label className="text-xs text-[var(--dg-text-secondary)]">
权限分配 权限分配
{form.permission_ids.length > 0 && ( {form.permission_ids.length > 0 && (
<span className="ml-2 dg-text-purple">已选 {form.permission_ids.length} 项</span> <span className="ml-2 dg-text-purple">{t('common.selectedPerms', { count: form.permission_ids.length })}</span>
)} )}
</Label> </Label>
<div className="max-h-[40vh] overflow-y-auto rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)] p-3 space-y-3"> <div className="max-h-[40vh] overflow-y-auto rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)] p-3 space-y-3">
@@ -60,15 +60,15 @@ export function CronJobViewDialog({ open, onOpenChange, job }: Props) {
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-2.5 text-xs"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-2 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-2.5 text-xs">
<div> <div>
<div className="dg-text-muted mb-0.5">上次执行</div> <div className="dg-text-muted mb-0.5">{t("common.lastRun")}</div>
<div className="text-[var(--dg-text-primary)]">{formatRelativeTime(job.last_run_at, t)}</div> <div className="text-[var(--dg-text-primary)]">{formatRelativeTime(job.last_run_at, t)}</div>
</div> </div>
<div> <div>
<div className="dg-text-muted mb-0.5">累计执行</div> <div className="dg-text-muted mb-0.5">{t("common.totalRun")}</div>
<div className="text-[var(--dg-text-primary)] tabular-nums">{job.run_count ?? 0} 次</div> <div className="text-[var(--dg-text-primary)] tabular-nums">{job.run_count ?? 0} 次</div>
</div> </div>
<div> <div>
<div className="dg-text-muted mb-0.5">成功 / 失败</div> <div className="dg-text-muted mb-0.5">{t("common.successFail")}</div>
<div className="text-[var(--dg-text-primary)] tabular-nums"> <div className="text-[var(--dg-text-primary)] tabular-nums">
<span className="dg-text-green">{job.success_count ?? 0}</span> <span className="dg-text-green">{job.success_count ?? 0}</span>
{' / '} {' / '}
@@ -76,7 +76,7 @@ export function CronJobViewDialog({ open, onOpenChange, job }: Props) {
</div> </div>
</div> </div>
<div> <div>
<div className="dg-text-muted mb-0.5">成功率</div> <div className="dg-text-muted mb-0.5">{t("monitor.lbl.successRate")}</div>
{rate != null ? ( {rate != null ? (
<SuccessRateBar success={job.success_count} fail={job.fail_count} /> <SuccessRateBar success={job.success_count} fail={job.fail_count} />
) : ( ) : (
@@ -55,7 +55,7 @@ export function TerminalOutput({
<span>{new Date(startedAt).toLocaleString('zh-CN', { hour12: false })}</span> <span>{new Date(startedAt).toLocaleString('zh-CN', { hour12: false })}</span>
)} )}
{durationMs != null && durationMs > 0 && ( {durationMs != null && durationMs > 0 && (
<span>耗时 {durationMs < 1000 ? `${durationMs}ms` : `${(durationMs / 1000).toFixed(2)}s`}</span> <span>{i18n.t("common.elapsed")} {durationMs < 1000 ? `${durationMs}ms` : `${(durationMs / 1000).toFixed(2)}s`}</span>
)} )}
{status && ( {status && (
<span className={status === 'success' ? 'text-[var(--dg-green)]' : status === 'failed' ? 'text-[var(--dg-red)]' : 'text-[var(--dg-yellow)]'}> <span className={status === 'success' ? 'text-[var(--dg-green)]' : status === 'failed' ? 'text-[var(--dg-red)]' : 'text-[var(--dg-yellow)]'}>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -325,7 +325,7 @@ export function ActiveSessionsPanel({ userToggledOff, onUserToggledOff }: Active
) : rdpSessions.length === 0 ? ( ) : rdpSessions.length === 0 ? (
<div className="flex flex-col items-center justify-center py-10 dg-text-muted"> <div className="flex flex-col items-center justify-center py-10 dg-text-muted">
<Activity className="h-7 w-7 mb-2 opacity-40" /> <Activity className="h-7 w-7 mb-2 opacity-40" />
<p className="text-sm">暂无 RDP 在线会话</p> <p className="text-sm">{t("common.noRdpActive")}</p>
</div> </div>
) : ( ) : (
<Table> <Table>
@@ -256,7 +256,7 @@ export function HistorySessionsPanel() {
) : rdpSessions.length === 0 ? ( ) : rdpSessions.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 dg-text-muted"> <div className="flex flex-col items-center justify-center py-12 dg-text-muted">
<Monitor className="h-7 w-7 mb-2 opacity-40" /> <Monitor className="h-7 w-7 mb-2 opacity-40" />
<p className="text-sm">暂无 RDP 历史会话</p> <p className="text-sm">{t("common.noRdpHistory")}</p>
</div> </div>
) : ( ) : (
<> <>
@@ -164,10 +164,10 @@ export function MiddlewareMonitor() {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={handleCollectNow} className="gap-1.5"> <Button variant="outline" size="sm" onClick={handleCollectNow} className="gap-1.5">
<Zap className="h-4 w-4" />立即采集 <Zap className="h-4 w-4" />{t("common.collectNow")}
</Button> </Button>
<Button variant="outline" size="sm" onClick={() => loadTargets(true)} disabled={refreshing} className="gap-1.5"> <Button variant="outline" size="sm" onClick={() => loadTargets(true)} disabled={refreshing} className="gap-1.5">
<RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />刷新 <RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />{t("common.refresh")}
</Button> </Button>
{targets.length === 0 && ( {targets.length === 0 && (
<Button variant="outline" size="sm" onClick={handleSeedDefaults} disabled={seeding} className="gap-1.5"> <Button variant="outline" size="sm" onClick={handleSeedDefaults} disabled={seeding} className="gap-1.5">
@@ -176,14 +176,14 @@ export function MiddlewareMonitor() {
</Button> </Button>
)} )}
<Button size="sm" onClick={() => openCreate()} className="gap-1.5"> <Button size="sm" onClick={() => openCreate()} className="gap-1.5">
<Plus className="h-4 w-4" />添加监控 <Plus className="h-4 w-4" />{t("common.addMonitor")}
</Button> </Button>
</div> </div>
</div> </div>
{loading && ( {loading && (
<div className="flex items-center justify-center py-12 text-sm text-[var(--dg-text-muted)]"> <div className="flex items-center justify-center py-12 text-sm text-[var(--dg-text-muted)]">
<Loader2 className="h-5 w-5 animate-spin mr-2" />加载中... <Loader2 className="h-5 w-5 animate-spin mr-2" />{t("common.loading")}
</div> </div>
)} )}
+5 -5
View File
@@ -188,12 +188,12 @@ export function SNMPMonitor() {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" onClick={refresh} className="h-8"> <Button size="sm" variant="outline" onClick={refresh} className="h-8">
<RefreshCw className="h-3.5 w-3.5 mr-1" />刷新 <RefreshCw className="h-3.5 w-3.5 mr-1" />{t("common.refresh")}
</Button> </Button>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}> <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button size="sm" className="bg-cyan-500/10 dg-text-cyan border border-cyan-500/30 h-8"> <Button size="sm" className="bg-cyan-500/10 dg-text-cyan border border-cyan-500/30 h-8">
<Plus className="h-3.5 w-3.5 mr-1" />添加设备 <Plus className="h-3.5 w-3.5 mr-1" />{t("common.addDevice")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-md bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <DialogContent className="sm:max-w-md bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
@@ -272,7 +272,7 @@ export function SNMPMonitor() {
{loading ? ( {loading ? (
<div className="py-16 text-center text-sm text-[var(--dg-text-muted)]"> <div className="py-16 text-center text-sm text-[var(--dg-text-muted)]">
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />加载中... <Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />{t("common.loading")}
</div> </div>
) : devices.length === 0 ? ( ) : devices.length === 0 ? (
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
@@ -289,7 +289,7 @@ export function SNMPMonitor() {
<div className="grid grid-cols-1 xl:grid-cols-12 gap-4 items-start"> <div className="grid grid-cols-1 xl:grid-cols-12 gap-4 items-start">
<Card className="xl:col-span-5 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="xl:col-span-5 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-3"> <CardContent className="p-3">
<h3 className="text-sm font-medium mb-3">设备列表 ({devices.length})</h3> <h3 className="text-sm font-medium mb-3">{t('common.deviceList')} ({devices.length})</h3>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <Table>
<TableHeader> <TableHeader>
@@ -373,7 +373,7 @@ export function SNMPMonitor() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead> {t('cloud.colMetric')} </TableHead><TableHead> {t('monitor.logIndex')} </TableHead><TableHead>值</TableHead><TableHead>OID</TableHead> <TableHead> {t('cloud.colMetric')} </TableHead><TableHead> {t('monitor.logIndex')} </TableHead><TableHead> {t('common.newValue')} </TableHead><TableHead>OID</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
+4 -4
View File
@@ -217,10 +217,10 @@ export function SiteMonitor() {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" className="h-8" onClick={() => { loadStats(); loadTasks(); }} disabled={refreshing}> <Button size="sm" variant="outline" className="h-8" onClick={() => { loadStats(); loadTasks(); }} disabled={refreshing}>
<RefreshCw className={`h-3.5 w-3.5 mr-1 ${refreshing ? 'animate-spin' : ''}`} />刷新 <RefreshCw className={`h-3.5 w-3.5 mr-1 ${refreshing ? 'animate-spin' : ''}`} />{t("common.refresh")}
</Button> </Button>
<Button size="sm" className="h-8 gap-1.5" onClick={() => openCreate()}> <Button size="sm" className="h-8 gap-1.5" onClick={() => openCreate()}>
<Plus className="h-3.5 w-3.5" />添加任务 <Plus className="h-3.5 w-3.5" />{t("common.addTask")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -302,7 +302,7 @@ export function SiteMonitor() {
<span className="text-sm font-medium truncate">{task.name}</span> <span className="text-sm font-medium truncate">{task.name}</span>
{isSslProbeTask(task) && isSslExpiringSoon(task.ssl_days_remaining) && ( {isSslProbeTask(task) && isSslExpiringSoon(task.ssl_days_remaining) && (
<Badge className="bg-amber-500/15 dg-text-yellow border-amber-500/30 text-[10px] shrink-0 gap-0.5"> <Badge className="bg-amber-500/15 dg-text-yellow border-amber-500/30 text-[10px] shrink-0 gap-0.5">
<ShieldAlert className="h-3 w-3" />证书 <ShieldAlert className="h-3 w-3" />{t("common.certLabel")}
</Badge> </Badge>
)} )}
</div> </div>
@@ -321,7 +321,7 @@ export function SiteMonitor() {
<TableCell className="py-2"> <TableCell className="py-2">
<div className="flex items-center justify-end gap-0.5"> <div className="flex items-center justify-end gap-0.5">
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={() => openDetail(task)}> <Button variant="outline" size="sm" className="h-7 text-xs" onClick={() => openDetail(task)}>
<LineChart className="h-3 w-3 mr-1" />可观测 <LineChart className="h-3 w-3 mr-1" />{t("common.observable")}
</Button> </Button>
<Button variant="ghost" size="icon-sm" onClick={() => openEdit(task)}><Pencil className="h-3.5 w-3.5" /></Button> <Button variant="ghost" size="icon-sm" onClick={() => openEdit(task)}><Pencil className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="icon-sm" className="dg-text-red" onClick={() => handleDelete(task)}><Trash2 className="h-3.5 w-3.5" /></Button> <Button variant="ghost" size="icon-sm" className="dg-text-red" onClick={() => handleDelete(task)}><Trash2 className="h-3.5 w-3.5" /></Button>
@@ -94,10 +94,10 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
<h3 className="text-xs font-medium text-[var(--dg-text-primary)]"> {t('monitor.lbl.unlimited')} </h3> <h3 className="text-xs font-medium text-[var(--dg-text-primary)]"> {t('monitor.lbl.unlimited')} </h3>
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" className="h-7 text-[11px]" onClick={() => refetch()} disabled={isFetching}> <Button size="sm" variant="outline" className="h-7 text-[11px]" onClick={() => refetch()} disabled={isFetching}>
<RefreshCw className={cn('w-3 h-3 mr-1', isFetching && 'animate-spin')} />刷新 <RefreshCw className={cn('w-3 h-3 mr-1', isFetching && 'animate-spin')} />{t("common.refresh")}
</Button> </Button>
<Button size="sm" className="h-7 text-[11px] bg-blue-600 hover:bg-blue-500" onClick={openCreateDialog}> <Button size="sm" className="h-7 text-[11px] bg-blue-600 hover:bg-blue-500" onClick={openCreateDialog}>
<Plus className="w-3 h-3 mr-1" />添加探针 <Plus className="w-3 h-3 mr-1" />{t("common.addProbe")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -145,8 +145,8 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
<span> {t('monitor.lbl.unlimited')} <span className={p.last_status === 'up' ? 'dg-text-green' : p.last_status === 'down' ? 'dg-text-red' : 'dg-text-secondary'}> <span> {t('monitor.lbl.unlimited')} <span className={p.last_status === 'up' ? 'dg-text-green' : p.last_status === 'down' ? 'dg-text-red' : 'dg-text-secondary'}>
{p.last_status ? String(p.last_status).toUpperCase() : t('monitor.lbl.notDetected')} {p.last_status ? String(p.last_status).toUpperCase() : t('monitor.lbl.notDetected')}
</span></span> </span></span>
{Number(p.last_latency_ms) > 0 && <span>延迟: {String(p.last_latency_ms)}ms</span>} {Number(p.last_latency_ms) > 0 && <span>{t('common.latency')}: {String(p.last_latency_ms)}ms</span>}
{Number(p.fail_count) > 0 && <span className="dg-text-red">连续失败: {String(p.fail_count)}次</span>} {Number(p.fail_count) > 0 && <span className="dg-text-red">{t('common.consecutiveFail')}: {String(p.fail_count)}</span>}
{p.last_checked_at && <span className="text-[10px]">{new Date(String(p.last_checked_at)).toLocaleString('zh-CN')}</span>} {p.last_checked_at && <span className="text-[10px]">{new Date(String(p.last_checked_at)).toLocaleString('zh-CN')}</span>}
</div> </div>
</div> </div>
@@ -10,7 +10,7 @@ export function ContextSwitchCard({ csInfo }: { csInfo: JsonData }) {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4"> <CardContent className="p-4">
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2"> <h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
<RefreshCw className="h-4 w-4 dg-text-green" />上下文切换 <RefreshCw className="h-4 w-4 dg-text-green" />{t("common.contextSwitch")}
</h3> </h3>
{csInfo?.ctx_per_sec != null ? ( {csInfo?.ctx_per_sec != null ? (
<div className="space-y-4"> <div className="space-y-4">
@@ -19,7 +19,7 @@ export function PortMonitorTab({ portInfo, portPolicyMode, portWhitelist, onOpen
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4"> <CardContent className="p-4">
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2"> <h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
<Server className="h-4 w-4 dg-text-green" />监听端口 {portInfo.length} <Server className="h-4 w-4 dg-text-green" />{t("common.listenPorts")} {portInfo.length}
<span className="ml-auto flex items-center gap-2"> <span className="ml-auto flex items-center gap-2">
{portPolicyMode === 'blacklist' && portWhitelist.length > 0 && ( {portPolicyMode === 'blacklist' && portWhitelist.length > 0 && (
<Badge variant="outline" className="text-[10px] dg-text-yellow border-amber-500/30"> <Badge variant="outline" className="text-[10px] dg-text-yellow border-amber-500/30">
@@ -10,7 +10,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]"> <Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4"> <CardContent className="p-4">
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2"> <h3 className="text-xs font-medium text-[var(--dg-text-primary)] mb-3 flex items-center gap-2">
<List className="h-4 w-4 dg-text-cyan" />进程 TOP {processTop.length} <List className="h-4 w-4 dg-text-cyan" />{t("common.processTop")} {processTop.length}
</h3> </h3>
{processTop.length === 0 ? ( {processTop.length === 0 ? (
<div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg"> <div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg">
@@ -125,7 +125,7 @@ export function MiddlewareDialog({ open, onOpenChange, editTarget, onSuccess }:
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>密码 {editTarget && <span className="text-xs text-muted-foreground">(留空不修改)</span>}</Label> <Label>{t('common.password')} {editTarget && <span className="text-xs text-muted-foreground">{t('common.passwordOptional')}</span>}</Label>
<Input type="password" value={form.password} onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))} /> <Input type="password" value={form.password} onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))} />
</div> </div>
{form.type === 'mysql' && ( {form.type === 'mysql' && (
@@ -84,12 +84,12 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
<div className="flex items-center justify-between gap-2 mb-3 flex-wrap"> <div className="flex items-center justify-between gap-2 mb-3 flex-wrap">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<Activity className="h-4 w-4 text-[var(--dg-text-muted)] shrink-0" /> <Activity className="h-4 w-4 text-[var(--dg-text-muted)] shrink-0" />
<h3 className="text-sm font-medium truncate">可观测 · {target.name}</h3> <h3 className="text-sm font-medium truncate">{t('common.observable')} · {target.name}</h3>
<Badge variant="outline" className="text-[10px] shrink-0">{target.ip}:{target.port}</Badge> <Badge variant="outline" className="text-[10px] shrink-0">{target.ip}:{target.port}</Badge>
{accent === 'redis' && ( {accent === 'redis' && (
<Badge variant="outline" className="text-[10px] shrink-0">{redisModeLabel(target.config_public?.redis_mode)}</Badge> <Badge variant="outline" className="text-[10px] shrink-0">{redisModeLabel(target.config_public?.redis_mode)}</Badge>
)} )}
<span className="text-[10px] text-[var(--dg-text-muted)] shrink-0">更新 {fmtLastSeen(target.last_seen_at)}</span> <span className="text-[10px] text-[var(--dg-text-muted)] shrink-0">{t('common.updated')} {fmtLastSeen(target.last_seen_at)}</span>
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setShowAll((v) => !v)}> <Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setShowAll((v) => !v)}>
@@ -105,7 +105,7 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
</SelectContent> </SelectContent>
</Select> </Select>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadCharts} disabled={loading}> <Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadCharts} disabled={loading}>
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />刷新 <RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />{t("common.refresh")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -184,7 +184,7 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
{loading ? ( {loading ? (
<div className="py-10 flex items-center justify-center text-sm text-[var(--dg-text-muted)]"> <div className="py-10 flex items-center justify-center text-sm text-[var(--dg-text-muted)]">
<Loader2 className="h-4 w-4 animate-spin mr-2" />加载趋势... <Loader2 className="h-4 w-4 animate-spin mr-2" />{t("common.loadTrend")}
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
@@ -16,7 +16,7 @@ export function ChartRangeHint({ label, zoomed, onReset }: Props) {
<div className="flex items-center justify-between text-[10px] text-[var(--dg-text-muted)] mt-1 px-0.5"> <div className="flex items-center justify-between text-[10px] text-[var(--dg-text-muted)] mt-1 px-0.5">
<span> {t('monitor.chartRangeHint.selectedRange')} <span className="text-[var(--dg-text-primary)] font-mono">{label}</span></span> <span> {t('monitor.chartRangeHint.selectedRange')} <span className="text-[var(--dg-text-primary)] font-mono">{label}</span></span>
<Button type="button" variant="ghost" size="sm" className="h-6 text-[10px] px-2" onClick={onReset}> <Button type="button" variant="ghost" size="sm" className="h-6 text-[10px] px-2" onClick={onReset}>
<ZoomOut className="h-3 w-3 mr-1" />恢复全量 <ZoomOut className="h-3 w-3 mr-1" />{t("common.restoreFull")}
</Button> </Button>
</div> </div>
); );
@@ -48,7 +48,7 @@ export function SiteProtocolPieChart({ tasks }: Props) {
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<PieChartIcon className="h-4 w-4 text-[var(--dg-text-muted)]" /> <PieChartIcon className="h-4 w-4 text-[var(--dg-text-muted)]" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]"> {t('monitor.lbl.availability24h')} </h3> <h3 className="text-sm font-medium text-[var(--dg-text-primary)]"> {t('monitor.lbl.availability24h')} </h3>
<span className="text-[10px] text-[var(--dg-text-muted)]">共 {tasks.length} 个任务</span> <span className="text-[10px] text-[var(--dg-text-muted)]">{t('common.itemsCount', { count: tasks.length })}</span>
</div> </div>
<ResponsiveContainer width="100%" height={200}> <ResponsiveContainer width="100%" height={200}>
<PieChart> <PieChart>
@@ -213,7 +213,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
{!fullPage && ( {!fullPage && (
<> <>
<Activity className="h-4 w-4 text-[var(--dg-text-muted)] shrink-0" /> <Activity className="h-4 w-4 text-[var(--dg-text-muted)] shrink-0" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)] truncate">可观测 · {task.name}</h3> <h3 className="text-sm font-medium text-[var(--dg-text-primary)] truncate">{t('common.observable')} · {task.name}</h3>
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)] shrink-0">{probeType}</Badge> <Badge variant="outline" className="text-[10px] border-[var(--dg-border)] shrink-0">{probeType}</Badge>
</> </>
)} )}
@@ -232,7 +232,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
</SelectContent> </SelectContent>
</Select> </Select>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadResults} disabled={loading}> <Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadResults} disabled={loading}>
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />刷新 <RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />{t("common.refresh")}
</Button> </Button>
<Button size="sm" className="h-7 text-xs" onClick={handleProbeNow} disabled={probing}> <Button size="sm" className="h-7 text-xs" onClick={handleProbeNow} disabled={probing}>
{probing ? <Loader2 className="h-3 w-3 mr-1 animate-spin" /> : <Zap className="h-3 w-3 mr-1" />}立即拨测 {probing ? <Loader2 className="h-3 w-3 mr-1 animate-spin" /> : <Zap className="h-3 w-3 mr-1" />}立即拨测
@@ -143,7 +143,7 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
{loading ? ( {loading ? (
<div className="py-12 flex items-center justify-center text-sm text-[var(--dg-text-muted)]"> <div className="py-12 flex items-center justify-center text-sm text-[var(--dg-text-muted)]">
<Loader2 className="h-4 w-4 animate-spin mr-2" />加载趋势... <Loader2 className="h-4 w-4 animate-spin mr-2" />{t("common.loadTrend")}
</div> </div>
) : charts.length === 0 ? ( ) : charts.length === 0 ? (
<div className="py-12 text-center text-sm text-[var(--dg-text-muted)]"> <div className="py-12 text-center text-sm text-[var(--dg-text-muted)]">