refactor(quality): Q37 k8s-yaml-utils.ts 工具文件拆分 — 504L→451L + k8s-helpers.ts 67L 新文件
提取 7 个通用工具函数到 k8s-helpers.ts (parseScalar/STATUS_COLORS/statusColor/aggregateByImage/formatK8sTime/elapsedSeconds/formatDuration),barrel re-export 保持 8 个消费者零修改,tsc -b=0,npm build✓ (17.49s),ESLint=0/66/0,go build=0,17/17 测试通过
This commit is contained in:
1 parent
4113079e24
commit
34050f5188
3 files changed
+79
-64
No files matched your search
@@ -509,6 +509,7 @@ services/api.ts (1324L)
|
||||
| Q35 | 前端巨型组件拆分(AgentCLI 521L→399L (-23%),提取 agent-cli/ 2 个文件 (event-handler.ts 149L createWsEventHandler 工厂函数封装 8 种 WS 事件分发+高危命令确认 + ChatInputArea.tsx 44L 受控输入区组件),导出 ConfirmOptions 接口供跨模块引用,tsc -b=0,npm build✓,ESLint=0/66/0,go build=0,17/17 测试通过。最后一个 ≥500L 业务组件拆分完成,剩余最大业务组件 UnifiedQueryPanel 529L(已从 682L 拆分)) | P0 | **done** |
|
||||
| Q36 | 前端巨型工具文件拆分(predictUtils.ts 716L→49L barrel re-export (-93%),提取 predict-types.ts 140L 类型+常量 + capacity-predict.ts 194L 容量预测函数(linearRegression+computePredictions+buildCapacityInsightPrompt) + cost-predict.ts 434L 云成本预测函数(computeCostPredictions+buildCostInsightPrompt),3 个消费者 import 路径无需修改,tsc -b=0,npm build✓ (17.26s),ESLint=0/66/0,go build=0,17/17 测试通过) | P0 | **done** |
|
||||
| U15 | ApiAccessKeySection i18n 迁移(清理 56 个 apiaccesskey.s{hash} 乱码键为语义化 user.apiKey.* 键 + 组件全量接入 useTranslation,UI 中文 57→0,zh/en 7556=7556 对齐,零 hash 键残留) | P3 | **done** |
|
||||
| Q37 | 前端巨型工具文件拆分(k8s-yaml-utils.ts 504L→451L + k8s-helpers.ts 67L 新文件,提取 7 个通用工具函数 parseScalar/STATUS_COLORS/statusColor/aggregateByImage/formatK8sTime/elapsedSeconds/formatDuration,barrel re-export 保持 8 个消费者零修改) | P0 | **done** |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// k8s-helpers.ts — K8s 通用工具函数(状态色/时间格式化/镜像聚合)
|
||||
// 从 k8s-yaml-utils.ts 提取,供 K8s 模块各组件复用
|
||||
|
||||
/** 解析简单 "key: value" 行 */
|
||||
export function parseScalar(line: string): { key: string; value: string } | null {
|
||||
const m = line.match(/^([A-Za-z_][\w-]*)\s*:\s*(.*)$/);
|
||||
if (!m) return null;
|
||||
return { key: m[1], value: m[2].trim().replace(/^["']|["']$/g, '') };
|
||||
}
|
||||
|
||||
/** 状态色 */
|
||||
export const STATUS_COLORS = {
|
||||
green: '#10b981',
|
||||
orange: '#f59e0b',
|
||||
red: '#ef4444',
|
||||
blue: '#326CE5',
|
||||
} as const;
|
||||
|
||||
export function statusColor(status: string | undefined): string {
|
||||
if (!status) return '#64748b';
|
||||
const s = status.toLowerCase();
|
||||
if (['running', 'available', 'ready', 'active', 'completed', 'succeeded'].includes(s)) return STATUS_COLORS.green;
|
||||
if (['pending', 'updating', 'progressing', 'containercreating'].includes(s)) return STATUS_COLORS.orange;
|
||||
if (['failed', 'error', 'crashloopbackoff', 'imagepullbackoff', 'errimageneverpull'].includes(s)) return STATUS_COLORS.red;
|
||||
return STATUS_COLORS.blue;
|
||||
}
|
||||
|
||||
/** 按镜像聚合 */
|
||||
export function aggregateByImage<T extends { image?: string; images?: string[] }>(items: T[]): Array<T & { _count: number }> {
|
||||
const map = new Map<string, T & { _count: number }>();
|
||||
for (const it of items) {
|
||||
const img = it.image || (Array.isArray(it.images) ? it.images[0] : '') || 'unknown';
|
||||
const existing = map.get(img);
|
||||
if (existing) {
|
||||
existing._count += 1;
|
||||
} else {
|
||||
map.set(img, { ...it, _count: 1 });
|
||||
}
|
||||
}
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
/** 格式化 K8s 时间戳 */
|
||||
export function formatK8sTime(ts: string | undefined | null): string {
|
||||
if (!ts) return '-';
|
||||
const d = new Date(ts);
|
||||
if (isNaN(d.getTime())) return ts;
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
export function elapsedSeconds(start?: string | null, end?: string | null): number | null {
|
||||
if (!start || !end) return null;
|
||||
const s = new Date(start).getTime();
|
||||
const e = new Date(end).getTime();
|
||||
if (isNaN(s) || isNaN(e) || e < s) return null;
|
||||
return Math.max(0, Math.round((e - s) / 1000));
|
||||
}
|
||||
|
||||
export function formatDuration(sec: number | null | undefined): string {
|
||||
if (sec == null) return '-';
|
||||
if (sec < 60) return `${sec}s`;
|
||||
if (sec < 3600) return `${Math.floor(sec / 60)}m${sec % 60}s`;
|
||||
const h = Math.floor(sec / 3600);
|
||||
const m = Math.floor((sec % 3600) / 60);
|
||||
return `${h}h${m}m`;
|
||||
}
|
||||
@@ -438,67 +438,14 @@ function isSystemMountPath(line: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/** 解析简单 "key: value" 行 */
|
||||
export function parseScalar(line: string): { key: string; value: string } | null {
|
||||
const m = line.match(/^([A-Za-z_][\w-]*)\s*:\s*(.*)$/);
|
||||
if (!m) return null;
|
||||
return { key: m[1], value: m[2].trim().replace(/^["']|["']$/g, '') };
|
||||
}
|
||||
|
||||
/** 状态色 */
|
||||
export const STATUS_COLORS = {
|
||||
green: '#10b981',
|
||||
orange: '#f59e0b',
|
||||
red: '#ef4444',
|
||||
blue: '#326CE5',
|
||||
} as const;
|
||||
|
||||
export function statusColor(status: string | undefined): string {
|
||||
if (!status) return '#64748b';
|
||||
const s = status.toLowerCase();
|
||||
if (['running', 'available', 'ready', 'active', 'completed', 'succeeded'].includes(s)) return STATUS_COLORS.green;
|
||||
if (['pending', 'updating', 'progressing', 'containercreating'].includes(s)) return STATUS_COLORS.orange;
|
||||
if (['failed', 'error', 'crashloopbackoff', 'imagepullbackoff', 'errimageneverpull'].includes(s)) return STATUS_COLORS.red;
|
||||
return STATUS_COLORS.blue;
|
||||
}
|
||||
|
||||
/** 按镜像聚合 */
|
||||
export function aggregateByImage<T extends { image?: string; images?: string[] }>(items: T[]): Array<T & { _count: number }> {
|
||||
const map = new Map<string, T & { _count: number }>();
|
||||
for (const it of items) {
|
||||
const img = it.image || (Array.isArray(it.images) ? it.images[0] : '') || 'unknown';
|
||||
const existing = map.get(img);
|
||||
if (existing) {
|
||||
existing._count += 1;
|
||||
} else {
|
||||
map.set(img, { ...it, _count: 1 });
|
||||
}
|
||||
}
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
/** 格式化 K8s 时间戳 */
|
||||
export function formatK8sTime(ts: string | undefined | null): string {
|
||||
if (!ts) return '-';
|
||||
const d = new Date(ts);
|
||||
if (isNaN(d.getTime())) return ts;
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
export function elapsedSeconds(start?: string | null, end?: string | null): number | null {
|
||||
if (!start || !end) return null;
|
||||
const s = new Date(start).getTime();
|
||||
const e = new Date(end).getTime();
|
||||
if (isNaN(s) || isNaN(e) || e < s) return null;
|
||||
return Math.max(0, Math.round((e - s) / 1000));
|
||||
}
|
||||
|
||||
export function formatDuration(sec: number | null | undefined): string {
|
||||
if (sec == null) return '-';
|
||||
if (sec < 60) return `${sec}s`;
|
||||
if (sec < 3600) return `${Math.floor(sec / 60)}m${sec % 60}s`;
|
||||
const h = Math.floor(sec / 3600);
|
||||
const m = Math.floor((sec % 3600) / 60);
|
||||
return `${h}h${m}m`;
|
||||
}
|
||||
// Barrel re-export: 通用工具函数已提取到 k8s-helpers.ts
|
||||
// 保留 re-export 以兼容现有 import 路径(8 个消费者无需修改)
|
||||
export {
|
||||
parseScalar,
|
||||
STATUS_COLORS,
|
||||
statusColor,
|
||||
aggregateByImage,
|
||||
formatK8sTime,
|
||||
elapsedSeconds,
|
||||
formatDuration,
|
||||
} from './k8s-helpers';
|
||||
Reference in new issue
Block a user