
Headlamp 前端核心工具库 lib/util 全解析时间格式化、资源单位换算与过滤钩子【免费下载链接】headlampA Kubernetes web UI that is fully-featured, user-friendly and extensible项目地址: https://gitcode.com/GitHub_Trending/he/headlamplib/util 是 HeadlampKubernetes 可扩展 Web UI前端中承担通用工具函数与自定义 Hook职责的核心模块。它把时间显示、资源用量换算、集群路径解析、列表过滤与 URL 状态同步等散落在各个页面里的重复逻辑收敛为一组类型安全、可测试的 API既被 Headlamp 自身的资源列表、节点详情、表头筛选等界面直接使用也通过lib/util作为命名空间导出给插件开发者复用。读完本文你将掌握这组工具 API 的完整签名、底层实现原理与典型使用场景并能基于源码与测试用例在自己的 Headlamp 插件或前端页面中正确调用它们。模块概览与源码位置lib/util的正式 API 文档位于 docs/development/api/modules/lib_util.md其实现集中在 frontend/src/lib/util.ts约 610 行。该文件顶部从其他模块再导出re-export了一批符号以保持对旧插件 API 的兼容// Exported to keep compatibility for plugins that may have used them. export { filterGeneric, filterResource, getClusterPrefixedPath, getCluster, getClusterGroup };从 lib/util.ts 源码 可见filterGeneric、filterResource实际定义于 frontend/src/redux/filterSlice.tsgetClusterPrefixedPath、getCluster、getClusterGroup定义于 frontend/src/lib/cluster.ts。模块还以命名空间形式导出auth与units两个子模块见 lib/util.tsexport * as auth from ./auth; export * as units from ./units;因此lib/util在 API 文档中呈现为一个聚合型模块包含两个命名空间 auth 与 units、一个接口 TimeAgoOptions、两个类型别名DateFormatOptions与DateParam、一个常量CLUSTER_ACTION_GRACE_PERIOD以及十余个函数与 React Hook。核心类型与常量DateParam 与 DateFormatOptionslib/util定义了两个贯穿全模块的类型别名lib/util.tsexport type DateParam string | number | Date; export type DateFormatOptions brief | mini;DateParam时间工具函数的输入统一为三种形式——ISO 时间字符串、毫秒时间戳number或Date对象。timeAgo、localeDate内部都会先执行new Date(date)做归一化。DateFormatOptions时长显示风格brief使用单一最大单位如4 weeksmini使用紧凑多单位形式如4w。TimeAgoOptions 接口TimeAgoOptions 是timeAgo与formatDuration共用的选项对象只有一个可选属性属性类型说明formatbrief \| mini可选输出风格默认brief源码中通过解构默认值实现lib/util.tsexport function formatDuration(duration: number, options: TimeAgoOptions {}) { const { format brief } options; if (format brief) { return shortHumanDuration(duration); } return humanDuration(duration); }CLUSTER_ACTION_GRACE_PERIOD 常量export const CLUSTER_ACTION_GRACE_PERIOD 5000; // ms该常量定义于 lib/util.ts表示集群操作如删除/重启的宽限期单位为毫秒。它在 frontend/src/redux/clusterActionSlice.ts 中被引用执行集群级操作时UI 会等待CLUSTER_ACTION_GRACE_PERIOD5 秒后再刷新状态给后端留出处理时间见 clusterActionSlice.ts 中的setTimeout(resolve, CLUSTER_ACTION_GRACE_PERIOD)。时间与时长工具timeAgo / formatDuration / localeDatetimeAgo计算距离现在多久timeAgo(date, options?)返回从给定日期到当前时刻的经过时长lib/util.tsexport function timeAgo(date: DateParam, options: TimeAgoOptions {}) { const fromDate new Date(date); let now new Date(); if (import.meta.env.UNDER_TEST) { // For testing, we consider the current moment to be 3 months from the dates we are testing. const days 24 * 3600 * 1000; // in ms now new Date(fromDate.getTime() 90 * days); } return formatDuration(now.getTime() - fromDate.getTime(), options); }值得注意的实现细节当构建环境变量UNDER_TEST为真时timeAgo会把当前时刻固定为入参日期之后的 90 天从而让快照snapshot测试不随真实时钟漂移而失效——这是 Headlamp 保证 UI 测试稳定性的关键设计。测试用例 frontend/src/lib/util.test.ts 验证了在UNDER_TEST下timeAgo的输出与formatDuration(90天)完全一致90d。formatDuration毫秒时长格式化formatDuration(duration, options?)接受毫秒数返回人类可读字符串其两种风格分别由两个内部函数实现brief单单位——shortHumanDurationlib/util.ts按最大适用单位取整输出秒60s、分钟60m、小时24h、天1y、年1y。示例45s、10m、3h、12d、2y。mini多单位——humanDurationlib/util.ts按区间混合输出多个单位与 Kubernetesapimachinery的HumanDuration保持对齐时长区间输出示例 2m70s 10m含秒3m10s、2m1s 3h70m 8h含分钟1h15m、3h1m 48h47h 8d含小时2d1h ~2y367d ~8y含天2y1d ~8y8y边界行为负到一定程度的输入返回invalid接近零的负值返回0s。util.test.ts中HUMAN_DURATION_BOUNDARY_CASES与SHORT_HUMAN_DURATION_BOUNDARY_CASESfrontend/src/lib/util.test.ts以约 60 组用例逐一锁定了各区间临界值例如2 * MINUTE - 1 → 119s、8 * DAY - 1 → 7d23h、8 * YEAR - 1 → 7y364d。localeDate本地化日期时间localeDate(date)lib/util.ts将日期格式化为包含时区名的本地字符串正常运行时读取 Redux 中config.settings.timezone配置若时区合法则传入Intl.DateTimeFormat的timeZone选项否则使用系统默认时区。时区合法性由辅助函数isValidTimezonelib/util.ts校验并带tzCache缓存。该函数专门处理了 Linux 上TZ:/etc/localtime被 Chrome 解析为Etc/Unknown从而抛RangeError的崩溃场景util.test.tsfrontend/src/lib/util.test.ts用vi.spyOn模拟Intl.DateTimeFormat抛错验证了该场景。UNDER_TEST下强制使用 UTC、12 小时制与en-US并直接返回toISOString()保证快照一致。百分比与副本数工具getPercentStrgetPercentStr(value, total)lib/util.ts计算百分比并格式化为字符串export function getPercentStr(value: number, total: number) { if (total 0) { return null; } const percentage (value / total) * 100; const formatted percentage.toFixed(1); return ${formatted.endsWith(.0) ? formatted.slice(0, -2) : formatted} %; }total 0时返回null避免除零。先toFixed(1)再去除尾部.0规避了浮点误差如29/100计算为28.999999999999996却仍应显示29 %与格式化抖动。测试见 frontend/src/lib/util.test.tsgetPercentStr(7, 10) 70 %、getPercentStr(5, 8) 62.5 %。getReadyReplicas / getTotalReplicas这两个函数用于从 Workload工作负载对象中提取就绪/期望副本数lib/util.tsexport function getReadyReplicas(item: Workload) { return item.status.readyReplicas || item.status.numberReady || 0; } export function getTotalReplicas(item: Workload) { return ( item.spec.replicas || item.status.currentNumberScheduled || item.status.desiredNumberScheduled || 0 ); }getReadyReplicas优先取status.readyReplicas其次兼容 DaemonSet 的status.numberReadygetTotalReplicas兼容 Deployment/StatefulSet 的spec.replicas与 DaemonSet 的currentNumberScheduled/desiredNumberScheduled取值缺失时回退为 0。资源单位换算normalizeUnit 与 units 命名空间normalizeUnit把 Kubernetes 资源量转成人类可读字符串normalizeUnit(resourceType, quantity)lib/util.ts是 UI 展示层最常用的换算函数。resourceType支持cpu与memory若传入形如metrics.cpu的带点字符串会先取.后的部分。CPU 分支500m毫核换算为0.5 cores数值为 1 时输出单数1 core否则输出复数cores末尾多余的零会被正则/(\.\d*?)0$/去除。内存分支先按 Kubernetes ResourceQuantity 规则把数量解析为字节支持二进制单位Ki/Mi/Gi/Ti/Pi/Ei乘以 1024 的幂与十进制单位m/u/n/k/M/G/T/P/E乘以 1000 的幂再按 1000 进制挑选合适的Bytes/KB/MB/GB/TB/PB/EB/ZB/YB前缀输出保留两位小数无法解析的非数字输入原样返回。normalizeUnit的行为在 frontend/src/lib/util.test.ts 中有系统化覆盖可作速查表输入输出normalizeUnit(cpu, 500m)0.5 coresnormalizeUnit(cpu, 1)1 corenormalizeUnit(cpu, 2)2 coresnormalizeUnit(memory, 1Ki)1.02 KBnormalizeUnit(memory, 1Mi)1.05 MBnormalizeUnit(memory, 1Gi)1.07 GBnormalizeUnit(memory, 1.5Gi)1.61 GBnormalizeUnit(memory, 0)0 BytesnormalizeUnit(memory, 500m)0.5 BytesnormalizeUnit(memory, -1Gi)-1.07 GB其中500m → 0.5 Bytes等亚字节场景是 issue #6628 的回归测试frontend/src/lib/util.test.ts确保小数量不会被格式化为字面量undefined。units 命名空间底层解析与反解析units 命名空间实现于 frontend/src/lib/units.ts源自 k8dash 项目提供四个常量与一组 parse/unparse 函数常量TO_GB 1024 ** 3、TO_ONE_M_CPU 1000000、TO_ONE_CPU 1000000000。parseRam(value)/parseDiskSpace(value)把内存/磁盘量解析为字节数支持m毫字节、指数形式1e3、二进制单位Ki/Mi/Gi、十进制单位K/M/G及小数如1.5Mi。unparseRam(value)字节数反向转为{ value, unit }单位依次取Bi/Ki/Mi/Gi/Ti/Pi/Ei值保留 1 位小数如unparseRam(1536) → { value: 1.5, unit: Ki }。parseCpu(value)CPU 量解析为纳核nano-core1n1、1u1000、1m1e6、11e9且用parseFloat保留小数核如parseCpu(0.5) 500000000。unparseCpu(value)纳核转毫核返回{ value, unit: m }保留 2 位小数如unparseCpu(1333333) → { value: 1.33, unit: m }。lib/util中getResourceStr(value, resourceType)与getResourceMetrics(item, metrics, resourceType)正是基于这些底层函数构建的getResourceStrlib/util.ts把 CPU/内存的数值换算为value unit字符串cpu走unparseCpu、memory走unparseRam。getResourceMetricslib/util.ts从KubeMetrics[]中按节点名查找对应指标用parseCpu/parseRam解析 usage 与 capacity返回[used, capacity]供节点资源使用率图表使用节点无status.capacity时返回[0, 0]。此外frontend/src/lib/units.ts 还提供了divideK8sResources(a, b, resourceType?)用于计算资源字段的比值如1Gi / 1Mi 1024、500m / 1(cpu) 0.5单元测试见 frontend/src/lib/units.test.ts。compareUnits忽略单位后缀的数值比较compareUnits(quantity1, quantity2)lib/util.ts去除空白并转小写后仅比较两个量的数值部分parseFloat忽略单位后缀export function compareUnits(quantity1: string, quantity2: string) { const qty1 quantity1.replace(/\s/g, ).toLowerCase(); const qty2 quantity2.replace(/\s/g, ).toLowerCase(); const n1 Number.parseFloat(qty1); const n2 Number.parseFloat(qty2); return !Number.isNaN(n1) !Number.isNaN(n2) n1 n2; }测试frontend/src/lib/util.test.ts确认compareUnits(500Mi, 500mi) true、compareUnits(1.5, 1.50) true、compareUnits(2.3Mi, 2.3) true而任一输入无法解析为数字时返回false。集群路径与过滤相关导出从 lib/cluster.ts 再导出的函数getClusterPrefixedPath(path?)frontend/src/lib/cluster.ts返回以/c/:cluster为前缀的路径若传入的 path 不以/开头会自动补上。getCluster()cluster.ts解析当前 URLElectron 下取window.location.hashWeb 下取 pathname返回当前集群名多集群场景下只取第一个分隔getClusterGroup()cluster.ts则返回完整的集群组数组。这些函数在详情页/列表页的集群路由解析中被大量复用。useFilterFunc / filterResource / filterGeneric全局过滤useFilterFunc(matchCriteria?)lib/util.ts从 Redux 读取全局filter状态返回一个(item, search?) boolean的过滤函数export function useFilterFuncT extends { [key: string]: any } | KubeObjectInterface | KubeEvent | KubeObjectInterface | KubeEvent (matchCriteria?: string[]) { const filter useTypedSelector(state state.filter); return (item: T, search?: string) { if (item?.metadata) { return filterResource(item as KubeObjectInterface | KubeEvent, filter, search, matchCriteria); } return filterGenericT(item, search, matchCriteria); }; }当条目带metadataKubernetes 对象时走filterResource否则走通用版本filterGeneric。其底层实现在 frontend/src/redux/filterSlice.tsfilterResourcefilterSlice.ts先做命名空间过滤filter.namespaces非空时条目必须属于其中一个命名空间随后做关键字搜索匹配项包括metadata.uid、namespace、name以及 labels 的键与值均转小写、子串匹配不命中再降级到filterGeneric。filterGenericfilterSlice.ts按matchCriteria中给定的 JSONPath如$.metadata.labels用jsonpath-plus求值把取到的字符串/数字/数组元素并入匹配池大小写不敏感地做includes判断空字符串会被剔除避免匹配一切。JSONPath 求值异常时打印 debug 日志并跳过该条件。配合 filterSlice.ts 导出的setNamespaceFilter、resetFilteraction 与useNamespaces选择器这套机制支撑了 Headlamp 资源列表页的命名空间选择器与搜索框联动。React HooksuseErrorState / useId / useURLStateuseErrorStateuseErrorState(dependentSetter?)lib/util.ts返回[error, setError]元组用于表单/页面的错误状态管理export function useErrorState(dependentSetter?: (...args: any) void) { const [error, setError] React.useStateApiError | null(null); React.useEffect(() { if (!!error !!dependentSetter) { dependentSetter(null); } }, [error]); return [error, setError] as const; }当错误产生时可选的dependentSetter会被调用典型用途清空依赖的错误字段实现错误联动清除。useIduseId(prefix )lib/util.ts基于 React 18 的React.useId()生成带前缀的唯一 ID并去除冒号export function useId(prefix ) { const reactId React.useId(); if (import.meta.env.UNDER_TEST) { return prefix id; } return prefix reactId.replace(/:/g, ); }UNDER_TEST下恒返回prefix id如myid确保快照不会因每次渲染的随机 ID 失效——这与timeAgo的测试时钟是同一设计思路。useURLStateuseURLState(key, defaultValue | params)lib/util.ts是URL 即状态的 Hook状态值同步写入 URL 查询参数刷新/分享链接后仍能恢复。支持两个重载useURLState(key, defaultValue)以数字为默认值。useURLState(key, { defaultValue, hideDefault?, prefix? })通过URLStateParamsT对象配置hideDefault默认true表示值等于默认值时从 URL 中删除该参数prefix会在 URL key 前加前缀.key形式的前缀源码见 lib/util.ts。实现要点lib/util.tskey为空字符串时退化为纯useState行为完全不读写 URL。初始化时从history.location.search读取 URL 值数字类型默认值时对 URL 值做Number()转换非法则回退默认值。值变化时若(value null || value defaultValue) hideDefault则删除 URL 参数否则urlParams.set(fullKey, ...)通过history.replace无刷新更新 URLhideDefault保证默认值不会污染 URL。该 Hook 广泛用于 Headlamp 中需要可分享/可刷新保持的 UI 状态例如表格页码、筛选条件等相关使用可见 frontend/src/components/common/Table/Table.tsx 与 frontend/src/components/common/SimpleTable.tsx。多集群数据聚合flattenClusterListItems 与 combineClusterListErrors虽然这两个函数未出现在 API 文档的导出列表中但它们定义于同一模块并承担多集群视图的关键聚合逻辑flattenClusterListItems(...args)lib/util.ts把每个集群一组条目的多个对象扁平化为单一条目数组null/空数组被过滤全部为空时返回null。combineClusterListErrors(...args)lib/util.ts用 lodashmerge合并各集群的错误映射仅保留非空错误无错误时返回null。两者的行为在 frontend/src/lib/util.test.ts 中有完整测试例如多集群条目合并、空集群返回null、跨集群错误合并与空错误剔除等。插件兼容性与实践建议lib/util被设计为既服务 Headlamp 内核、又对插件开发者开放的公共 API通过export { ... }再导出的过滤/集群函数与export * as auth、export * as units命名空间保持了对历史插件可能从lib/util直接 import的兼容性相关说明见 lib/util.ts 源码注释。auth命名空间frontend/src/lib/auth.ts提供getToken、setToken、getUserInfo、hasToken、logout、deleteTokens等认证函数。其实现支持 Redux 中ui.functionsToOverride.getToken/setToken的插件覆写机制默认情况下 token 存放在后端 HttpOnly Cookie 中通过backendFetch(/clusters/{cluster}/set-token)设置JavaScript 侧不可读取——只有插件覆写了getToken后才能从前端拿到 token源码注释明确标注了这一点见 auth.ts。插件/组件实践建议展示工作负载副本状态时直接用getReadyReplicas/getTotalReplicas自动兼容 Deployment/StatefulSet/DaemonSet 的字段差异展示节点资源用量时组合getResourceMetrics与units命名空间的 parse/unparse 函数避免手写单位换算列表搜索框与命名空间选择器联动时使用useFilterFuncfilterResource/filterGeneric以复用全局过滤语义需要可分享的状态如分页、筛选参数时优先使用useURLState而非裸useState渲染相对时间统一走timeAgo/formatDuration以继承与 Kubernetesapimachinery对齐的格式化风格及测试时钟约定。小结lib/util是 Headlamp 前端公共工具层的工具箱时间展示timeAgo/formatDuration/localeDate、资源换算normalizeUnit与units命名空间、副本与百分比统计、全局过滤useFilterFunc三件套、集群路径解析、URL 状态同步useURLState、稳定 ID 与错误状态useId/useErrorState以及多集群数据聚合均收敛于此。它的实现与测试frontend/src/lib/util.test.ts、frontend/src/lib/units.test.ts互为印证边界行为除零、负时长、非法时区、NaN 数量、亚字节数量都有明确约定是理解 Headlamp 前端工程化风格与编写高质量插件的绝佳起点。【免费下载链接】headlampA Kubernetes web UI that is fully-featured, user-friendly and extensible项目地址: https://gitcode.com/GitHub_Trending/he/headlamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考