Migrate NFS pages to HeaderPage

Always render the plugin header for page blueprints and move page-level actions into the Backstage UI header pattern for affected NFS pages.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-07 12:36:16 +01:00
parent b3466f6f78
commit aa29b508d1
58 changed files with 1474 additions and 556 deletions
+1
View File
@@ -62,6 +62,7 @@
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/ui": "workspace:^",
"@graphiql/react": "0.29.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
+7 -5
View File
@@ -77,11 +77,13 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({
path: '/api-docs',
routeRef: rootRoute,
loader: () =>
import('./components/ApiExplorerPage').then(m => (
<m.ApiExplorerIndexPage
initiallySelectedFilter={config.initiallySelectedFilter}
/>
)),
import('./components/ApiExplorerPage/DefaultApiExplorerPage').then(
m => (
<m.NfsApiExplorerPage
initiallySelectedFilter={config.initiallySelectedFilter}
/>
),
),
});
},
});
@@ -24,6 +24,7 @@ import {
TableProps,
} from '@backstage/core-components';
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import { HeaderPage } from '@backstage/ui';
import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog';
import {
EntityKindPicker,
@@ -67,6 +68,42 @@ export type DefaultApiExplorerPageProps = {
pagination?: EntityListPagination;
};
type ApiExplorerPageContentProps = {
initiallySelectedFilter: UserListFilterKind;
columns?: TableColumn<CatalogTableRow>[];
actions?: TableProps<CatalogTableRow>['actions'];
ownerPickerMode?: EntityOwnerPickerProps['mode'];
pagination?: EntityListPagination;
};
function ApiExplorerPageContent(props: ApiExplorerPageContentProps) {
const {
initiallySelectedFilter,
columns,
actions,
ownerPickerMode,
pagination,
} = props;
return (
<EntityListProvider pagination={pagination}>
<CatalogFilterLayout>
<CatalogFilterLayout.Filters>
<EntityKindPicker initialFilter="api" hidden />
<EntityTypePicker />
<UserListPicker initialFilter={initiallySelectedFilter} />
<EntityOwnerPicker mode={ownerPickerMode} />
<EntityLifecyclePicker />
<EntityTagPicker />
</CatalogFilterLayout.Filters>
<CatalogFilterLayout.Content>
<CatalogTable columns={columns || defaultColumns} actions={actions} />
</CatalogFilterLayout.Content>
</CatalogFilterLayout>
</EntityListProvider>
);
}
/**
* DefaultApiExplorerPage
* @public
@@ -89,6 +126,19 @@ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
const { allowed } = usePermission({
permission: catalogEntityCreatePermission,
});
const headerActions = (
<>
{allowed && (
<CreateButton
title={t('defaultApiExplorerPage.createButtonTitle')}
to={registerComponentLink?.()}
/>
)}
<SupportButton>
{t('defaultApiExplorerPage.supportButtonTitle')}
</SupportButton>
</>
);
return (
<PageWithHeader
@@ -98,36 +148,67 @@ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
pageTitleOverride={t('defaultApiExplorerPage.pageTitleOverride')}
>
<Content>
<ContentHeader title="">
{allowed && (
<CreateButton
title={t('defaultApiExplorerPage.createButtonTitle')}
to={registerComponentLink?.()}
/>
)}
<SupportButton>
{t('defaultApiExplorerPage.supportButtonTitle')}
</SupportButton>
</ContentHeader>
<EntityListProvider pagination={pagination}>
<CatalogFilterLayout>
<CatalogFilterLayout.Filters>
<EntityKindPicker initialFilter="api" hidden />
<EntityTypePicker />
<UserListPicker initialFilter={initiallySelectedFilter} />
<EntityOwnerPicker mode={ownerPickerMode} />
<EntityLifecyclePicker />
<EntityTagPicker />
</CatalogFilterLayout.Filters>
<CatalogFilterLayout.Content>
<CatalogTable
columns={columns || defaultColumns}
actions={actions}
/>
</CatalogFilterLayout.Content>
</CatalogFilterLayout>
</EntityListProvider>
<ContentHeader title="">{headerActions}</ContentHeader>
<ApiExplorerPageContent
initiallySelectedFilter={initiallySelectedFilter}
columns={columns}
actions={actions}
ownerPickerMode={ownerPickerMode}
pagination={pagination}
/>
</Content>
</PageWithHeader>
);
};
export const NfsApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
const {
initiallySelectedFilter = 'all',
columns,
actions,
ownerPickerMode,
pagination,
} = props;
const configApi = useApi(configApiRef);
const { t } = useTranslationRef(apiDocsTranslationRef);
const generatedSubtitle = t('defaultApiExplorerPage.subtitle', {
orgName: configApi.getOptionalString('organization.name') ?? 'Backstage',
});
const registerComponentLink = useRouteRef(registerComponentRouteRef);
const { allowed } = usePermission({
permission: catalogEntityCreatePermission,
});
const headerActions = (
<>
{allowed && (
<CreateButton
title={t('defaultApiExplorerPage.createButtonTitle')}
to={registerComponentLink?.()}
/>
)}
<SupportButton>
{t('defaultApiExplorerPage.supportButtonTitle')}
</SupportButton>
</>
);
return (
<>
<HeaderPage
title={t('defaultApiExplorerPage.title')}
subtitle={generatedSubtitle}
customActions={headerActions}
/>
<Content>
<ApiExplorerPageContent
initiallySelectedFilter={initiallySelectedFilter}
columns={columns}
actions={actions}
ownerPickerMode={ownerPickerMode}
pagination={pagination}
/>
</Content>
</>
);
};