Headlamp 集群选择器覆盖示例插件:用 registerClusterChooser 替换顶栏集群切换按钮
【免费下载链接】headlampA Kubernetes web UI that is fully-featured, user-friendly and extensible项目地址: https://gitcode.com/GitHub_Trending/he/headlamp
本文以 Headlamp 官方示例插件plugins/examples/cluster-chooser为主体,讲解如何通过插件机制覆盖 Headlamp 顶栏默认的集群选择按钮(cluster chooser),包括完整的本地运行步骤、示例源码逐段解析,以及框架侧 registerClusterChooser 与 useClustersConf 的实现原理。读完后你可以掌握:如何注册自定义集群选择组件、如何利用K8s工具类读取集群配置、如何扩展“无集群”空状态页面,以及如何为该插件编写单元测试。
示例插件的定位
该示例插件的目标只有一个:把 Headlamp 顶栏中央的默认集群选择按钮替换为自定义按钮。默认情况下,这个按钮由内置组件 ClusterChooser 渲染,显示当前集群名称及图标;覆盖后,它会被插件提供的组件取代,点击后仍会弹出 Headlamp 原有的集群选择弹窗。
插件目录结构如下,核心代码全部位于src/index.tsx:
plugins/examples/cluster-chooser/ ├── src/ │ ├── headlamp-plugin.d.ts # 引入 @kinvolk/headlamp-plugin 的类型声明 │ ├── index.tsx # 插件主代码 │ └── index.test.tsx # 组件单元测试 ├── package.json ├── tsconfig.json └── README.md本地运行插件
在已安装 Node.js 的环境中,进入仓库中的插件目录执行以下命令即可启动(插件会由headlamp-plugin脚手架工具构建并以开发模式加载):
cd headlamp/plugins/examples/cluster-chooser/ npm install npm start运行成功后,在 Headlamp 顶栏中央可以看到被替换的集群选择按钮。
从 package.json 可以看到,该插件的全部脚本都委托给headlamp-pluginCLI:start、build、lint、tsc、test、i18n、storybook等,开发依赖仅@kinvolk/headlamp-plugin一项,并将 TypeScript 锁定为5.6.2。这种单依赖结构是 Headlamp 插件的标准形态,插件开发入门可参考仓库内的 插件构建文档。
核心实现:registerClusterChooser 覆盖集群按钮
下面是插件主文件 src/index.tsx 的关键代码,逐段说明:
import { ClusterChooserProps, ClusterEmptyStateProps, K8s, registerClusterChooser, registerClusterEmptyState, } from '@kinvolk/headlamp-plugin/lib'; import { Button } from '@mui/material';插件统一从@kinvolk/headlamp-plugin/lib导入注册函数与工具类,这是所有 Headlamp 插件的入口约定。
自定义按钮组件
/** Props for the ClusterChooserButton component. */ export interface ClusterChooserButtonProps { /** Handler called when the button is clicked. */ clickHandler: ClusterChooserProps['clickHandler']; /** Currently selected cluster name. */ cluster: ClusterChooserProps['cluster']; } /** A button that shows the current cluster and total cluster count using useClustersConf. */ export function ClusterChooserButton({ clickHandler, cluster }: ClusterChooserButtonProps) { const clusters = K8s.useClustersConf(); const clusterNames = clusters ? Object.keys(clusters) : []; return ( <Button onClick={clickHandler}> Our Cluster Chooser button. Cluster: {cluster} ({clusterNames.length} clusters) </Button> ); }组件要点:
clickHandler:框架注入的点击回调。插件不需要自己实现“打开集群选择弹窗”的逻辑,只需在按钮的onClick中调用该回调,Headlamp 会照常弹出集群选择界面。cluster:当前已选中集群的名称,由框架通过 props 传入,示例中直接展示为Cluster: minikube这类文本。K8s.useClustersConf():Headlamp 提供给插件的 Hook,用于读取当前所有集群的配置对象。示例通过Object.keys(clusters)统计集群数量并显示在按钮上(例如(3 clusters))。
注册覆盖
// Replaces the default cluster chooser in the top bar with a button that shows // the current cluster name and total number of configured clusters (e.g. "Cluster: minikube (3 clusters)"). registerClusterChooser(({ clickHandler, cluster }: ClusterChooserProps) => ( <ClusterChooserButton clickHandler={clickHandler} cluster={cluster} /> ));registerClusterChooser接受一个 React 组件(或元素),其 props 类型由 ClusterChooserProps 定义:
| 字段 | 类型 | 说明 |
|---|---|---|
clickHandler | (event?: React.MouseEvent) => void | 点击按钮时由框架触发的处理器,用于打开默认集群选择弹窗 |
cluster | string? | 当前选中的集群名称 |
selectedClusters | string[]? | 多选模式下已选中的集群列表 |
icon | string? | 集群徽标图标(默认按钮用于ClusterBadge展示) |
accentColor | string? | 集群主题强调色,可用于自定义按钮配色 |
其中clickHandler是框架要求的必填项:注册函数内部会将其分发给 Redux 状态,顶栏在渲染时把该回调与当前集群信息一起传给插件组件。
框架侧的覆盖机制
从源码结构看,覆盖流程可以分为三段:
注册入口。registerClusterChooser 的实现非常直接:
export function registerClusterChooser(chooser: ClusterChooserType) { store.dispatch(uiSlice.actions.setClusterChooserButton(chooser)); }它把插件提供的组件写入
uiSlice中的clusterChooserButton状态,因此后注册的插件会覆盖先前的注册结果。函数文档注释中也给出了最小示例:registerClusterChooser(({ clickHandler, cluster }) => <button onClick={clickHandler}>...</button>)。默认按钮。未被覆盖时,顶栏渲染内置的 ClusterChooser。它是一个
React.forwardRef组件,使用 MUIButton与ClusterBadge渲染集群名称,并处理了多选场景:选中多个集群时若不超过 2 个则内联展示名称,否则显示{{count}} clusters文案。插件组件注册后,顶栏改为渲染插件组件,因此插件只需要负责“长什么样”,弹窗交互仍由框架接管。兼容旧 API。
Registry类上保留了registerClusterChooserComponent方法,但已被标记@deprecated,会打印警告并转发到函数式 API(见 registry.tsx 第 306-311 行)。新插件应直接使用registerClusterChooser。
useClustersConf 的数据来源
示例按钮统计的集群数量来自K8s.useClustersConf()。其框架侧实现在 frontend/src/lib/k8s/index.ts:该 Hook 从 Redux 的configstate 中读取clusters与allClusters并深拷贝合并;若存在statelessClusters(无状态集群,例如通过 URL token 注入的集群),也会合并进结果中。也就是说,插件按钮上显示的集群总数同时包含常规配置集群与无状态集群。
扩展空状态:registerClusterEmptyState
示例插件同时演示了第二个覆盖点:当用户尚未配置任何集群时,Headlamp 展示的“无集群”空状态页面。插件代码:
/** Demonstrates how a product can extend Headlamp's no-cluster experience. */ export function CustomClusterEmptyState({ defaultContent }: ClusterEmptyStateProps) { return ( <section> <p>Choose a cluster provider</p> {defaultContent} </section> ); } registerClusterEmptyState(CustomClusterEmptyState);对应框架 API 在 registry.tsx 第 994 行:registerClusterEmptyState接收一个产品方提供的组件,该组件会拿到 Headlamp 的默认内容defaultContent,产品可以将其包裹在自定义文案或布局中,而不是完全丢弃原有内容;再次注册会替换此前的组件。这个 API 面向的产品化场景是:在默认的连接引导之上追加厂商自己的集群提供者入口(如云厂商一键接入)。
测试验证
插件的 index.test.tsx 用 Vitest + Testing Library 验证了按钮的核心行为,值得参考:
- 集群未加载时显示 0:构造一个
clusters: null的 Redux store 渲染组件,断言出现0 clusters,覆盖useClustersConf()返回空值的分支; - 展示集群数量:store 中放入 1 个集群
my-cluster,断言出现Cluster: my-cluster与1 clusters; - 计入无状态集群:store 中同时包含 1 个常规集群与 1 个
statelessClusters集群,断言总数为2 clusters,验证了上面提到的合并逻辑。
测试的关键技巧是:ClusterChooserButton通过K8s.useClustersConf()读取的是 Reduxconfigslice,因此测试只需configureStore一个伪造的configreducer,再用<Provider store={...}>包裹组件,即可完全脱离真实 Headlamp 环境运行。
小结与进一步阅读
- 覆盖顶栏集群选择按钮只需调用
registerClusterChooser,传入实现ClusterChooserProps的组件,并把框架传入的clickHandler绑定到自身按钮的点击事件上; - 需要读取集群配置时使用
K8s.useClustersConf(),它会合并常规集群与无状态集群; - 需要定制“无集群”页面时使用
registerClusterEmptyState,并保留defaultContent; - 相关源码入口:registerClusterChooser 实现、默认 ClusterChooser 组件、useClustersConf 实现;
- 插件开发通用流程见 docs/development/plugins/building.md,同类覆盖式示例还可参考 change-logo 插件 与 ui-panels 插件。
【免费下载链接】headlampA Kubernetes web UI that is fully-featured, user-friendly and extensible项目地址: https://gitcode.com/GitHub_Trending/he/headlamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考