Headlamp 插件开发:基于 sidebar 示例插件深入理解侧边栏与路由注册体系
Headlamp 插件开发基于 sidebar 示例插件深入理解侧边栏与路由注册体系【免费下载链接】headlampA Kubernetes web UI that is fully-featured, user-friendly and extensible项目地址: https://gitcode.com/GitHub_Trending/he/headlampHeadlamp 是一款可扩展的 Kubernetes Web UI其左侧边栏Sidebar和 URL 路由体系是插件介入 UI 的两个核心扩展点。本文以仓库中的官方示例插件 sidebar 为主体完整讲解如何通过registerSidebarEntry、registerRoute及其过滤器 API 增删 Headlamp 侧边栏菜单项与页面路由并结合 frontend/src/plugin/registry.tsx 与 Redux slice 源码说明这些注册在底层是如何被存储、过滤和转换为/c/集群名/...形式 URL 的。读完本文你将能够独立编写、运行并调试一个自定义 Headlamp 插件并理解侧边栏条目与路由之间的绑定关系。一、运行示例插件示例插件位于 plugins/examples/sidebar按文档说明启动方式非常直接cd plugins/examples/sidebar npm install npm start # 此时观察 Headlamp 左侧边栏会发现菜单已经发生变化npm start背后调用的是 plugins/examples/sidebar/package.json 中定义的headlamp-plugin start脚本。该插件的 package.json 展示了 Headlamp 插件工程的典型骨架所有脚本start、build、format、lint、tsc、storybook、test、i18n都委托给headlamp-pluginCLI由插件工具链统一管理构建唯一的开发依赖是kinvolk/headlamp-plugin当前版本约束为^0.13.1插件运行时 API 正是从该包导入通过overrides将 typescript 固定在5.6.2并沿用headlamp-k8sESLint 与 Prettier 配置。插件主代码只有一个文件plugins/examples/sidebar/src/index.tsx另有一个 src/headlamp-plugin.d.ts其内容仅为/// reference typeskinvolk/headlamp-plugin /用于把工具链的类型声明挂到工程里。从该插件当前版本的代码看它完成的能力比 README 摘要所述更多除了放置Feedback菜单项、删除 Namespaces 菜单外它还演示了子菜单、自定义侧边栏、HOME 侧边栏、非集群路由等全部侧边栏 API 形态。下文按 API 逐个拆解。二、registerSidebarEntry添加侧边栏菜单项示例插件中几乎每个registerSidebarEntry调用都值得逐行阅读。API 从kinvolk/headlamp-plugin/lib导入最终实现位于 frontend/src/plugin/registry.tsxexport function registerSidebarEntry({ parent, name, label, url, useClusterURL true, icon, sidebar, entryType, sx, }: SidebarEntryProps) { store.dispatch(setSidebarItem({ name, label, url, parent, useClusterURL, icon, sidebar, entryType, sx })); }可以看到注册动作本质上是一次 Redux dispatch。各字段含义依据 frontend/src/components/Sidebar/sidebarSlice.ts 中的SidebarEntry接口注释字段类型/默认值说明namestring必填条目唯一标识作为 Redux 中entries的键labelstring必填显示文案parentstring | null父条目namenull表示顶级urlstring点击后跳转的 URL通常配合registerRouteuseClusterURLboolean默认true是否把 URL 写成/c/集群名/...形式iconiconify 图标字符串如mdi:comment-quote取值参考 iconify 的 MDI 图标集sidebarstring条目所属的侧边栏默认为集群侧边栏IN_CLUSTER也可取HOME或自定义字符串entryTypelink \| subheader默认可点击链接subheader渲染为不可点击的分节标题sxMUI SxProps对subheader条目的样式覆盖2.1 顶级条目与配套路由// 顶级条目。生成的链接 URL 为: /c/mycluster/feedback registerSidebarEntry({ parent: null, name: feedback, label: Feedback, url: /feedback, icon: mdi:comment-quote, }); // 与 URL 路径配套的组件渲染于 /c/mycluster/feedback registerRoute({ path: /feedback, sidebar: feedback, name: feedback, exact: true, component: () ( SectionBox titleFeedback textAligncenter paddingTop{2} TypographyEmbed your feedback forms here/Typography /SectionBox ), });这段代码揭示了侧边栏与路由的绑定关系条目里的url是相对路径真正的完整 URL 由运行时加上当前集群前缀得到例如集群名是mycluster时为/c/mycluster/feedback集群名为minikube时则是/c/minikube/feedback而registerRoute中的sidebar: feedback反向声明访问该路由时应高亮哪个侧边栏条目。2.2 嵌套子菜单// 另一个顶级菜单项链接 URL 为: /c/mycluster/feedback2 registerSidebarEntry({ parent: null, name: feedback2, label: Diff Feedback, url: /feedback2, icon: mdi:comment-quote }); // 挂在 feedback2 下的两个子菜单 registerSidebarEntry({ parent: feedback2, name: feedback3, label: More Feedback, url: /feedback3 }); registerSidebarEntry({ parent: feedback2, name: feedback4, label: Other Feedback, url: /feedback4 });parent指向任意已注册的条目名即可形成多级菜单子菜单项同样可以省略icon。2.3 非集群路由与 subheader// 挂在 cluster 下的二级条目但 URL 不带集群前缀 registerSidebarEntry({ parent: cluster, name: no-cluster-sublevel-link, label: No cluster link, url: /no-cluster-link, icon: mdi:airplane, useClusterURL: false, }); // 不可点击的分节标题 registerSidebarEntry({ parent: null, name: feedback-section, label: Feedback Tools, entryType: subheader, sx: { fontSize: 0.8rem, textTransform: none }, });useClusterURL: false表示该条目的链接就是/no-cluster-link本身与集群上下文无关。配套的 src/index.tsx 中还有更完整的形态registerRoute({ path: /no-cluster-link, sidebar: null, // 不激活任何侧边栏条目 name: no-cluster-link, exact: true, useClusterURL: false, // URL 中不包含 /c/mycluster/ noAuthRequired: true, // 查看该页面无需已认证 hideAppBar: true, // 隐藏顶部 AppBar component: () ( SectionBox titleNo Cluster Link textAligncenter paddingTop{2} TypographyYour component here/Typography /SectionBox ), });noAuthRequired: true使该页面在未选择集群、未登录时也可访问hideAppBar: true则隐藏页面顶部的应用栏适合做全屏或独立视图。2.4 创建全新侧边栏与 HOME 侧边栏sidebar字段不仅可以取值还可以造一个新侧边栏——当某个字符串值首次出现时Headlamp 会创建对应的全新侧边栏// 因 myplugin 这个侧边栏不存在此调用直接创建了一个全新侧边栏 registerSidebarEntry({ name: backtoclusters, label: Back to Clusters, url: /, icon: mdi:hexagon, sidebar: myplugin, }); // 再往刚创建的 myplugin 侧边栏里追加条目 registerSidebarEntry({ name: mypluginarea, label: Special Area, url: /mypluginarea, icon: mdi:comment-quote, sidebar: myplugin, });对应的路由则通过对象形式的sidebar字段同时指定哪个侧边栏 哪个条目并把页面挂到 HOME 侧边栏下// 加到 HOME 侧边栏不在集群内的条目 registerSidebarEntry({ name: mypluginsidebar, label: Special Plugin Area, url: /mypluginarea, icon: mdi:comment-quote, sidebar: HOME, }); registerRoute({ path: /mypluginarea, sidebar: { item: mypluginarea, sidebar: myplugin }, useClusterURL: false, noAuthRequired: true, name: mypluginarea, exact: true, component: () ( SectionBox titleSpecial Plugin Area textAligncenter paddingTop{2} TypographySee how the home sidebar is completely new?/Typography /SectionBox ), });内置侧边栏的取值在 sidebarSlice.ts 中定义为枚举export enum DefaultSidebars { HOME HOME, IN_CLUSTER IN-CLUSTER, }即集群侧边栏选中集群后显示的菜单与HOME 侧边栏集群选择页等场景显示的菜单是两条独立的菜单树。2.5 运行时动态移除条目示例中有一个点一下就消失的条目它演示了过滤器也可以延迟注册——在路由组件的useEffect里再调用一次registerSidebarEntryFilterregisterSidebarEntry({ parent: null, name: nothing-to-see-here, label: Click me and I will disappear, url: /feedback2, icon: mdi:glasses, }); registerRoute({ path: /feedback2, sidebar: feedback2, name: feedback2, exact: true, component: () { React.useEffect(() { // 该过滤器把 nothing-to-see-here 条目从侧边栏移除 registerSidebarEntryFilter(entry (entry.name nothing-to-see-here ? null : entry)); }, []); return ( SectionBox titleDiff Feedback textAligncenter paddingTop{2} TypographyDifferent feedback forms go here./Typography /SectionBox ); }, });三、registerRoute完整的路由注册frontend/src/lib/router/Route.tsx 定义了Route接口的全部字段字段默认值说明path必填URL 路径支持path-to-regexp语法如/namespaces/:nameexact-为true时仅当路径精确匹配才命中name-人类可读名称供createRouteURL按名查找useClusterURLtrueURL 是否带集群前缀旧字段noCluster已标记废弃noAuthRequired-无需认证即可访问该路由sidebar必填路由命中时激活的侧边栏条目名null不激活任何条目对象形式{ item, sidebar }可跨侧边栏定位component必填渲染的 React 组件hideAppBar-隐藏顶部 AppBarisFullWidth-全宽渲染以示例中的/feedback4为例它展示了子菜单路由的典型写法src/index.tsxregisterRoute({ path: /feedback4, sidebar: feedback4, name: feedback4, exact: true, component: () ( SectionBox titleOther Feedback textAligncenter paddingTop{2} TypographyOther feedback forms go here./Typography /SectionBox ), hideAppBar: true, // 该路由下隐藏顶部 AppBar });另外注意条目名与路由名建议保持一致如feedback/feedback因为路由的sidebar字段要精确引用条目的name否则高亮会错位。四、过滤器 API移除内置菜单项与路由Headlamp 的默认路由表Nodes、Namespaces、Workloads 等集中在 frontend/src/lib/router/index.tsx 中定义。插件无需修改前端源码即可用三个过滤器裁剪用户可见的 UI这正是 README 所述移除 Namespaces 侧边栏条目和路由的实现方式src/index.tsx// 移除 Workloads 顶级侧边栏菜单项 registerSidebarEntryFilter(entry (entry.name workloads ? null : entry)); // 移除 /workloads 路由 registerRouteFilter(route (route.path /workloads ? null : route)); // 移除二级侧边栏菜单项 Namespaces registerSidebarEntryFilter(entry (entry.name namespaces ? null : entry)); // 移除 /namespaces 路由 registerRouteFilter(route (route.path /namespaces ? null : route)); // 从 HOME 侧边栏移除 settings registerHomeSidebarEntryFilter(entry (entry.name settings ? null : entry));三个过滤器的分工实现见 frontend/src/plugin/registry.tsxregisterSidebarEntryFilter过滤/修改IN_CLUSTER 集群侧边栏条目返回null即删除该条目返回修改后的条目即改写它registerHomeSidebarEntryFilter同样语义但作用于HOME 侧边栏registerRouteFilter过滤/修改路由表返回null即从路由中删除——菜单项删了但路由没删用户仍可通过手输 URL 访问所以两者要成对出现。五、源码视角注册数据如何被存储与消费5.1 侧边栏状态sidebarSlicefrontend/src/components/Sidebar/sidebarSlice.ts 中的 reducer 说明了存储结构setSidebarItem(state, action: PayloadActionSidebarEntry) { state.entries[action.payload.name] castDraft(action.payload); }, setSidebarItemFilter(state, action: PayloadAction(entry) SidebarEntry | null) { state.filters.push(action.payload); }, setHomeSidebarItemFilter(state, action: PayloadAction(entry) SidebarEntry | null) { state.homeFilters.push(action.payload); },由此可以确认两点侧边栏条目按name存为字典同名注册会覆盖先前的条目——插件自定义条目应避免与内置条目如nodes、pods重名集群侧边栏与 HOME 侧边栏的过滤器分别累积在filters和homeFilters两个数组中渲染时各自应用这就是两套 Filter API 分离的原因。5.2 路由状态routesSlicefrontend/src/redux/routesSlice.ts 的结构类似但按path作键setRoute(state, action: PayloadActionRoute) { state.routes[action.payload.path] action.payload; }, setRouteFilter(state, action: PayloadAction(route) Route | null) { state.routeFilters.push(action.payload); },也就是说registerRoute时若path已存在会直接覆盖旧路由registerRouteFilter则按注册顺序对所有路由包括内置路由与插件路由逐一执行。5.3 URL 生成createRouteURL 与集群前缀前端跳转普遍使用createRouteURL(routeName, params)。frontend/src/lib/router/createRouteURL.tsx 的处理逻辑是先在 Redux 中存储的插件路由里按 name 查找找不到再按 path 查找此时会打印按路径匹配的弃用警告最后回退到内置路由表随后若路由useClusterURL为真则取当前集群参数拼入路径通过generatePath产出最终 URL。这段实现解释了示例中所有 URL 形态的来源侧边栏条目/路由useClusterURL实际访问 URLfeedbacktrue默认/c/集群名/feedbackfeedback2/3/4子菜单true默认/c/集群名/feedback2等no-cluster-sublevel-link/no-sidebar-linkfalse/no-cluster-linkmypluginareaHOME/自定义侧边栏false/mypluginareatable-props测试路由-/table-props/table-props是该插件顺带保留的一个 Table 组件测试页面与侧边栏主题无关可忽略。六、小结与工程建议加菜单registerSidebarEntry定义条目name/label/parent/url/icon/sidebar/entryType配合registerRoute提供页面组件两者通过route.sidebar entry.name绑定高亮做层级parent挂子菜单sidebar挂自定义侧边栏首次出现的字符串即新侧边栏sidebar: HOME进入 HOME 菜单树去菜单registerSidebarEntryFilterregisterRouteFilter成对删除内置条目与路由HOME 侧边栏用registerHomeSidebarEntryFilter控 URLuseClusterURL: false剥离集群前缀noAuthRequired: true开放未认证访问hideAppBar: true做沉浸式页面避坑条目名与路由 path 分别是侧边栏字典和路由字典的主键重复注册会覆盖旧值noCluster字段已废弃应使用useClusterURL。所有 API 的权威定义与 JSDoc 示例可继续参阅 frontend/src/plugin/registry.tsxregisterSidebarEntry见 L327 起、registerRoute见 L471 起路由默认表见 frontend/src/lib/router/index.tsx完整可运行代码见 plugins/examples/sidebar/src/index.tsx。【免费下载链接】headlampA Kubernetes web UI that is fully-featured, user-friendly and extensible项目地址: https://gitcode.com/GitHub_Trending/he/headlamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考