feat(k8s): F7 集群详情EventsTab真实后端对接 + go vet修复
1. EventsTab.tsx: DEMO_EVENTS硬编码→kubernetesApi.getEvents真实K8s事件 - 复用 normalizeK8sEvent/formatEventRelative 工具(与 EventsPage 一致) - 新增 loading/empty 状态 + 刷新按钮 - Warning事件优先排序 + 限100条防溢出 - KubernetesClusterDetail 传入 clusterCode prop - 修复 Q28 组件拆分时遗留的 demo 数据未对接缺口 2. aiops_alert_analysis.go: log.Printf %s→%d 修复 go vet 失败 - AlertID 类型为 uint, %s 格式符不匹配致 go test 构建失败 - go vet ./... 全量零错误 验证: tsc -b=0, npm build✓ (18.00s), go build=0, go vet=0, 17/17测试通过 Events API 返回5条真实事件 (type=Normal, reason=Pulled, count=5487)
This commit is contained in:
1 parent
a874832f10
commit
ca036a85ca
4 files changed
+94
-20
No files matched your search
@@ -535,6 +535,7 @@ services/api.ts (1324L)
|
|||||||
| U45 | AI 告警分析模块 i18n 补全(analyzerHelpers.ts 6 告警类型 × ~4 模板字符串 = 25 处硬编码中文 rootCause/impact/recommendations→i18n.t() 插值 + mapAlertEvent locale 硬编码 zh-CN→动态 i18n.language + 事件ID比较硬编码→i18n.t(defaultEvent) + AIPredictiveAnalytics 预测图标中文-only includes→match() 双语兼容 — 新增 aiops.analyzer 26 翻译键, zh/en 8059→8085 对齐;tsc -b=0, npm build✓ (17.40s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
|
| U45 | AI 告警分析模块 i18n 补全(analyzerHelpers.ts 6 告警类型 × ~4 模板字符串 = 25 处硬编码中文 rootCause/impact/recommendations→i18n.t() 插值 + mapAlertEvent locale 硬编码 zh-CN→动态 i18n.language + 事件ID比较硬编码→i18n.t(defaultEvent) + AIPredictiveAnalytics 预测图标中文-only includes→match() 双语兼容 — 新增 aiops.analyzer 26 翻译键, zh/en 8059→8085 对齐;tsc -b=0, npm build✓ (17.40s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
|
||||||
| U46 | i18n 残余 UI 硬编码中文清零 — 深度边角(6 文件 12 处→0: UserFormDialog '新建用户' + CredentialManager '显示' title + RdpAuditViewer RDP实时审计/RDP历史回放/录屏帧解码失败 + LiveMonitorDialog 连接超时提示 + ServerFileBrowser prompt输入目录名 + context-loaders.ts AI上下文模板字符串(alertSummary/alertLine/metricLine/highLoadHosts/agentMetricsSummary/platformOverview/highLoadAssets/recentAlerts/recentReleases/overviewFallback) 全量 i18n.t() 插值 — 新增 common.show/dirNamePrompt + system.createUser/rdpFrameError/rdpHistoryReplayId/connectTimeoutActive + aiops.ctx 11 键 共 17 翻译键, zh/en 8085→8102 对齐;tsc -b=0, npm build✓ (18.05s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
|
| U46 | i18n 残余 UI 硬编码中文清零 — 深度边角(6 文件 12 处→0: UserFormDialog '新建用户' + CredentialManager '显示' title + RdpAuditViewer RDP实时审计/RDP历史回放/录屏帧解码失败 + LiveMonitorDialog 连接超时提示 + ServerFileBrowser prompt输入目录名 + context-loaders.ts AI上下文模板字符串(alertSummary/alertLine/metricLine/highLoadHosts/agentMetricsSummary/platformOverview/highLoadAssets/recentAlerts/recentReleases/overviewFallback) 全量 i18n.t() 插值 — 新增 common.show/dirNamePrompt + system.createUser/rdpFrameError/rdpHistoryReplayId/connectTimeoutActive + aiops.ctx 11 键 共 17 翻译键, zh/en 8085→8102 对齐;tsc -b=0, npm build✓ (18.05s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
|
||||||
| U47 | i18n 最后 3 处残余硬编码中文清零 — 全量 CJK 扫描验证(cloud-server-import/types.ts 阿里云/腾讯云/华为云→i18n.t('cloud.*') + TerminalSessionsTab.tsx 批量删除→t('system.terminalBatchDelete') + PreviewTable.tsx 类型→t('common.type') — **全前端非注释 CJK 字符 = 0**, 复用已有翻译键零新增, zh/en 8102=8102 对齐;tsc -b=0, npm build✓ (18.02s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
|
| U47 | i18n 最后 3 处残余硬编码中文清零 — 全量 CJK 扫描验证(cloud-server-import/types.ts 阿里云/腾讯云/华为云→i18n.t('cloud.*') + TerminalSessionsTab.tsx 批量删除→t('system.terminalBatchDelete') + PreviewTable.tsx 类型→t('common.type') — **全前端非注释 CJK 字符 = 0**, 复用已有翻译键零新增, zh/en 8102=8102 对齐;tsc -b=0, npm build✓ (18.02s), ESLint=0/43/any=0, go build=0, 17/17 测试通过) | P3 | **done** |
|
||||||
|
| F7 | 集群详情 EventsTab 真实后端对接(DEMO_EVENTS 硬编码→kubernetesApi.getEvents 真实 K8s 事件 + 复用 normalizeK8sEvent/formatEventRelative 工具 + loading/empty 状态 + 刷新按钮 + Warning 优先排序 + 限 100 条 — 修复 Q28 拆分时遗留的 demo 数据对接缺口 + 修复 aiops_alert_analysis.go log.Printf %s→%d go vet 失败) | P1 | **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** |
|
| 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** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ func (h *AIOpsAlertAnalysisHandler) saveAnalysisTrace(ctx context.Context, in an
|
|||||||
Error: in.Error,
|
Error: in.Error,
|
||||||
}
|
}
|
||||||
if err := h.db.WithContext(ctx).Create(&row).Error; err != nil {
|
if err := h.db.WithContext(ctx).Create(&row).Error; err != nil {
|
||||||
log.Printf("[AIOpsAnalysis] 保存告警分析结果失败 (alert_id=%s, provider=%s): %v", in.AlertID, in.Provider, err)
|
log.Printf("[AIOpsAnalysis] 保存告警分析结果失败 (alert_id=%d, provider=%s): %v", in.AlertID, in.Provider, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function KubernetesClusterDetail({ cluster, onClose }: { cluster?: JsonDa
|
|||||||
<TabsContent value="deployments" className="mt-4"><DeploymentsTab deployments={deployments} /></TabsContent>
|
<TabsContent value="deployments" className="mt-4"><DeploymentsTab deployments={deployments} /></TabsContent>
|
||||||
<TabsContent value="services" className="mt-4"><ServicesTab services={services} /></TabsContent>
|
<TabsContent value="services" className="mt-4"><ServicesTab services={services} /></TabsContent>
|
||||||
<TabsContent value="ingresses" className="mt-4"><IngressesTab ingresses={ingresses} /></TabsContent>
|
<TabsContent value="ingresses" className="mt-4"><IngressesTab ingresses={ingresses} /></TabsContent>
|
||||||
<TabsContent value="events" className="mt-4"><EventsTab /></TabsContent>
|
<TabsContent value="events" className="mt-4"><EventsTab clusterCode={cluster?.code as string | undefined} /></TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,41 +1,114 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { RefreshCw } from 'lucide-react';
|
||||||
|
import { kubernetesApi } from '@/api';
|
||||||
|
import { normalizeK8sEvent, formatEventRelative } from '../k8s-event-utils';
|
||||||
|
import type { NormalizedEvent } from '../k8s-event-utils';
|
||||||
|
import type { JsonData } from '@/types/common';
|
||||||
|
|
||||||
/** 集群事件 mock 数据 — 真正的事件需后端 events/summary 端点对接 */
|
interface EventsTabProps {
|
||||||
const DEMO_EVENTS = [
|
clusterCode?: string;
|
||||||
{ type: 'Normal', reason: 'Started', message: 'Started container nginx' },
|
}
|
||||||
{ type: 'Normal', reason: 'Created', message: 'Created pod nginx-deployment-xxx' },
|
|
||||||
{ type: 'Warning', reason: 'FailedScheduling', message: '0/3 nodes are available' },
|
|
||||||
{ type: 'Normal', reason: 'Pulling', message: 'Pulling image "nginx:latest"' },
|
|
||||||
{ type: 'Normal', reason: 'Pulled', message: 'Successfully pulled image' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const MIN_AGOS = [2, 5, 10, 15, 12];
|
export function EventsTab({ clusterCode }: EventsTabProps) {
|
||||||
|
|
||||||
export function EventsTab() {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [events, setEvents] = useState<NormalizedEvent[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const fetchEvents = useCallback(async () => {
|
||||||
|
if (!clusterCode) return;
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await kubernetesApi.getEvents(clusterCode);
|
||||||
|
const arr = Array.isArray(res) ? res : (res?.list || []);
|
||||||
|
const normalized = (arr as JsonData[]).map(normalizeK8sEvent);
|
||||||
|
// Warning 优先,然后按 lastTime 降序
|
||||||
|
normalized.sort((a, b) => {
|
||||||
|
if (a.type !== b.type) return a.type === 'Warning' ? -1 : 1;
|
||||||
|
return new Date(b.lastTime || 0).getTime() - new Date(a.lastTime || 0).getTime();
|
||||||
|
});
|
||||||
|
setEvents(normalized.slice(0, 100));
|
||||||
|
} catch {
|
||||||
|
setEvents([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [clusterCode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (clusterCode) {
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
fetchEvents();
|
||||||
|
}
|
||||||
|
}, [clusterCode, fetchEvents]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center py-8 text-[var(--dg-text-muted)]">
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
{t('common.loading')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="py-8 text-center text-[var(--dg-text-muted)]">
|
||||||
|
{t('k8s.empty.events')}
|
||||||
|
</div>
|
||||||
|
{clusterCode && (
|
||||||
|
<Button variant="ghost" size="sm" onClick={fetchEvents} title={t('common.refresh')}>
|
||||||
|
<RefreshCw className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{DEMO_EVENTS.map((event, index) => (
|
<div className="flex justify-end mb-2">
|
||||||
|
<Button variant="ghost" size="sm" onClick={fetchEvents} title={t('common.refresh')}>
|
||||||
|
<RefreshCw className="h-3.5 w-3.5 mr-1" />
|
||||||
|
{t('common.refresh')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{events.map((event, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={`${event.name}-${index}`}
|
||||||
className="flex items-center gap-3 p-3 rounded-lg bg-[var(--dg-bg-tertiary)]/50"
|
className="flex items-center gap-3 p-3 rounded-lg bg-[var(--dg-bg-tertiary)]/50"
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={
|
className={
|
||||||
event.type === 'Normal'
|
event.type === 'Normal'
|
||||||
? 'border-[var(--dg-green)]/30 text-[var(--dg-green)] bg-[var(--dg-green)]/10'
|
? 'border-[var(--dg-green)]/30 text-[var(--dg-green)] bg-[var(--dg-green)]/10 shrink-0'
|
||||||
: 'border-[var(--dg-orange)]/30 text-[var(--dg-orange)] bg-[var(--dg-orange)]/10'
|
: 'border-[var(--dg-orange)]/30 text-[var(--dg-orange)] bg-[var(--dg-orange)]/10 shrink-0'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{event.type}
|
{event.type}
|
||||||
</Badge>
|
</Badge>
|
||||||
<span className="text-sm font-medium text-[var(--dg-text-secondary)] min-w-[120px]">{event.reason}</span>
|
<span className="text-sm font-medium text-[var(--dg-text-secondary)] min-w-[120px] shrink-0">{event.reason}</span>
|
||||||
<span className="text-sm text-[var(--dg-text-secondary)] flex-1">{event.message}</span>
|
<span className="text-sm text-[var(--dg-text-secondary)] flex-1 truncate" title={event.message}>{event.message}</span>
|
||||||
<span className="text-xs text-[var(--dg-text-muted)]">{t('common.duration.minAgo', { count: MIN_AGOS[index] })}</span>
|
{event.count > 1 && (
|
||||||
|
<span className="text-xs text-[var(--dg-text-muted)] shrink-0">×{event.count}</span>
|
||||||
|
)}
|
||||||
|
{event.object && event.object !== '-' && (
|
||||||
|
<span className="text-xs font-mono text-[var(--dg-text-muted)] shrink-0 max-w-[200px] truncate" title={event.object}>{event.object}</span>
|
||||||
|
)}
|
||||||
|
{event.lastTime && (
|
||||||
|
<span className="text-xs text-[var(--dg-text-muted)] shrink-0">{formatEventRelative(event.lastTime)}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{events.length === 100 && (
|
||||||
|
<div className="text-xs text-center text-[var(--dg-text-muted)] py-2">
|
||||||
|
{t('k8s.event.totalCount', { count: 100 })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user