定制指南:从分页、导出到自定义过滤器与完全自建 CatalogIndexPage`)
Backstage 软件目录Software Catalog定制指南从分页、导出到自定义过滤器与完全自建 CatalogIndexPage【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文面向仍在使用**旧版前端系统old frontend system**的 Backstage 应用系统讲解如何围绕默认的CatalogIndexPage组件进行深度定制启用分页与目录导出、调整默认筛选器与初始 Kind、增删表格列、扩展行操作actions乃至基于EntityFilter接口与useEntityListHook 编写全新过滤器最终完全自建一个属于你自己的目录首页。读完本文你将掌握backstage/plugin-catalog与backstage/plugin-catalog-react中绝大多数目录页定制入口并能直接套用到packages/app工程中。注意本文档面向仍在使用旧前端系统的 Backstage 应用。如果你的应用已经迁移到新前端系统请阅读当前版本指南Catalog Customization。定制前必读默认 CatalogIndexPage 与 props 全景Backstage 软件目录自带一个默认的CatalogIndexPage页面用于筛选和查找目录实体Entity该页面由backstage/create-app默认搭建完成。它提供了开箱即用的目录浏览体验但如果你需要修改默认首页行为——例如设置初始选中的筛选器、调整表格列、增删行操作、或者为目录添加自定义过滤器——就需要了解这个组件暴露的全部定制入口。从源码可以看到DefaultCatalogPageProps完整定义了这些 props见 DefaultCatalogPage.tsxProp类型默认值用途initiallySelectedFilterUserListFilterKindowned初始选中的用户列表筛选owned / starred / allinitialKindstringcomponent初始选中的实体 KindcolumnsTableColumn[] \| CatalogTableColumnsFunc内置默认列覆盖表格列actionsTableProps[actions]view / edit / star覆盖表格行操作tableOptionsTableProps[options]{}透传到底层表格的选项emptyContentReactNode—空列表占位内容ownerPickerModeEntityOwnerPickerProps[mode]owners-only属主选择器模式filtersReactNodeDefaultFilters自定义筛选器集合initiallySelectedNamespacesstring[]—初始选中的命名空间paginationEntityListPagination—分页配置v1.21.0exportSettingsCatalogExportSettings—目录导出配置下文将围绕这些 props 逐一展开并深入其对应的源码实现。启用分页Pagination目录首页的分页支持在 Backstagev1.21.0中加入使用该特性前请确保你的版本不低于此。启用方式非常简单给CatalogIndexPage传入paginationpropRoute path/catalog element{CatalogIndexPage pagination /} /从源码实现看pagination会被传递给EntityListProvider见 DefaultCatalogPage.tsx底层支持两种分页模式offset基于偏移量与cursor基于游标。同时插件内部提供了OffsetPaginatedCatalogTable与CursorPaginatedCatalogTable两种表格实现见 CatalogTable 目录。pagination还可以传入对象形式例如在自定义列一节中出现的pagination{{ mode: offset, limit: 20 }}用于指定分页模式与每页条数。目录导出Export目录导出功能允许用户将目录表格中的数据一键导出。启用方式是为CatalogIndexPage传入带enabled: true的exportSettingspropRoute path/catalog element{CatalogIndexPage exportSettings{{ enabled: true }} /} /启用后页面头部会出现一个导出按钮。点击后会打开一个对话框用户可以选择导出格式默认提供CSV与JSON两种以复选框形式勾选/取消勾选要包含的列默认全部预选。从源码看导出功能由 CatalogExportButton.tsx 组件实现它负责弹窗、格式下拉、列复选与导出触发当exportSettings?.enabled为真时该按钮会被渲染到页头见 DefaultCatalogPage.tsx。按钮默认展示在目录页右上角带下载图标与文字标题。导出配置接口CatalogExportSettings导出行为可以通过exportSettings配置CatalogExportSettings接口中的各种选项。该接口的源码定义位于 CatalogExportButton.tsxexport interface CatalogExportSettings { enabled?: boolean; /** * Array of columns to include in the export. * * Each column requires an entityFilterKey (dot-separated path into the entity object that is returned by the catalog api) and an optional title for display. * When title is omitted, entityFilterKey is used as the display title. * * Default columns are: name, type, owner and description. **/ columns?: CatalogExportSettingsColumn[]; /** * Map of custom export format handlers. * * Each map entry provides an exporter function and an optional display label. * Custom formats appear in the export dialog alongside built-in CSV and JSON options. **/ exporters?: Recordstring, CatalogExporterConfig; /** Callback function invoked after successful export completion. Useful for displaying notifications or triggering post-export actions. */ onSuccess?: () void; /** Callback function invoked if export fails. Receives an object containing the Error for error handling and user notification. */ onError?: (options: { error: Error }) void; /** When true, hides the built-in CSV and JSON export options. Useful when only custom exporters should be available. */ disableBuiltinExporters?: boolean; }各字段说明enabled是否显示导出按钮默认falsecolumns自定义导出列不配置时使用默认列metadata.nameName、spec.typeType、spec.ownerOwner、metadata.descriptionDescription。默认列定义在源码 CatalogExportButton.tsx 的DEFAULT_EXPORT_COLUMNS中exporters自定义导出格式处理器的映射表键为格式名如xml、yaml值为{ exporter, label? }。label缺省时格式下拉中会以键名大写显示onSuccess/onError导出成功/失败回调可用于弹通知或执行后续动作。若未提供组件内部会通过toastApi弹出默认的成功/失败提示见 CatalogExportButton.tsxdisableBuiltinExporters为true时隐藏内置的 CSV、JSON 选项适合只希望暴露自定义导出格式的场景。自定义导出列默认导出包含 name、type、owner、description 四列。导出对话框打开时所有已配置的列都会以复选框形式展示并处于预选状态用户可以取消勾选不想导出的列再确认导出。你可以自定义可用列import { CatalogIndexPage } from backstage/plugin-catalog; const customColumns [ { entityFilterKey: metadata.name, title: Name }, { entityFilterKey: metadata.namespace, title: Namespace }, { entityFilterKey: spec.owner, title: Owner }, ]; CatalogIndexPage exportSettings{{ enabled: true, columns: customColumns, }} /;列定义使用CatalogExportSettingsColumn结构entityFilterKey是实体对象中的点分路径dot-separated pathtitle是导出文件中的表头缺省时直接用entityFilterKey作为表头。其源码定义见 serializeEntities.ts。实际取值时代码通过getByPath按点分路径逐级解引用实体字段见同文件 serializeEntities.ts而在序列化 CSV 时还会对以、、-、开头的值加单引号前缀防止 CSV/公式注入见 serializeEntities.ts这说明导出功能在实现层面就已考虑了安全细节。自定义导出格式除 CSV 和 JSON 外你还可以通过提供自定义导出函数来增加新的导出格式。自定义导出器使用 async generator异步生成器实现流式下载数据边生成边写入磁盘在支持的浏览器中不会把整个导出内容缓存在内存里。import { CatalogIndexPage, CatalogExporter, CatalogExporterConfig, } from backstage/plugin-catalog; import { catalogApiRef } from backstage/plugin-catalog-react; // Custom exporter using async generator for streaming const xmlExporter: CatalogExporter ({ apis, columns, streamRequest }) { const catalogApi apis.get(catalogApiRef); // Return an async generator that yields XML chunks async function* generateXml() { yield ?xml version1.0 encodingUTF-8?\nentities\n; for await (const page of catalogApi.streamEntities(streamRequest)) { for (const entity of page) { // Serialize each entity to XML and yield immediately yield serializeEntityToXml(entity, columns); } } yield /entities; } return { generator: generateXml(), contentType: application/xml, }; }; const yamlExporter: CatalogExporter ({ apis, columns, streamRequest }) { const catalogApi apis.get(catalogApiRef); async function* generateYaml() { for await (const page of catalogApi.streamEntities(streamRequest)) { for (const entity of page) { yield serializeEntityToYaml(entity, columns); yield ---\n; // YAML document separator } } } return { generator: generateYaml(), contentType: application/x-yaml, }; }; const exporters: Recordstring, CatalogExporterConfig { xml: { exporter: xmlExporter, label: XML }, yaml: { exporter: yamlExporter, label: YAML }, }; CatalogIndexPage exportSettings{{ enabled: true, exporters, }} /;提供自定义格式后它们会与内置的 CSV、JSON 选项一起出现在导出对话框中若同时设置了disableBuiltinExporters: true则只会显示自定义格式。从源码层面理解这一机制CatalogExporter类型要求导出函数返回一个{ generator: AsyncGeneratorstring, void, unknown; contentType: string }结构见 useStreamingExport.ts。执行导出时useStreamingExportHook 会把生成器通过createStreamFromAsyncGenerator包装成流并调用streamDownload触发浏览器下载见 useStreamingExport.ts。内置的 CSV/JSON 导出同样是基于 async generator 实现的——streamEntitiesCsvGenerator逐页调用catalogApi.streamEntities并立即yield序列化结果JSON 导出器还会先yield [再在结尾yield \n]以保持合法 JSON 结构见 useStreamingExport.ts。值得一提的细节是如果调用方没有显式提供streamRequest导出会通过toStreamRequest(filters)从当前EntityList的筛选状态推导请求参数见 useStreamingExport.ts这意味着导出的数据与用户当前在目录页上的筛选视图保持一致而不是导出全量数据。成功/失败回调你还可以提供回调来处理导出成功或失败的情况CatalogIndexPage exportSettings{{ enabled: true, onSuccess: () { // Handle successful export notificationApi.success({ message: Export completed! }); }, onError: ({ error }) { // Handle export error notificationApi.error({ message: Export failed: ${error.message}, }); }, }} /onSuccess在导出流程成功结束后被调用onError接收{ error: Error }便于你自定义错误提示或上报。从源码看导出完成/失败后组件会依次触发这两个回调若未提供则回退到内置 toast 提示见 CatalogExportButton.tsx。组合示例以下示例组合了全部自定义选项CatalogIndexPage exportSettings{{ enabled: true, columns: [ { entityFilterKey: metadata.name, title: Name }, { entityFilterKey: spec.type, title: Type }, { entityFilterKey: spec.owner, title: Owner }, { entityFilterKey: metadata.namespace, title: Namespace }, ], exporters: { xml: { exporter: xmlExporter, label: XML }, yaml: { exporter: yamlExporter, label: YAML }, }, onSuccess: () { notificationApi.success({ message: Export completed! }); }, onError: ({ error }) { notificationApi.error({ message: Export failed: ${error.message}, }); }, }} /设置初始选中的筛选器Initially Selected Filter默认情况下目录页初始选中的筛选器是Owned。如果你的目录还在建设初期、实体不多这可能导致首页一开始显示空列表。如果你希望默认显示All可以这样修改Route path/catalog element{CatalogIndexPage initiallySelectedFilterall /} /可选值为owned、starred、all。从源码看该 prop 的默认值为owned并会被透传给DefaultFilters中的UserListPicker见 DefaultCatalogPage.tsx 与 DefaultFilters.tsx。设置初始选中的 KindInitially Selected Kind默认情况下进入目录页时初始选中的 Kind 是Component但你的组织可能有不同的需求——例如希望始终默认选中Domain可以这样配置Route path/catalog element{CatalogIndexPage initialKinddomain /} /可选值包括系统模型中的所有默认 Kind以及你自定义添加的任何 Kind。源码中该 prop 默认值为component见 DefaultCatalogPage.tsx并最终传递到EntityKindPicker的initialFilter见 DefaultFilters.tsx。属主选择器模式Owner Picker ModeOwner属主筛选器默认只包含实际拥有目录中实体的用户和/或用户组。如果你需要显示全部用户/组可以这样配置Route path/catalog element{CatalogIndexPage ownerPickerModeall /} /可选值为owners-only默认或all。该值会作为EntityOwnerPicker的modeprop 传入见 DefaultFilters.tsx。表格选项Table OptionsBackstage 中的表格基于material-table/core构建CatalogIndexPage提供了tableOptionsprop 让你在一定程度上定制底层表格但部分 Backstage 硬编码的设置无法修改。下面示例展示了如何用该 prop 禁用表格表头的搜索框Route path/catalog element{CatalogIndexPage tableOptions{{ search: false }} /} /tableOptions可设置大量选项其完整列表对应material-table/core的Options接口Backstage 当前使用的版本为v3.1.0。实际使用时建议以该接口的类型定义为准CatalogIndexPage会把它原样透传给CatalogTable见 DefaultCatalogPage.tsx。自定义表格列Customize ColumnsCatalogIndexPage中看到的列是面向大多数场景精选的起点但你完全可能希望为已有或自定义 Kind 增删列。为已有 Kind 添加列假设我们想为UserKind 添加一列 User Email。做法是覆盖传入CatalogIndexPage的columns。首先匹配要覆盖的实体 Kind并定义要展示的列const myColumnsFunc: CatalogTableColumnsFunc entityListContext { if (entityListContext.filters.kind?.value user) { return [ // Render existing columns ...CatalogTable.defaultColumnsFunc(entityListContext), // Add new columns here ]; } return CatalogTable.defaultColumnsFunc(entityListContext); };然后实现createUserEmailColumn函数并把它加入列列表。field用于从实体中取数据render则允许你自定义数据的展示方式const createUserEmailColumn (): TableColumnCatalogTableRow ({ title: User Email, field: entity.spec.profile.email, render: ({ entity }) ( OverflowTooltip text{entity.spec?.profile?.[email] || N/A} placementbottom-start / ), }); const myColumnsFunc: CatalogTableColumnsFunc entityListContext { if (entityListContext.filters.kind?.value user) { return [ // Render existing columns ...CatalogTable.defaultColumnsFunc(entityListContext), // Add new columns here createUserEmailColumn(), ]; } return CatalogTable.defaultColumnsFunc(entityListContext); };最后把myColumnsFunc传给CatalogIndexPageconst routes ( FlatRoutes Route path/catalog element{ CatalogIndexPage pagination{{ mode: offset, limit: 20 }} columns{myColumnsFunc} / } / {/* Other routes */} /FlatRoutes )这里涉及两个关键类型见 CatalogTable/types.tsCatalogTableRow表格行的数据结构包含entity完整实体与resolved预解析的 name、entityRef、所属关系等信息见 types.tsCatalogTableColumnsFunc接收entityListContext含当前筛选状态filters并返回列数组的函数类型见 types.ts。为自定义或特定 Kind 添加列另一个典型场景是为自定义Kind添加列该能力在 Backstagev1.23.0及以上可用。例如import { CatalogEntityPage, CatalogIndexPage, catalogPlugin, CatalogTable, CatalogTableColumnsFunc, } from backstage/plugin-catalog; const myColumnsFunc: CatalogTableColumnsFunc entityListContext { if (entityListContext.filters.kind?.value MyKind) { return [ CatalogTable.columns.createNameColumn(), CatalogTable.columns.createOwnerColumn(), ]; } return CatalogTable.defaultColumnsFunc(entityListContext); }; Route path/catalog element{CatalogIndexPage columns{myColumnsFunc} /} /CatalogTable.columns提供了如createNameColumn、createOwnerColumn等可复用的内置列工厂方便你为特定 Kind 快速组装列集合。:::note 以上示例中的文件内容为便于说明均做了精简。 :::自定义行操作Customize ActionsCatalogIndexPage默认带三个行操作view查看、edit编辑和star收藏。你可能会想添加更多。首先需要把mui/utils添加到packages/app/package.jsonyarn --cwd packages/app add mui/utils然后进行如下修改import { AlertDisplay, OAuthRequestDialog, SignInPage, TableProps, } from backstage/core-components; import { CatalogEntityPage, CatalogIndexPage, CatalogTableRow, catalogPlugin, } from backstage/plugin-catalog; import { Typography } from material-ui/core; import OpenInNew from material-ui/icons/OpenInNew; import { visuallyHidden } from mui/utils; const customActions: TablePropsCatalogTableRow[actions] [ ({ entity }) { const url https://backstage.io/; const title View - ${entity.metadata.name}; return { icon: () ( Typography style{visuallyHidden}{title}/Typography OpenInNew fontSizesmall / / ), tooltip: title, disabled: !url, onClick: () { if (!url) return; window.open(url, _blank); }, }; }, ]; Route path/catalog element{CatalogIndexPage actions{customActions} /} /:::note 以上App.tsx示例内容为便于说明做了精简。 :::需要特别说明上述自定义会覆盖现有操作。目前如果想保留默认操作并添加自己的操作唯一的办法是把默认操作defaultActions即 view / edit / star 的实现也复制到你的操作数组中。默认操作的源码位于 CatalogTable.tsx 的defaultActions定义处可直接参考其实现来保留原有行为。自定义筛选器Customize Filters自定义筛选器有多种方式通过 props 调整现有筛选器、增删默认筛选器、创建全新的自定义筛选器。下面分情况说明。默认筛选器 PropsDefault Filtersbackstage/plugin-catalog-react提供了一组默认筛选器DefaultFilters它聚合了前文提到的各种 props。用法如下import { DefaultFilters } from backstage/plugin-catalog-react; Route path/catalog element{ CatalogIndexPage filters{ DefaultFilters initialKindDomain initiallySelectedFilterall ownerPickerModeall / / } / } /;从源码看DefaultFilters实际渲染了 8 个筛选器组件EntityKindPicker、EntityTypePicker、UserListPicker、EntityOwnerPicker、EntityLifecyclePicker、EntityTagPicker、EntityProcessingStatusPicker和EntityNamespacePicker见 DefaultFilters.tsx。移除默认筛选器如果你不想使用 Lifecycle生命周期、Tag标签和 Processing Status处理状态筛选器可以这样移除import { EntityKindPicker, EntityTypePicker, UserListPicker, EntityOwnerPicker, EntityNamespacePicker, } from backstage/plugin-catalog-react; Route path/catalog element{ CatalogIndexPage filters{ EntityKindPicker / EntityTypePicker / UserListPicker / EntityOwnerPicker / EntityNamespacePicker / / } / } /;当你显式传入filters时DefaultCatalogPage会使用你提供的筛选器集合替换默认的DefaultFilters见 DefaultCatalogPage.tsx因此只保留你列出的筛选器即可。自定义筛选器你可以添加自定义筛选器。例如假设我们想按实体上的自定义注解company.com/security-tier进行筛选可以按以下步骤构建筛选器。首先创建一个实现EntityFilter接口的新筛选器import { EntityFilter } from backstage/plugin-catalog-react; import { Entity } from backstage/catalog-model; class EntitySecurityTierFilter implements EntityFilter { constructor(readonly values: string[]) {} filterEntity(entity: Entity): boolean { const tier entity.metadata.annotations?.[company.com/security-tier]; return tier ! undefined this.values.includes(tier); } }EntityFilter接口支持两类实现方式见 catalog-react 的 types.ts后端筛选backend filter通过实现getCatalogFilters()返回查询参数筛选条件会被下推到catalog-backend在查询阶段完成过滤前端筛选frontend filter通过实现filterEntity(entity)在实体从后端加载后于前端过滤。上面的EntitySecurityTierFilter使用了前端过滤方式只实现filterEntity。仓库中内置筛选器则提供了两种方式的参考例如EntityTagFilter同时实现了filterEntity与getCatalogFilters见 catalog-react 的 filters.ts既可在后端过滤也可在前端兜底而EntityKindFilter、EntityTypeFilter只实现getCatalogFilters属于纯后端筛选见 filters.ts。接下来以类型安全的方式用这个筛选器扩展默认筛选器集合。在筛选器旁边创建扩展默认结构的自定义筛选器类型export type CustomFilters DefaultEntityFilters { securityTiers?: EntitySecurityTierFilter; };为了控制这个筛选器可以创建一个显示安全等级复选框的 React 组件。该组件会用到useEntityListHook并把扩展后的筛选器类型作为泛型参数传入export const EntitySecurityTierPicker () { // The securityTiers key is recognized due to the CustomFilter generic const { filters: { securityTiers }, updateFilters, } useEntityListCustomFilters(); // Toggles the value, depending on whether its already selected function onChange(value: string) { const newTiers securityTiers?.values.includes(value) ? securityTiers.values.filter(tier tier ! value) : [...(securityTiers?.values ?? []), value]; updateFilters({ securityTiers: newTiers.length ? new EntitySecurityTierFilter(newTiers) : undefined, }); } const tierOptions [1, 2, 3]; return ( FormControl componentfieldset Typography variantbuttonSecurity Tier/Typography FormGroup {tierOptions.map(tier ( FormControlLabel key{tier} control{ Checkbox checked{securityTiers?.values.includes(tier)} onChange{() onChange(tier)} / } label{Tier ${tier}} / ))} /FormGroup /FormControl ); };现在把该组件加入CatalogIndexPageimport { DefaultFilters } from backstage/plugin-catalog-react; const routes ( FlatRoutes Navigate key/ tocatalog / Route path/catalog element{ CatalogIndexPage filters{ DefaultFilters / EntitySecurityTierPicker / / } / } / {/* ... */} /FlatRoutes );同样的方法也可以用来以不同接口定制_默认_筛选器——此时无需泛型参数因为筛选器结构与默认结构保持一致例如直接使用useEntityListDefaultEntityFilters()并覆盖默认筛选键即可。高级定制完全自建 CatalogIndexPage如果以上方案都无法满足你的需求你还可以选择创建完全自定义的CatalogIndexPage。import { PageWithHeader, Content, ContentHeader, SupportButton, } from backstage/core-components; import { useApi, configApiRef } from backstage/core-plugin-api; import { CatalogTable } from backstage/plugin-catalog; import { EntityListProvider, CatalogFilterLayout, EntityKindPicker, EntityLifecyclePicker, EntityNamespacePicker, EntityOwnerPicker, EntityProcessingStatusPicker, EntityTagPicker, EntityTypePicker, UserListPicker, } from backstage/plugin-catalog-react; export const CustomCatalogPage () { const orgName useApi(configApiRef).getOptionalString(organization.name) ?? Backstage; return ( PageWithHeader title{orgName} themeIdhome Content ContentHeader title SupportButtonAll your software catalog entities/SupportButton /ContentHeader EntityListProvider pagination CatalogFilterLayout CatalogFilterLayout.Filters EntityKindPicker / EntityTypePicker / UserListPicker / EntityOwnerPicker / EntityLifecyclePicker / EntityTagPicker / EntityProcessingStatusPicker / EntityNamespacePicker / /CatalogFilterLayout.Filters CatalogFilterLayout.Content CatalogTable / /CatalogFilterLayout.Content /CatalogFilterLayout /EntityListProvider /Content /PageWithHeader ); };上述是一个非常基础的全自定义CatalogIndexPage版本。这个示例建立在默认页面所使用的构建模块之上——即 DefaultCatalogPage.tsx 中的BaseCatalogPage实现。你可以深入探索各个组件的 props看看还能做哪些事情例如自定义EntityListProvider的pagination、自定义CatalogTable的columns/actions、在ContentHeader中追加导出按钮等。:::note 目录首页被设计为具有极小的代码足迹以便于定制但复制一份页面也意味着存在随版本演进而逐渐过时的风险。建议定期查看 catalog 插件的 CHANGELOG关注CatalogIndexPage相关 API 的变更。 :::要使用这个名为CustomCatalogPage的自定义页面需要修改路由const routes ( FlatRoutes Navigate key/ tocatalog / Route path/catalog element{CatalogIndexPage /} CustomCatalogPage / /Route {/* ... */} /FlatRoutes );总结围绕CatalogIndexPageBackstage 提供了一条从「零配置开箱即用」到「完全自建页面」的平滑定制路径轻量调整通过pagination、initiallySelectedFilter、initialKind、ownerPickerMode、tableOptions等 props 快速改变默认行为数据导出通过exportSettings启用导出并可自定义导出列、基于 async generator 的自定义格式如 XML、YAML、成功/失败回调甚至禁用内置格式展示层定制通过columns函数为已有或自定义 Kind 增删列通过actions覆盖行操作筛选逻辑定制使用DefaultFilters调整默认筛选器、移除不需要的筛选器或基于EntityFilter接口 useEntityListHook 编写完全自定义的筛选组件终极方案以EntityListProvider、CatalogFilterLayout、CatalogTable等构建模块为积木自建一个完全属于你的目录首页。所有 props 与类型的最终定义都可以在 DefaultCatalogPage.tsx、CatalogExportButton.tsx 与 CatalogTable/types.ts 中找到配合这些源码可以更准确地理解每个配置项的实际影响。如果你的应用已迁移到新前端系统请转而阅读新版指南以获取对应实现方式。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。