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:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
'@backstage/plugin-app': patch
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Pages created with `PageBlueprint` now render the plugin header by default in the new frontend system, and `HeaderPage` now supports subtitles for page-level headers.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-auth': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
'@backstage/plugin-catalog-unprocessed-entities': patch
|
||||
'@backstage/plugin-devtools': patch
|
||||
'@backstage/plugin-notifications': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-search': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
New frontend system pages now use the default plugin header together with `HeaderPage` instead of the legacy core page header pattern.
|
||||
@@ -75,6 +75,9 @@ export const PageBlueprint = createExtensionBlueprint({
|
||||
const icon = params.icon;
|
||||
const pluginId = node.spec.plugin.pluginId;
|
||||
const noHeader = params.noHeader ?? false;
|
||||
const resolvedTitle =
|
||||
title ?? node.spec.plugin.title ?? node.spec.plugin.pluginId;
|
||||
const resolvedIcon = icon ?? node.spec.plugin.icon;
|
||||
|
||||
yield coreExtensionData.routePath(config.path ?? params.path);
|
||||
if (params.loader) {
|
||||
@@ -85,8 +88,8 @@ export const PageBlueprint = createExtensionBlueprint({
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
title={title ?? node.spec.plugin.title ?? node.spec.plugin.pluginId}
|
||||
icon={icon ?? node.spec.plugin.icon}
|
||||
title={resolvedTitle}
|
||||
icon={resolvedIcon}
|
||||
noHeader={noHeader}
|
||||
headerActions={headerActions}
|
||||
>
|
||||
@@ -117,8 +120,8 @@ export const PageBlueprint = createExtensionBlueprint({
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
title={title}
|
||||
icon={icon}
|
||||
title={resolvedTitle}
|
||||
icon={resolvedIcon}
|
||||
tabs={tabs}
|
||||
headerActions={headerActions}
|
||||
>
|
||||
@@ -147,7 +150,11 @@ export const PageBlueprint = createExtensionBlueprint({
|
||||
const headerActionsApi = useApi(pluginHeaderActionsApiRef);
|
||||
const headerActions = headerActionsApi.getPluginHeaderActions(pluginId);
|
||||
return (
|
||||
<PageLayout title={title} icon={icon} headerActions={headerActions} />
|
||||
<PageLayout
|
||||
title={resolvedTitle}
|
||||
icon={resolvedIcon}
|
||||
headerActions={headerActions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
yield coreExtensionData.reactElement(<PageContent />);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -84,22 +84,21 @@ export const PageLayout = SwappableComponentBlueprint.make({
|
||||
[tabs],
|
||||
);
|
||||
|
||||
if (tabsWithMatchStrategy) {
|
||||
return (
|
||||
<>
|
||||
{!noHeader && (
|
||||
<PluginHeader
|
||||
title={title}
|
||||
icon={icon}
|
||||
tabs={tabsWithMatchStrategy}
|
||||
customActions={headerActions}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
if (noHeader) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
return <>{children}</>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PluginHeader
|
||||
title={title}
|
||||
icon={icon}
|
||||
tabs={tabsWithMatchStrategy}
|
||||
customActions={headerActions}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@remixicon/react": "^4.6.0",
|
||||
"react-use": "^17.2.4"
|
||||
|
||||
@@ -79,8 +79,8 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({
|
||||
path: '/catalog-graph',
|
||||
routeRef: catalogGraphRouteRef,
|
||||
loader: () =>
|
||||
import('./components/CatalogGraphPage').then(m => (
|
||||
<m.CatalogGraphPage {...config} />
|
||||
import('./components/CatalogGraphPage/CatalogGraphPage').then(m => (
|
||||
<m.NfsCatalogGraphPage {...config} />
|
||||
)),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import {
|
||||
entityRouteRef,
|
||||
humanizeEntityRef,
|
||||
@@ -116,21 +117,23 @@ const useStyles = makeStyles(
|
||||
{ name: 'PluginCatalogGraphCatalogGraphPage' },
|
||||
);
|
||||
|
||||
export const CatalogGraphPage = (
|
||||
props: {
|
||||
initialState?: {
|
||||
selectedRelations?: string[];
|
||||
selectedKinds?: string[];
|
||||
rootEntityRefs?: string[];
|
||||
maxDepth?: number;
|
||||
unidirectional?: boolean;
|
||||
mergeRelations?: boolean;
|
||||
direction?: Direction;
|
||||
showFilters?: boolean;
|
||||
curve?: 'curveStepBefore' | 'curveMonotoneX';
|
||||
};
|
||||
} & Partial<EntityRelationsGraphProps>,
|
||||
) => {
|
||||
type CatalogGraphPageProps = {
|
||||
initialState?: {
|
||||
selectedRelations?: string[];
|
||||
selectedKinds?: string[];
|
||||
rootEntityRefs?: string[];
|
||||
maxDepth?: number;
|
||||
unidirectional?: boolean;
|
||||
mergeRelations?: boolean;
|
||||
direction?: Direction;
|
||||
showFilters?: boolean;
|
||||
curve?: 'curveStepBefore' | 'curveMonotoneX';
|
||||
};
|
||||
} & Partial<EntityRelationsGraphProps>;
|
||||
|
||||
function CatalogGraphPageContent(
|
||||
props: CatalogGraphPageProps & { headerVariant: 'legacy' | 'bui' },
|
||||
) {
|
||||
const { relationPairs, initialState, entityFilter } = props;
|
||||
const { t } = useTranslationRef(catalogGraphTranslationRef);
|
||||
const navigate = useNavigate();
|
||||
@@ -185,93 +188,125 @@ export const CatalogGraphPage = (
|
||||
[catalogEntityRoute, navigate, setRootEntityNames, analytics],
|
||||
);
|
||||
|
||||
const pageBody = (
|
||||
<Grid container alignItems="stretch" className={classes.container}>
|
||||
{showFilters && (
|
||||
<Grid item xs={12} lg={2} className={classes.filters}>
|
||||
<MaxDepthFilter value={maxDepth} onChange={setMaxDepth} />
|
||||
<SelectedKindsFilter
|
||||
value={selectedKinds}
|
||||
onChange={setSelectedKinds}
|
||||
/>
|
||||
<SelectedRelationsFilter
|
||||
value={selectedRelations}
|
||||
onChange={setSelectedRelations}
|
||||
/>
|
||||
<DirectionFilter value={direction} onChange={setDirection} />
|
||||
<CurveFilter value={curve} onChange={setCurve} />
|
||||
<SwitchFilter
|
||||
value={unidirectional}
|
||||
onChange={setUnidirectional}
|
||||
label={t('catalogGraphPage.simplifiedSwitchLabel')}
|
||||
/>
|
||||
<SwitchFilter
|
||||
value={mergeRelations}
|
||||
onChange={setMergeRelations}
|
||||
label={t('catalogGraphPage.mergeRelationsSwitchLabel')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs className={classes.fullHeight}>
|
||||
<Paper className={classes.graphWrapper}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
display="block"
|
||||
className={classes.legend}
|
||||
>
|
||||
<ZoomOutMap className="icon" />{' '}
|
||||
{t('catalogGraphPage.zoomOutDescription')}
|
||||
</Typography>
|
||||
<EntityRelationsGraph
|
||||
{...props}
|
||||
rootEntityNames={rootEntityNames}
|
||||
maxDepth={maxDepth}
|
||||
kinds={
|
||||
selectedKinds && selectedKinds.length > 0
|
||||
? selectedKinds
|
||||
: undefined
|
||||
}
|
||||
relations={
|
||||
selectedRelations && selectedRelations.length > 0
|
||||
? selectedRelations
|
||||
: undefined
|
||||
}
|
||||
mergeRelations={mergeRelations}
|
||||
unidirectional={unidirectional}
|
||||
onNodeClick={onNodeClick}
|
||||
direction={direction}
|
||||
relationPairs={relationPairs}
|
||||
entityFilter={entityFilter}
|
||||
className={classes.graph}
|
||||
zoom="enabled"
|
||||
curve={curve}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
const filterAction = (
|
||||
<ToggleButton
|
||||
value="show filters"
|
||||
selected={showFilters}
|
||||
onChange={() => toggleShowFilters()}
|
||||
>
|
||||
<FilterListIcon /> {t('catalogGraphPage.filterToggleButtonTitle')}
|
||||
</ToggleButton>
|
||||
);
|
||||
const headerActions = (
|
||||
<>
|
||||
{filterAction}
|
||||
<SupportButton>
|
||||
{t('catalogGraphPage.supportButtonDescription')}
|
||||
</SupportButton>
|
||||
</>
|
||||
);
|
||||
const subtitle = rootEntityNames.map(e => humanizeEntityRef(e)).join(', ');
|
||||
|
||||
if (props.headerVariant === 'bui') {
|
||||
return (
|
||||
<>
|
||||
<HeaderPage
|
||||
title={t('catalogGraphPage.title')}
|
||||
subtitle={subtitle}
|
||||
customActions={headerActions}
|
||||
/>
|
||||
<Content stretch className={classes.content}>
|
||||
{pageBody}
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={t('catalogGraphPage.title')}
|
||||
subtitle={rootEntityNames.map(e => humanizeEntityRef(e)).join(', ')}
|
||||
/>
|
||||
<Header title={t('catalogGraphPage.title')} subtitle={subtitle} />
|
||||
<Content stretch className={classes.content}>
|
||||
<ContentHeader
|
||||
titleComponent={
|
||||
<ToggleButton
|
||||
value="show filters"
|
||||
selected={showFilters}
|
||||
onChange={() => toggleShowFilters()}
|
||||
>
|
||||
<FilterListIcon /> {t('catalogGraphPage.filterToggleButtonTitle')}
|
||||
</ToggleButton>
|
||||
}
|
||||
>
|
||||
<ContentHeader titleComponent={filterAction}>
|
||||
<SupportButton>
|
||||
{t('catalogGraphPage.supportButtonDescription')}
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
<Grid container alignItems="stretch" className={classes.container}>
|
||||
{showFilters && (
|
||||
<Grid item xs={12} lg={2} className={classes.filters}>
|
||||
<MaxDepthFilter value={maxDepth} onChange={setMaxDepth} />
|
||||
<SelectedKindsFilter
|
||||
value={selectedKinds}
|
||||
onChange={setSelectedKinds}
|
||||
/>
|
||||
<SelectedRelationsFilter
|
||||
value={selectedRelations}
|
||||
onChange={setSelectedRelations}
|
||||
/>
|
||||
<DirectionFilter value={direction} onChange={setDirection} />
|
||||
<CurveFilter value={curve} onChange={setCurve} />
|
||||
<SwitchFilter
|
||||
value={unidirectional}
|
||||
onChange={setUnidirectional}
|
||||
label={t('catalogGraphPage.simplifiedSwitchLabel')}
|
||||
/>
|
||||
<SwitchFilter
|
||||
value={mergeRelations}
|
||||
onChange={setMergeRelations}
|
||||
label={t('catalogGraphPage.mergeRelationsSwitchLabel')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs className={classes.fullHeight}>
|
||||
<Paper className={classes.graphWrapper}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
display="block"
|
||||
className={classes.legend}
|
||||
>
|
||||
<ZoomOutMap className="icon" />{' '}
|
||||
{t('catalogGraphPage.zoomOutDescription')}
|
||||
</Typography>
|
||||
<EntityRelationsGraph
|
||||
{...props}
|
||||
rootEntityNames={rootEntityNames}
|
||||
maxDepth={maxDepth}
|
||||
kinds={
|
||||
selectedKinds && selectedKinds.length > 0
|
||||
? selectedKinds
|
||||
: undefined
|
||||
}
|
||||
relations={
|
||||
selectedRelations && selectedRelations.length > 0
|
||||
? selectedRelations
|
||||
: undefined
|
||||
}
|
||||
mergeRelations={mergeRelations}
|
||||
unidirectional={unidirectional}
|
||||
onNodeClick={onNodeClick}
|
||||
direction={direction}
|
||||
relationPairs={relationPairs}
|
||||
entityFilter={entityFilter}
|
||||
className={classes.graph}
|
||||
zoom="enabled"
|
||||
curve={curve}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{pageBody}
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const CatalogGraphPage = (props: CatalogGraphPageProps) => (
|
||||
<CatalogGraphPageContent {...props} headerVariant="legacy" />
|
||||
);
|
||||
|
||||
export const NfsCatalogGraphPage = (props: CatalogGraphPageProps) => (
|
||||
<CatalogGraphPageContent {...props} headerVariant="bui" />
|
||||
);
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/plugin-permission-react": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
|
||||
@@ -43,9 +43,9 @@ const catalogImportPage = PageBlueprint.make({
|
||||
path: '/catalog-import',
|
||||
routeRef: rootRouteRef,
|
||||
loader: () =>
|
||||
import('./components/ImportPage').then(m => (
|
||||
import('./components/ImportPage/ImportPage').then(m => (
|
||||
<RequirePermission permission={catalogEntityCreatePermission}>
|
||||
<m.ImportPage />
|
||||
<m.NfsImportPage />
|
||||
</RequirePermission>
|
||||
)),
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||
@@ -52,17 +53,22 @@ export const DefaultImportPage = () => {
|
||||
<ImportStepper />
|
||||
</Grid>,
|
||||
];
|
||||
const headerTitle = t('defaultImportPage.headerTitle');
|
||||
const supportAction = (
|
||||
<SupportButton>
|
||||
{t('defaultImportPage.supportTitle', { appTitle })}
|
||||
</SupportButton>
|
||||
);
|
||||
const contentHeaderTitle = t('defaultImportPage.contentHeaderTitle', {
|
||||
appTitle,
|
||||
});
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header title={t('defaultImportPage.headerTitle')} />
|
||||
<Header title={headerTitle} />
|
||||
<Content>
|
||||
<ContentHeader
|
||||
title={t('defaultImportPage.contentHeaderTitle', { appTitle })}
|
||||
>
|
||||
<SupportButton>
|
||||
{t('defaultImportPage.supportTitle', { appTitle })}
|
||||
</SupportButton>
|
||||
<ContentHeader title={contentHeaderTitle}>
|
||||
{supportAction}
|
||||
</ContentHeader>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
@@ -72,3 +78,39 @@ export const DefaultImportPage = () => {
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsDefaultImportPage = () => {
|
||||
const { t } = useTranslationRef(catalogImportTranslationRef);
|
||||
const theme = useTheme();
|
||||
const configApi = useApi(configApiRef);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const appTitle = configApi.getOptionalString('app.title') || 'Backstage';
|
||||
|
||||
const contentItems = [
|
||||
<Grid key={0} item xs={12} md={4} lg={6} xl={8}>
|
||||
<ImportInfoCard />
|
||||
</Grid>,
|
||||
|
||||
<Grid key={1} item xs={12} md={8} lg={6} xl={4}>
|
||||
<ImportStepper />
|
||||
</Grid>,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderPage
|
||||
title={t('defaultImportPage.contentHeaderTitle', { appTitle })}
|
||||
customActions={
|
||||
<SupportButton>
|
||||
{t('defaultImportPage.supportTitle', { appTitle })}
|
||||
</SupportButton>
|
||||
}
|
||||
/>
|
||||
<Content>
|
||||
<Grid container spacing={2}>
|
||||
{isMobile ? contentItems : contentItems.reverse()}
|
||||
</Grid>
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { useOutlet } from 'react-router-dom';
|
||||
import { DefaultImportPage } from '../DefaultImportPage';
|
||||
import { NfsDefaultImportPage } from '../DefaultImportPage/DefaultImportPage';
|
||||
|
||||
/**
|
||||
* The whole catalog import page.
|
||||
@@ -27,3 +28,9 @@ export const ImportPage = () => {
|
||||
|
||||
return outlet || <DefaultImportPage />;
|
||||
};
|
||||
|
||||
export const NfsImportPage = () => {
|
||||
const outlet = useOutlet();
|
||||
|
||||
return outlet || <NfsDefaultImportPage />;
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^",
|
||||
"@backstage/plugin-devtools-react": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
|
||||
@@ -51,7 +51,7 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({
|
||||
routeRef: rootRouteRef,
|
||||
loader: () =>
|
||||
import('../components/UnprocessedEntities').then(m => (
|
||||
<m.UnprocessedEntities />
|
||||
<m.NfsUnprocessedEntities />
|
||||
)),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { ChangeEvent, useState } from 'react';
|
||||
|
||||
import { Page, Header, Content } from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import TabContext from '@material-ui/lab/TabContext';
|
||||
@@ -66,3 +67,14 @@ export const UnprocessedEntities = () => {
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsUnprocessedEntities = () => {
|
||||
return (
|
||||
<>
|
||||
<HeaderPage title="Unprocessed Entities" />
|
||||
<Content>
|
||||
<UnprocessedEntitiesContent />
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,13 +27,14 @@ import useAsync from 'react-use/esm/useAsync';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Box from '@material-ui/core/Box';
|
||||
|
||||
import { Header, Breadcrumbs } from '@backstage/core-components';
|
||||
import { Breadcrumbs } from '@backstage/core-components';
|
||||
import {
|
||||
useApi,
|
||||
useRouteRef,
|
||||
useRouteRefParams,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/frontend-plugin-api';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
|
||||
import {
|
||||
Entity,
|
||||
@@ -57,12 +58,11 @@ import { EntityContextMenu } from '../../../components/EntityContextMenu';
|
||||
import { rootRouteRef, unregisterRedirectRouteRef } from '../../../routes';
|
||||
|
||||
function headerProps(
|
||||
paramKind: string | undefined,
|
||||
_paramKind: string | undefined,
|
||||
paramNamespace: string | undefined,
|
||||
paramName: string | undefined,
|
||||
entity: Entity | undefined,
|
||||
): { headerTitle: string; headerType: string } {
|
||||
const kind = paramKind ?? entity?.kind ?? '';
|
||||
): { headerTitle: string } {
|
||||
const namespace = paramNamespace ?? entity?.metadata.namespace ?? '';
|
||||
const name =
|
||||
entity?.metadata.title ?? paramName ?? entity?.metadata.name ?? '';
|
||||
@@ -71,14 +71,6 @@ function headerProps(
|
||||
headerTitle: `${name}${
|
||||
namespace && namespace !== DEFAULT_NAMESPACE ? ` in ${namespace}` : ''
|
||||
}`,
|
||||
headerType: (() => {
|
||||
let t = kind.toLocaleLowerCase('en-US');
|
||||
if (entity && entity.spec && 'type' in entity.spec) {
|
||||
t += ' — ';
|
||||
t += (entity.spec as { type: string }).type.toLocaleLowerCase('en-US');
|
||||
}
|
||||
return t;
|
||||
})(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -202,13 +194,6 @@ export function EntityHeader(props: {
|
||||
subtitle,
|
||||
} = props;
|
||||
const { entity } = useAsyncEntity();
|
||||
const { kind, namespace, name } = useRouteRefParams(entityRouteRef);
|
||||
const { headerTitle: entityFallbackText, headerType: type } = headerProps(
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
entity,
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
@@ -265,28 +250,42 @@ export function EntityHeader(props: {
|
||||
);
|
||||
|
||||
const inspectDialogOpen = typeof selectedInspectEntityDialogTab === 'string';
|
||||
const headerTitle = (
|
||||
<Box display="flex" flexDirection="column">
|
||||
{title ?? <EntityHeaderTitle />}
|
||||
{entity && (
|
||||
<Box mt={1}>
|
||||
<EntityLabels entity={entity} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Header
|
||||
pageTitleOverride={entityFallbackText}
|
||||
type={type}
|
||||
title={title ?? <EntityHeaderTitle />}
|
||||
subtitle={
|
||||
subtitle ?? (
|
||||
<EntityHeaderSubtitle parentEntityRelations={parentEntityRelations} />
|
||||
)
|
||||
}
|
||||
>
|
||||
<>
|
||||
<HeaderPage
|
||||
title={headerTitle}
|
||||
subtitle={
|
||||
subtitle ?? (
|
||||
<EntityHeaderSubtitle
|
||||
parentEntityRelations={parentEntityRelations}
|
||||
/>
|
||||
)
|
||||
}
|
||||
customActions={
|
||||
entity ? (
|
||||
<EntityContextMenu
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
contextMenuItems={contextMenuItems}
|
||||
onInspectEntity={openInspectEntityDialog}
|
||||
onUnregisterEntity={openUnregisterEntityDialog}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
{entity && (
|
||||
<>
|
||||
<EntityLabels entity={entity} />
|
||||
<EntityContextMenu
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
contextMenuItems={contextMenuItems}
|
||||
onInspectEntity={openInspectEntityDialog}
|
||||
onUnregisterEntity={openUnregisterEntityDialog}
|
||||
/>
|
||||
<InspectEntityDialog
|
||||
entity={entity!}
|
||||
initialTab={
|
||||
@@ -306,6 +305,6 @@ export function EntityHeader(props: {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import {
|
||||
Content,
|
||||
Link,
|
||||
Page,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
@@ -150,7 +149,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
const { t } = useTranslationRef(catalogTranslationRef);
|
||||
|
||||
return (
|
||||
<Page themeId={entity?.spec?.type?.toString() ?? 'home'}>
|
||||
<>
|
||||
{header ?? (
|
||||
<EntityHeader
|
||||
parentEntityRelations={parentEntityRelations}
|
||||
@@ -195,7 +194,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
)}
|
||||
</Content>
|
||||
)}
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -62,12 +62,14 @@ export const catalogPage = PageBlueprint.makeWithOverrides({
|
||||
icon: <CategoryIcon fontSize="inherit" />,
|
||||
title: 'Catalog',
|
||||
loader: async () => {
|
||||
const { BaseCatalogPage } = await import('../components/CatalogPage');
|
||||
const { NfsDefaultCatalogPage } = await import(
|
||||
'../components/CatalogPage/DefaultCatalogPage'
|
||||
);
|
||||
const filters = inputs.filters.map(filter =>
|
||||
filter.get(coreExtensionData.reactElement),
|
||||
);
|
||||
return (
|
||||
<BaseCatalogPage
|
||||
<NfsDefaultCatalogPage
|
||||
filters={<>{filters}</>}
|
||||
pagination={config.pagination}
|
||||
/>
|
||||
|
||||
@@ -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 {
|
||||
CatalogFilterLayout,
|
||||
DefaultFilters,
|
||||
@@ -48,9 +49,51 @@ export type BaseCatalogPageProps = {
|
||||
pagination?: EntityListPagination;
|
||||
};
|
||||
|
||||
function CatalogPageContent(props: BaseCatalogPageProps) {
|
||||
const { filters, content = <CatalogTable />, pagination } = props;
|
||||
|
||||
return (
|
||||
<EntityListProvider pagination={pagination}>
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>{filters}</CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>{content}</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
</EntityListProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function BaseCatalogPage(props: BaseCatalogPageProps) {
|
||||
const { filters, content = <CatalogTable />, pagination } = props;
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
const { t } = useTranslationRef(catalogTranslationRef);
|
||||
const { allowed } = usePermission({
|
||||
permission: catalogEntityCreatePermission,
|
||||
});
|
||||
const headerActions = (
|
||||
<>
|
||||
{allowed && (
|
||||
<CreateButton
|
||||
title={t('indexPage.createButtonTitle')}
|
||||
to={createComponentLink && createComponentLink()}
|
||||
/>
|
||||
)}
|
||||
<SupportButton>{t('indexPage.supportButtonContent')}</SupportButton>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageWithHeader title={t('indexPage.title', { orgName })} themeId="home">
|
||||
<Content>
|
||||
<ContentHeader title="">{headerActions}</ContentHeader>
|
||||
<CatalogPageContent {...props} />
|
||||
</Content>
|
||||
</PageWithHeader>
|
||||
);
|
||||
}
|
||||
|
||||
function NfsBaseCatalogPage(props: BaseCatalogPageProps) {
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
@@ -60,25 +103,25 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<PageWithHeader title={t('indexPage.title', { orgName })} themeId="home">
|
||||
<>
|
||||
<HeaderPage
|
||||
title={t('indexPage.title', { orgName })}
|
||||
customActions={
|
||||
<>
|
||||
{allowed && (
|
||||
<CreateButton
|
||||
title={t('indexPage.createButtonTitle')}
|
||||
to={createComponentLink && createComponentLink()}
|
||||
/>
|
||||
)}
|
||||
<SupportButton>{t('indexPage.supportButtonContent')}</SupportButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Content>
|
||||
<ContentHeader title="">
|
||||
{allowed && (
|
||||
<CreateButton
|
||||
title={t('indexPage.createButtonTitle')}
|
||||
to={createComponentLink && createComponentLink()}
|
||||
/>
|
||||
)}
|
||||
<SupportButton>{t('indexPage.supportButtonContent')}</SupportButton>
|
||||
</ContentHeader>
|
||||
<EntityListProvider pagination={pagination}>
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>{filters}</CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>{content}</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
</EntityListProvider>
|
||||
<CatalogPageContent {...props} />
|
||||
</Content>
|
||||
</PageWithHeader>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,3 +181,42 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function NfsDefaultCatalogPage(props: DefaultCatalogPageProps) {
|
||||
const {
|
||||
columns,
|
||||
actions,
|
||||
initiallySelectedFilter = 'owned',
|
||||
initialKind = 'component',
|
||||
tableOptions = {},
|
||||
emptyContent,
|
||||
pagination,
|
||||
ownerPickerMode,
|
||||
filters,
|
||||
initiallySelectedNamespaces,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<NfsBaseCatalogPage
|
||||
filters={
|
||||
filters ?? (
|
||||
<DefaultFilters
|
||||
initialKind={initialKind}
|
||||
initiallySelectedFilter={initiallySelectedFilter}
|
||||
ownerPickerMode={ownerPickerMode}
|
||||
initiallySelectedNamespaces={initiallySelectedNamespaces}
|
||||
/>
|
||||
)
|
||||
}
|
||||
content={
|
||||
<CatalogTable
|
||||
columns={columns}
|
||||
actions={actions}
|
||||
tableOptions={tableOptions}
|
||||
emptyContent={emptyContent}
|
||||
/>
|
||||
}
|
||||
pagination={pagination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"@backstage/plugin-devtools-common": "workspace:^",
|
||||
"@backstage/plugin-devtools-react": "workspace:^",
|
||||
"@backstage/plugin-permission-react": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
|
||||
@@ -68,8 +68,8 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({
|
||||
title: content.get(coreExtensionData.title),
|
||||
children: content.get(coreExtensionData.reactElement),
|
||||
}));
|
||||
return import('../components/DevToolsPage').then(m => (
|
||||
<m.DevToolsPage contents={contents} />
|
||||
return import('../components/DevToolsPage/DevToolsPage').then(m => (
|
||||
<m.NfsDevToolsPage contents={contents} />
|
||||
));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,7 +21,10 @@ import {
|
||||
|
||||
import { ConfigContent } from '../Content';
|
||||
import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
|
||||
import { DevToolsLayout } from '../DevToolsLayout';
|
||||
import {
|
||||
DevToolsLayout,
|
||||
NfsDevToolsLayout,
|
||||
} from '../DevToolsLayout/DevToolsLayout';
|
||||
import { InfoContent } from '../Content';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { ScheduledTasksContent } from '../Content/ScheduledTasksContent';
|
||||
@@ -56,3 +59,32 @@ export const DefaultDevToolsPage = ({ contents }: DevToolsPageProps) => (
|
||||
))}
|
||||
</DevToolsLayout>
|
||||
);
|
||||
|
||||
export const NfsDefaultDevToolsPage = ({ contents }: DevToolsPageProps) => (
|
||||
<NfsDevToolsLayout>
|
||||
<DevToolsLayout.Route path="info" title="Info">
|
||||
<RequirePermission permission={devToolsInfoReadPermission}>
|
||||
<InfoContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="config" title="Config">
|
||||
<RequirePermission permission={devToolsConfigReadPermission}>
|
||||
<ConfigContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="scheduled-tasks" title="Scheduled Tasks">
|
||||
<RequirePermission permission={devToolsTaskSchedulerReadPermission}>
|
||||
<ScheduledTasksContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
{contents?.map((content, index) => (
|
||||
<DevToolsLayout.Route
|
||||
key={`extension-${index}`}
|
||||
path={content.path}
|
||||
title={content.title}
|
||||
>
|
||||
{content.children}
|
||||
</DevToolsLayout.Route>
|
||||
))}
|
||||
</NfsDevToolsLayout>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Header, Page, RoutedTabs } from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import {
|
||||
attachComponentData,
|
||||
useElementFilter,
|
||||
@@ -82,4 +83,28 @@ export const DevToolsLayout = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsDevToolsLayout = ({
|
||||
children,
|
||||
title,
|
||||
subtitle,
|
||||
}: DevToolsLayoutProps) => {
|
||||
const routes = useElementFilter(children, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: dataKey,
|
||||
withStrictError:
|
||||
'Child of DevToolsLayout must be an DevToolsLayout.Route',
|
||||
})
|
||||
.getElements<SubRoute>()
|
||||
.map(child => child.props),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderPage title={title ?? 'Backstage DevTools'} subtitle={subtitle} />
|
||||
<RoutedTabs routes={routes} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DevToolsLayout.Route = Route;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { useOutlet } from 'react-router-dom';
|
||||
import { DefaultDevToolsPage } from '../DefaultDevToolsPage';
|
||||
import { NfsDefaultDevToolsPage } from '../DefaultDevToolsPage/DefaultDevToolsPage';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
/**
|
||||
@@ -39,3 +40,9 @@ export const DevToolsPage = ({ contents }: DevToolsPageProps) => {
|
||||
|
||||
return <>{outlet || <DefaultDevToolsPage contents={contents} />}</>;
|
||||
};
|
||||
|
||||
export const NfsDevToolsPage = ({ contents }: DevToolsPageProps) => {
|
||||
const outlet = useOutlet();
|
||||
|
||||
return <>{outlet || <NfsDefaultDevToolsPage contents={contents} />}</>;
|
||||
};
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"@backstage/plugin-notifications-common": "workspace:^",
|
||||
"@backstage/plugin-signals-react": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -30,7 +30,7 @@ const page = PageBlueprint.make({
|
||||
routeRef: rootRouteRef,
|
||||
loader: () =>
|
||||
import('./components/NotificationsPage').then(m => (
|
||||
<m.NotificationsPage />
|
||||
<m.NfsNotificationsPage />
|
||||
)),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
PageWithHeader,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { ConfirmProvider } from 'material-ui-confirm';
|
||||
import { useSignal } from '@backstage/plugin-signals-react';
|
||||
@@ -66,7 +67,9 @@ export type NotificationsPageProps = {
|
||||
typeLink?: string;
|
||||
};
|
||||
|
||||
export const NotificationsPage = (props?: NotificationsPageProps) => {
|
||||
function NotificationsPageContent(
|
||||
props: NotificationsPageProps & { headerVariant: 'legacy' | 'bui' },
|
||||
) {
|
||||
const { t } = useTranslationRef(notificationsTranslationRef);
|
||||
const {
|
||||
title = t('notificationsPage.title'),
|
||||
@@ -76,7 +79,8 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
|
||||
type,
|
||||
typeLink,
|
||||
markAsReadOnLinkOpen,
|
||||
} = props ?? {};
|
||||
headerVariant,
|
||||
} = props;
|
||||
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { lastSignal } = useSignal('notifications');
|
||||
@@ -186,6 +190,57 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
|
||||
});
|
||||
}
|
||||
|
||||
const pageContent = (
|
||||
<Content>
|
||||
<ConfirmProvider>
|
||||
<Grid container>
|
||||
<Grid item xs={2}>
|
||||
<NotificationsFilters
|
||||
unreadOnly={unreadOnly}
|
||||
onUnreadOnlyChanged={setUnreadOnly}
|
||||
createdAfter={createdAfter}
|
||||
onCreatedAfterChanged={setCreatedAfter}
|
||||
onSortingChanged={setSorting}
|
||||
sorting={sorting}
|
||||
saved={saved}
|
||||
onSavedChanged={setSaved}
|
||||
severity={severity}
|
||||
onSeverityChanged={setSeverity}
|
||||
topic={topic}
|
||||
onTopicChanged={setTopic}
|
||||
allTopics={allTopics}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<NotificationsTable
|
||||
title={tableTitle}
|
||||
isLoading={loading}
|
||||
isUnread={isUnread}
|
||||
markAsReadOnLinkOpen={markAsReadOnLinkOpen}
|
||||
notifications={notifications}
|
||||
onUpdate={onUpdate}
|
||||
setContainsText={setContainsText}
|
||||
onPageChange={setPageNumber}
|
||||
onRowsPerPageChange={setPageSize}
|
||||
page={pageNumber}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalCount}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ConfirmProvider>
|
||||
</Content>
|
||||
);
|
||||
|
||||
if (headerVariant === 'bui') {
|
||||
return (
|
||||
<>
|
||||
<HeaderPage title={title} subtitle={subtitle} />
|
||||
{pageContent}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWithHeader
|
||||
title={title}
|
||||
@@ -195,45 +250,15 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
|
||||
type={type}
|
||||
typeLink={typeLink}
|
||||
>
|
||||
<Content>
|
||||
<ConfirmProvider>
|
||||
<Grid container>
|
||||
<Grid item xs={2}>
|
||||
<NotificationsFilters
|
||||
unreadOnly={unreadOnly}
|
||||
onUnreadOnlyChanged={setUnreadOnly}
|
||||
createdAfter={createdAfter}
|
||||
onCreatedAfterChanged={setCreatedAfter}
|
||||
onSortingChanged={setSorting}
|
||||
sorting={sorting}
|
||||
saved={saved}
|
||||
onSavedChanged={setSaved}
|
||||
severity={severity}
|
||||
onSeverityChanged={setSeverity}
|
||||
topic={topic}
|
||||
onTopicChanged={setTopic}
|
||||
allTopics={allTopics}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<NotificationsTable
|
||||
title={tableTitle}
|
||||
isLoading={loading}
|
||||
isUnread={isUnread}
|
||||
markAsReadOnLinkOpen={markAsReadOnLinkOpen}
|
||||
notifications={notifications}
|
||||
onUpdate={onUpdate}
|
||||
setContainsText={setContainsText}
|
||||
onPageChange={setPageNumber}
|
||||
onRowsPerPageChange={setPageSize}
|
||||
page={pageNumber}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalCount}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ConfirmProvider>
|
||||
</Content>
|
||||
{pageContent}
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const NotificationsPage = (props?: NotificationsPageProps) => (
|
||||
<NotificationsPageContent {...(props ?? {})} headerVariant="legacy" />
|
||||
);
|
||||
|
||||
export const NfsNotificationsPage = (props?: NotificationsPageProps) => (
|
||||
<NotificationsPageContent {...(props ?? {})} headerVariant="bui" />
|
||||
);
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"@backstage/plugin-techdocs-common": "workspace:^",
|
||||
"@backstage/plugin-techdocs-react": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/legacy-modes": "^6.1.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
|
||||
@@ -727,6 +727,7 @@ export type TemplateListPageProps = {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
@@ -742,6 +743,7 @@ export type TemplateWizardPageProps = {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
@@ -499,7 +499,14 @@ export type RouterProps = {
|
||||
TemplateCardComponent?: ComponentType<{
|
||||
template: TemplateEntityV1beta3;
|
||||
}>;
|
||||
TaskPageComponent?: ComponentType<PropsWithChildren<{}>>;
|
||||
TaskPageComponent?: ComponentType<
|
||||
PropsWithChildren<{
|
||||
TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput_2;
|
||||
}>;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}>
|
||||
>;
|
||||
EXPERIMENTAL_TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput_2;
|
||||
}>;
|
||||
@@ -622,6 +629,7 @@ export const TaskPage: (props: {
|
||||
TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput_2;
|
||||
}>;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public @deprecated
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Page, Header, Content } from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
|
||||
@@ -23,9 +22,11 @@ import { editRouteRef } from '../../../routes';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
import { CustomFieldExplorer } from './CustomFieldExplorer';
|
||||
import { ScaffolderPageLayout } from '../../../components/ScaffolderPageLayout';
|
||||
|
||||
interface CustomFieldsPageProps {
|
||||
fieldExtensions?: FieldExtensionOptions<any, any>[];
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}
|
||||
|
||||
export function CustomFieldsPage(props: CustomFieldsPageProps) {
|
||||
@@ -33,16 +34,15 @@ export function CustomFieldsPage(props: CustomFieldsPageProps) {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={t('templateCustomFieldPage.title')}
|
||||
subtitle={t('templateCustomFieldPage.subtitle')}
|
||||
type={t('templateIntroPage.title')}
|
||||
typeLink={editLink()}
|
||||
/>
|
||||
<Content>
|
||||
<CustomFieldExplorer customFieldExtensions={props.fieldExtensions} />
|
||||
</Content>
|
||||
</Page>
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
title={t('templateCustomFieldPage.title')}
|
||||
subtitle={t('templateCustomFieldPage.subtitle')}
|
||||
type={t('templateIntroPage.title')}
|
||||
typeLink={editLink()}
|
||||
>
|
||||
<CustomFieldExplorer customFieldExtensions={props.fieldExtensions} />
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Content, Header, Page } from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import {
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
import { editRouteRef } from '../../../routes';
|
||||
import { TemplateEditor } from './TemplateEditor';
|
||||
import { ScaffolderPageLayout } from '../../../components/ScaffolderPageLayout';
|
||||
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
@@ -40,6 +40,7 @@ interface TemplatePageProps {
|
||||
fieldExtensions?: FieldExtensionOptions<any, any>[];
|
||||
layouts?: LayoutOptions[];
|
||||
formProps?: FormProps;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}
|
||||
|
||||
export function TemplateEditorPage(props: TemplatePageProps) {
|
||||
@@ -48,20 +49,20 @@ export function TemplateEditorPage(props: TemplatePageProps) {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={t('templateEditorPage.title')}
|
||||
subtitle={t('templateEditorPage.subtitle')}
|
||||
type={t('templateIntroPage.title')}
|
||||
typeLink={editLink()}
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
title={t('templateEditorPage.title')}
|
||||
subtitle={t('templateEditorPage.subtitle')}
|
||||
type={t('templateIntroPage.title')}
|
||||
typeLink={editLink()}
|
||||
contentClassName={classes.content}
|
||||
>
|
||||
<TemplateEditor
|
||||
layouts={props.layouts}
|
||||
formProps={props.formProps}
|
||||
fieldExtensions={props.fieldExtensions}
|
||||
/>
|
||||
<Content className={classes.content}>
|
||||
<TemplateEditor
|
||||
layouts={props.layouts}
|
||||
formProps={props.formProps}
|
||||
fieldExtensions={props.fieldExtensions}
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
import { Page, Header, Content } from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import {
|
||||
@@ -32,6 +31,7 @@ import { editRouteRef } from '../../../routes';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
import { TemplateFormPreviewer } from './TemplateFormPreviewer';
|
||||
import { ScaffolderPageLayout } from '../../../components/ScaffolderPageLayout';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
@@ -44,6 +44,7 @@ interface TemplateFormPageProps {
|
||||
formProps?: FormProps;
|
||||
fieldExtensions?: FieldExtensionOptions<any, any>[];
|
||||
defaultPreviewTemplate?: string;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}
|
||||
|
||||
export function TemplateFormPage(props: TemplateFormPageProps) {
|
||||
@@ -57,22 +58,22 @@ export function TemplateFormPage(props: TemplateFormPageProps) {
|
||||
}, [navigate, editLink]);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={t('templateFormPage.title')}
|
||||
subtitle={t('templateFormPage.subtitle')}
|
||||
type={t('templateIntroPage.title')}
|
||||
typeLink={editLink()}
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
title={t('templateFormPage.title')}
|
||||
subtitle={t('templateFormPage.subtitle')}
|
||||
type={t('templateIntroPage.title')}
|
||||
typeLink={editLink()}
|
||||
contentClassName={classes.root}
|
||||
>
|
||||
<TemplateFormPreviewer
|
||||
layouts={props.layouts}
|
||||
formProps={props.formProps}
|
||||
customFieldExtensions={props.fieldExtensions}
|
||||
defaultPreviewTemplate={props.defaultPreviewTemplate}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
<Content className={classes.root}>
|
||||
<TemplateFormPreviewer
|
||||
layouts={props.layouts}
|
||||
formProps={props.formProps}
|
||||
customFieldExtensions={props.fieldExtensions}
|
||||
defaultPreviewTemplate={props.defaultPreviewTemplate}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useCallback } from 'react';
|
||||
import { Content, Header, Page } from '@backstage/core-components';
|
||||
|
||||
import { TemplateEditorIntro } from './TemplateEditorIntro';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -28,8 +27,9 @@ import {
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
import { useTemplateDirectory } from './useTemplateDirectory';
|
||||
import { ScaffolderPageLayout } from '../../../components/ScaffolderPageLayout';
|
||||
|
||||
export function TemplateIntroPage() {
|
||||
export function TemplateIntroPage(props: { headerVariant?: 'legacy' | 'bui' }) {
|
||||
const navigate = useNavigate();
|
||||
const createLink = useRouteRef(rootRouteRef);
|
||||
const editorLink = useRouteRef(editorRouteRef);
|
||||
@@ -65,16 +65,15 @@ export function TemplateIntroPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={t('templateIntroPage.title')}
|
||||
type="Scaffolder"
|
||||
typeLink={createLink()}
|
||||
subtitle={t('templateIntroPage.subtitle')}
|
||||
/>
|
||||
<Content>
|
||||
<TemplateEditorIntro onSelect={handleSelect} />
|
||||
</Content>
|
||||
</Page>
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
title={t('templateIntroPage.title')}
|
||||
type="Scaffolder"
|
||||
typeLink={createLink()}
|
||||
subtitle={t('templateIntroPage.subtitle')}
|
||||
>
|
||||
<TemplateEditorIntro onSelect={handleSelect} />
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { useApp, useRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
DocsIcon,
|
||||
Header,
|
||||
Page,
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import {
|
||||
@@ -59,6 +56,7 @@ import {
|
||||
useTranslationRef,
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
import { ScaffolderPageLayout } from '../../../components/ScaffolderPageLayout';
|
||||
import { buildTechDocsURL } from '@backstage/plugin-techdocs-react';
|
||||
import {
|
||||
TECHDOCS_ANNOTATION,
|
||||
@@ -85,6 +83,7 @@ export type TemplateListPageProps = {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
};
|
||||
|
||||
const createGroupsWithOther = (
|
||||
@@ -183,55 +182,62 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
|
||||
},
|
||||
[navigate, templateRoute],
|
||||
);
|
||||
const pageActions = (
|
||||
<>
|
||||
<RegisterExistingButton
|
||||
title={t('templateListPage.contentHeader.registerExistingButtonTitle')}
|
||||
to={registerComponentLink && registerComponentLink()}
|
||||
/>
|
||||
<SupportButton>
|
||||
{t('templateListPage.contentHeader.supportButtonTitle')}
|
||||
</SupportButton>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<EntityListProvider>
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride={t('templateListPage.pageTitle')}
|
||||
title={t('templateListPage.title')}
|
||||
subtitle={t('templateListPage.subtitle')}
|
||||
{...headerOptions}
|
||||
>
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentHeader>
|
||||
<RegisterExistingButton
|
||||
title={t(
|
||||
'templateListPage.contentHeader.registerExistingButtonTitle',
|
||||
)}
|
||||
to={registerComponentLink && registerComponentLink()}
|
||||
/>
|
||||
<SupportButton>
|
||||
{t('templateListPage.contentHeader.supportButtonTitle')}
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
pageTitleOverride={
|
||||
headerOptions?.pageTitleOverride ?? t('templateListPage.pageTitle')
|
||||
}
|
||||
title={headerOptions?.title ?? t('templateListPage.title')}
|
||||
subtitle={headerOptions?.subtitle ?? t('templateListPage.subtitle')}
|
||||
headerActions={
|
||||
<>
|
||||
{props.headerVariant === 'bui' ? pageActions : undefined}
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</>
|
||||
}
|
||||
>
|
||||
{props.headerVariant !== 'bui' ? (
|
||||
<ContentHeader>{pageActions}</ContentHeader>
|
||||
) : null}
|
||||
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>
|
||||
<EntitySearchBar />
|
||||
<EntityKindPicker initialFilter="template" hidden />
|
||||
<UserListPicker
|
||||
initialFilter="all"
|
||||
availableFilters={['all', 'starred']}
|
||||
/>
|
||||
<TemplateCategoryPicker />
|
||||
<EntityTagPicker />
|
||||
<EntityOwnerPicker />
|
||||
</CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>
|
||||
<TemplateGroups
|
||||
groups={groups}
|
||||
templateFilter={templateFilter}
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
onTemplateSelected={onTemplateSelected}
|
||||
additionalLinksForEntity={additionalLinksForEntity}
|
||||
/>
|
||||
</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
</Content>
|
||||
</Page>
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>
|
||||
<EntitySearchBar />
|
||||
<EntityKindPicker initialFilter="template" hidden />
|
||||
<UserListPicker
|
||||
initialFilter="all"
|
||||
availableFilters={['all', 'starred']}
|
||||
/>
|
||||
<TemplateCategoryPicker />
|
||||
<EntityTagPicker />
|
||||
<EntityOwnerPicker />
|
||||
</CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>
|
||||
<TemplateGroups
|
||||
groups={groups}
|
||||
templateFilter={templateFilter}
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
onTemplateSelected={onTemplateSelected}
|
||||
additionalLinksForEntity={additionalLinksForEntity}
|
||||
/>
|
||||
</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
</ScaffolderPageLayout>
|
||||
</EntityListProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
useTemplateParameterSchema,
|
||||
} from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Header, Page, Progress } from '@backstage/core-components';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
|
||||
import {
|
||||
rootRouteRef,
|
||||
@@ -53,6 +53,7 @@ import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
import { TemplateWizardPageContextMenu } from './TemplateWizardPageContextMenu';
|
||||
import { useFormDecorators } from '../../hooks';
|
||||
import { ScaffolderPageLayout } from '../../../components/ScaffolderPageLayout';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
@@ -69,6 +70,7 @@ export type TemplateWizardPageProps = {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
};
|
||||
|
||||
export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
@@ -136,21 +138,24 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
|
||||
return (
|
||||
<AnalyticsContext attributes={{ entityRef: templateRef }}>
|
||||
<Page themeId="website">
|
||||
<Header
|
||||
pageTitleOverride={
|
||||
manifest?.title
|
||||
? t('templateWizardPage.templateWithTitle', {
|
||||
templateTitle: manifest.title,
|
||||
})
|
||||
: t('templateWizardPage.pageTitle')
|
||||
}
|
||||
title={t('templateWizardPage.title')}
|
||||
subtitle={t('templateWizardPage.subtitle')}
|
||||
{...props.headerOptions}
|
||||
>
|
||||
<TemplateWizardPageContextMenu editUrl={editUrl} />
|
||||
</Header>
|
||||
<ScaffolderPageLayout
|
||||
themeId="website"
|
||||
withContent={false}
|
||||
headerVariant={props.headerVariant}
|
||||
pageTitleOverride={
|
||||
props.headerOptions?.pageTitleOverride ??
|
||||
(manifest?.title
|
||||
? t('templateWizardPage.templateWithTitle', {
|
||||
templateTitle: manifest.title,
|
||||
})
|
||||
: t('templateWizardPage.pageTitle'))
|
||||
}
|
||||
title={props.headerOptions?.title ?? t('templateWizardPage.title')}
|
||||
subtitle={
|
||||
props.headerOptions?.subtitle ?? t('templateWizardPage.subtitle')
|
||||
}
|
||||
headerActions={<TemplateWizardPageContextMenu editUrl={editUrl} />}
|
||||
>
|
||||
{isCreating && <Progress />}
|
||||
<Workflow
|
||||
namespace={namespace}
|
||||
@@ -162,7 +167,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
formProps={props.formProps}
|
||||
layouts={props.layouts}
|
||||
/>
|
||||
</Page>
|
||||
</ScaffolderPageLayout>
|
||||
</AnalyticsContext>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({
|
||||
const formFields = [...apiFormFields, ...loadedFormFields];
|
||||
|
||||
return import('../components/Router/Router').then(m => (
|
||||
<m.InternalRouter formFields={formFields} />
|
||||
<m.InternalRouter formFields={formFields} headerVariant="bui" />
|
||||
));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,13 +31,10 @@ import SearchIcon from '@material-ui/icons/Search';
|
||||
|
||||
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
ErrorPanel,
|
||||
Header,
|
||||
Link,
|
||||
MarkdownContent,
|
||||
Page,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
@@ -52,6 +49,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
import { Expanded, RenderSchema, SchemaRenderContext } from '../RenderSchema';
|
||||
import { ScaffolderUsageExamplesTable } from '../ScaffolderUsageExamplesTable';
|
||||
import { ScaffolderPageLayout } from '../ScaffolderPageLayout';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
code: {
|
||||
@@ -242,6 +240,7 @@ export type ActionsPageProps = {
|
||||
create?: boolean;
|
||||
templatingExtensions?: boolean;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
};
|
||||
|
||||
export const ActionsPage = (props: ActionsPageProps) => {
|
||||
@@ -273,17 +272,17 @@ export const ActionsPage = (props: ActionsPageProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride={t('actionsPage.pageTitle')}
|
||||
title={t('actionsPage.title')}
|
||||
subtitle={t('actionsPage.subtitle')}
|
||||
>
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
pageTitleOverride={t('actionsPage.pageTitle')}
|
||||
title={t('actionsPage.title')}
|
||||
subtitle={t('actionsPage.subtitle')}
|
||||
headerActions={
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
<Content>
|
||||
<ActionPageContent />
|
||||
</Content>
|
||||
</Page>
|
||||
}
|
||||
>
|
||||
<ActionPageContent />
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,12 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
ErrorPanel,
|
||||
Header,
|
||||
Link,
|
||||
Page,
|
||||
Progress,
|
||||
Table,
|
||||
} from '@backstage/core-components';
|
||||
@@ -48,6 +45,7 @@ import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/al
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
import { ScaffolderPageLayout } from '../ScaffolderPageLayout';
|
||||
|
||||
export interface MyTaskPageProps {
|
||||
initiallySelectedFilter?: 'owned' | 'all';
|
||||
@@ -57,6 +55,7 @@ export interface MyTaskPageProps {
|
||||
create?: boolean;
|
||||
templatingExtensions?: boolean;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}
|
||||
|
||||
const ListTaskPageContent = (props: MyTaskPageProps) => {
|
||||
@@ -200,17 +199,17 @@ export const ListTasksPage = (props: MyTaskPageProps) => {
|
||||
: undefined,
|
||||
};
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride={t('listTaskPage.pageTitle')}
|
||||
title={t('listTaskPage.title')}
|
||||
subtitle={t('listTaskPage.subtitle')}
|
||||
>
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
pageTitleOverride={t('listTaskPage.pageTitle')}
|
||||
title={t('listTaskPage.title')}
|
||||
subtitle={t('listTaskPage.subtitle')}
|
||||
headerActions={
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
<Content>
|
||||
<ListTaskPageContent {...props} />
|
||||
</Content>
|
||||
</Page>
|
||||
}
|
||||
>
|
||||
<ListTaskPageContent {...props} />
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Content, ErrorPanel, Header, Page } from '@backstage/core-components';
|
||||
import { ErrorPanel } from '@backstage/core-components';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Button from '@material-ui/core/Button';
|
||||
@@ -57,6 +57,7 @@ import { scaffolderTranslationRef } from '../../translation';
|
||||
import { entityPresentationApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { default as reactUseAsync } from 'react-use/esm/useAsync';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { ScaffolderPageLayout } from '../ScaffolderPageLayout';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
@@ -86,6 +87,7 @@ export const OngoingTask = (props: {
|
||||
TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput;
|
||||
}>;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}) => {
|
||||
// todo(blam): check that task Id actually exists, and that it's valid. otherwise redirect to something more useful.
|
||||
const { taskId } = useParams();
|
||||
@@ -106,9 +108,7 @@ export const OngoingTask = (props: {
|
||||
taskId,
|
||||
}}
|
||||
>
|
||||
<Page themeId="website">
|
||||
<OngoingTaskContent {...props} />
|
||||
</Page>
|
||||
<OngoingTaskContent {...props} />
|
||||
</AnalyticsContext>
|
||||
);
|
||||
};
|
||||
@@ -117,6 +117,7 @@ function OngoingTaskContent(props: {
|
||||
TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput;
|
||||
}>;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}) {
|
||||
const { taskId } = useParams();
|
||||
const templateRouteRef = useRouteRef(selectedTemplateRouteRef);
|
||||
@@ -243,23 +244,24 @@ function OngoingTaskContent(props: {
|
||||
!cancelEnabled || cancelStatus !== 'not-executed' || !canCancelTask;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
pageTitleOverride={
|
||||
presentation
|
||||
? t('ongoingTask.pageTitle.hasTemplateName', {
|
||||
templateName: presentation.primaryTitle,
|
||||
})
|
||||
: t('ongoingTask.pageTitle.noTemplateName')
|
||||
}
|
||||
title={
|
||||
<div>
|
||||
{t('ongoingTask.title')}{' '}
|
||||
<code>{presentation ? presentation.primaryTitle : ''}</code>
|
||||
</div>
|
||||
}
|
||||
subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })}
|
||||
>
|
||||
<ScaffolderPageLayout
|
||||
themeId="website"
|
||||
headerVariant={props.headerVariant}
|
||||
pageTitleOverride={
|
||||
presentation
|
||||
? t('ongoingTask.pageTitle.hasTemplateName', {
|
||||
templateName: presentation.primaryTitle,
|
||||
})
|
||||
: t('ongoingTask.pageTitle.noTemplateName')
|
||||
}
|
||||
title={
|
||||
<div>
|
||||
{t('ongoingTask.title')}{' '}
|
||||
<code>{presentation ? presentation.primaryTitle : ''}</code>
|
||||
</div>
|
||||
}
|
||||
subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })}
|
||||
headerActions={
|
||||
<ContextMenu
|
||||
cancelEnabled={cancelEnabled}
|
||||
canRetry={canRetry}
|
||||
@@ -274,89 +276,89 @@ function OngoingTaskContent(props: {
|
||||
onCancel={triggerCancel}
|
||||
isCancelButtonDisabled={isCancelButtonDisabled}
|
||||
/>
|
||||
</Header>
|
||||
<Content className={classes.contentWrapper}>
|
||||
{taskStream.error ? (
|
||||
<Box paddingBottom={2}>
|
||||
<ErrorPanel
|
||||
error={taskStream.error}
|
||||
titleFormat="markdown"
|
||||
title={taskStream.error.message}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
}
|
||||
contentClassName={classes.contentWrapper}
|
||||
>
|
||||
{taskStream.error ? (
|
||||
<Box paddingBottom={2}>
|
||||
<TaskSteps
|
||||
steps={steps}
|
||||
activeStep={activeStep}
|
||||
isComplete={taskStream.completed}
|
||||
isError={Boolean(taskStream.error)}
|
||||
<ErrorPanel
|
||||
error={taskStream.error}
|
||||
titleFormat="markdown"
|
||||
title={taskStream.error.message}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
<Outputs output={taskStream.output} />
|
||||
<Box paddingBottom={2}>
|
||||
<TaskSteps
|
||||
steps={steps}
|
||||
activeStep={activeStep}
|
||||
isComplete={taskStream.completed}
|
||||
isError={Boolean(taskStream.error)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{buttonBarVisible ? (
|
||||
<Box paddingBottom={2}>
|
||||
<Paper>
|
||||
<Box padding={2}>
|
||||
<div className={classes.buttonBar}>
|
||||
<Button
|
||||
className={classes.cancelButton}
|
||||
disabled={
|
||||
!cancelEnabled ||
|
||||
(cancelStatus !== 'not-executed' && !isRetryableTask) ||
|
||||
!canCancelTask
|
||||
}
|
||||
onClick={triggerCancel}
|
||||
data-testid="cancel-button"
|
||||
>
|
||||
{t('ongoingTask.cancelButtonTitle')}
|
||||
</Button>
|
||||
{isRetryableTask && (
|
||||
<Button
|
||||
className={classes.retryButton}
|
||||
disabled={cancelEnabled || !canRetry}
|
||||
onClick={triggerRetry}
|
||||
data-testid="retry-button"
|
||||
>
|
||||
{t('ongoingTask.retryButtonTitle')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className={classes.logsVisibilityButton}
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => setLogVisibleState(!logsVisible)}
|
||||
>
|
||||
{logsVisible
|
||||
? t('ongoingTask.hideLogsButtonTitle')
|
||||
: t('ongoingTask.showLogsButtonTitle')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={cancelEnabled || !canStartOver}
|
||||
onClick={startOver}
|
||||
data-testid="start-over-button"
|
||||
>
|
||||
{t('ongoingTask.startOverButtonTitle')}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
) : null}
|
||||
<Outputs output={taskStream.output} />
|
||||
|
||||
{logsVisible ? (
|
||||
<Paper style={{ height: '100%' }}>
|
||||
<Box padding={2} height="100%">
|
||||
<TaskLogStream logs={taskStream.stepLogs} />
|
||||
{buttonBarVisible ? (
|
||||
<Box paddingBottom={2}>
|
||||
<Paper>
|
||||
<Box padding={2}>
|
||||
<div className={classes.buttonBar}>
|
||||
<Button
|
||||
className={classes.cancelButton}
|
||||
disabled={
|
||||
!cancelEnabled ||
|
||||
(cancelStatus !== 'not-executed' && !isRetryableTask) ||
|
||||
!canCancelTask
|
||||
}
|
||||
onClick={triggerCancel}
|
||||
data-testid="cancel-button"
|
||||
>
|
||||
{t('ongoingTask.cancelButtonTitle')}
|
||||
</Button>
|
||||
{isRetryableTask && (
|
||||
<Button
|
||||
className={classes.retryButton}
|
||||
disabled={cancelEnabled || !canRetry}
|
||||
onClick={triggerRetry}
|
||||
data-testid="retry-button"
|
||||
>
|
||||
{t('ongoingTask.retryButtonTitle')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className={classes.logsVisibilityButton}
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => setLogVisibleState(!logsVisible)}
|
||||
>
|
||||
{logsVisible
|
||||
? t('ongoingTask.hideLogsButtonTitle')
|
||||
: t('ongoingTask.showLogsButtonTitle')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={cancelEnabled || !canStartOver}
|
||||
onClick={startOver}
|
||||
data-testid="start-over-button"
|
||||
>
|
||||
{t('ongoingTask.startOverButtonTitle')}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Content>
|
||||
</>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
{logsVisible ? (
|
||||
<Paper style={{ height: '100%' }}>
|
||||
<Box padding={2} height="100%">
|
||||
<TaskLogStream logs={taskStream.stepLogs} />
|
||||
</Box>
|
||||
</Paper>
|
||||
) : null}
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,14 @@ export type RouterProps = {
|
||||
TemplateCardComponent?: ComponentType<{
|
||||
template: TemplateEntityV1beta3;
|
||||
}>;
|
||||
TaskPageComponent?: ComponentType<PropsWithChildren<{}>>;
|
||||
TaskPageComponent?: ComponentType<
|
||||
PropsWithChildren<{
|
||||
TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput;
|
||||
}>;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}>
|
||||
>;
|
||||
EXPERIMENTAL_TemplateOutputsComponent?: ComponentType<{
|
||||
output?: ScaffolderTaskOutput;
|
||||
}>;
|
||||
@@ -117,6 +124,7 @@ export const InternalRouter = (
|
||||
props: PropsWithChildren<
|
||||
RouterProps & {
|
||||
formFields?: Array<FormField>;
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
}
|
||||
>,
|
||||
) => {
|
||||
@@ -162,6 +170,7 @@ export const InternalRouter = (
|
||||
groups={props.groups}
|
||||
templateFilter={props.templateFilter}
|
||||
headerOptions={props.headerOptions}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -175,6 +184,7 @@ export const InternalRouter = (
|
||||
layouts={customLayouts}
|
||||
components={{ ReviewStepComponent }}
|
||||
formProps={props.formProps}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
}
|
||||
@@ -184,6 +194,7 @@ export const InternalRouter = (
|
||||
element={
|
||||
<TaskPageComponent
|
||||
TemplateOutputsComponent={TemplateOutputsComponent}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -192,7 +203,7 @@ export const InternalRouter = (
|
||||
element={
|
||||
<RequirePermission permission={templateManagementPermission}>
|
||||
<SecretsContextProvider>
|
||||
<TemplateIntroPage />
|
||||
<TemplateIntroPage headerVariant={props.headerVariant} />
|
||||
</SecretsContextProvider>
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -202,7 +213,10 @@ export const InternalRouter = (
|
||||
element={
|
||||
<RequirePermission permission={templateManagementPermission}>
|
||||
<SecretsContextProvider>
|
||||
<CustomFieldsPage fieldExtensions={fieldExtensions} />
|
||||
<CustomFieldsPage
|
||||
fieldExtensions={fieldExtensions}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -216,6 +230,7 @@ export const InternalRouter = (
|
||||
layouts={customLayouts}
|
||||
formProps={props.formProps}
|
||||
fieldExtensions={fieldExtensions}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
</RequirePermission>
|
||||
@@ -224,11 +239,21 @@ export const InternalRouter = (
|
||||
|
||||
<Route
|
||||
path={actionsRouteRef.path}
|
||||
element={<ActionsPage contextMenu={props.contextMenu} />}
|
||||
element={
|
||||
<ActionsPage
|
||||
contextMenu={props.contextMenu}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={scaffolderListTaskRouteRef.path}
|
||||
element={<ListTasksPage contextMenu={props.contextMenu} />}
|
||||
element={
|
||||
<ListTasksPage
|
||||
contextMenu={props.contextMenu}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={editorRouteRef.path}
|
||||
@@ -239,6 +264,7 @@ export const InternalRouter = (
|
||||
layouts={customLayouts}
|
||||
formProps={props.formProps}
|
||||
fieldExtensions={fieldExtensions}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
</SecretsContextProvider>
|
||||
</RequirePermission>
|
||||
@@ -246,7 +272,12 @@ export const InternalRouter = (
|
||||
/>
|
||||
<Route
|
||||
path={templatingExtensionsRouteRef.path}
|
||||
element={<TemplatingExtensionsPage contextMenu={props.contextMenu} />}
|
||||
element={
|
||||
<TemplatingExtensionsPage
|
||||
contextMenu={props.contextMenu}
|
||||
headerVariant={props.headerVariant}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<NotFoundErrorPage />} />
|
||||
</Routes>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Content, Header, Page } from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type HeaderVariant = 'legacy' | 'bui';
|
||||
|
||||
type ScaffolderPageLayoutProps = {
|
||||
headerVariant?: HeaderVariant;
|
||||
themeId?: string;
|
||||
title?: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
pageTitleOverride?: string;
|
||||
type?: string;
|
||||
typeLink?: string;
|
||||
headerActions?: ReactNode;
|
||||
contentClassName?: string;
|
||||
withContent?: boolean;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const ScaffolderPageLayout = (props: ScaffolderPageLayoutProps) => {
|
||||
const {
|
||||
headerVariant = 'legacy',
|
||||
themeId = 'home',
|
||||
title,
|
||||
subtitle,
|
||||
pageTitleOverride,
|
||||
type,
|
||||
typeLink,
|
||||
headerActions,
|
||||
contentClassName,
|
||||
withContent = true,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
const pageContent = withContent ? (
|
||||
<Content className={contentClassName}>{children}</Content>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
if (headerVariant === 'bui') {
|
||||
return (
|
||||
<>
|
||||
<HeaderPage
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
customActions={headerActions}
|
||||
/>
|
||||
{pageContent}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page themeId={themeId}>
|
||||
<Header
|
||||
pageTitleOverride={pageTitleOverride}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
type={type}
|
||||
typeLink={typeLink}
|
||||
>
|
||||
{headerActions}
|
||||
</Header>
|
||||
{pageContent}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
+13
-14
@@ -29,12 +29,9 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
import {
|
||||
Content,
|
||||
EmptyState,
|
||||
ErrorPanel,
|
||||
Header,
|
||||
Link,
|
||||
Page,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
|
||||
@@ -68,6 +65,7 @@ import {
|
||||
TemplateGlobalFunctions,
|
||||
TemplateGlobalValues,
|
||||
} from './TemplateGlobals';
|
||||
import { ScaffolderPageLayout } from '../ScaffolderPageLayout';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
code: {
|
||||
@@ -303,6 +301,7 @@ export type TemplatingExtensionsPageProps = {
|
||||
tasks?: boolean;
|
||||
create?: boolean;
|
||||
};
|
||||
headerVariant?: 'legacy' | 'bui';
|
||||
};
|
||||
|
||||
export const TemplatingExtensionsPage = (
|
||||
@@ -337,17 +336,17 @@ export const TemplatingExtensionsPage = (
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride={t('templatingExtensions.pageTitle')}
|
||||
title={t('templatingExtensions.title')}
|
||||
subtitle={t('templatingExtensions.subtitle')}
|
||||
>
|
||||
<ScaffolderPageLayout
|
||||
themeId="home"
|
||||
headerVariant={props.headerVariant}
|
||||
pageTitleOverride={t('templatingExtensions.pageTitle')}
|
||||
title={t('templatingExtensions.title')}
|
||||
subtitle={t('templatingExtensions.subtitle')}
|
||||
headerActions={
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
<Content>
|
||||
<TemplatingExtensionsPageContent linkLocal />
|
||||
</Content>
|
||||
</Page>
|
||||
}
|
||||
>
|
||||
<TemplatingExtensionsPageContent linkLocal />
|
||||
</ScaffolderPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"@backstage/plugin-search-common": "workspace:^",
|
||||
"@backstage/plugin-search-react": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@backstage/version-bridge": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -23,10 +23,9 @@ import {
|
||||
CatalogIcon,
|
||||
Content,
|
||||
DocsIcon,
|
||||
Header,
|
||||
Page,
|
||||
useSidebarPinState,
|
||||
} from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import {
|
||||
useApi,
|
||||
discoveryApiRef,
|
||||
@@ -143,8 +142,8 @@ export const searchPage = PageBlueprint.makeWithOverrides({
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
{!isMobile && <Header title="Search" />}
|
||||
<>
|
||||
{!isMobile && <HeaderPage title="Search" />}
|
||||
<Content>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
@@ -249,7 +248,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"@backstage/plugin-techdocs-common": "workspace:^",
|
||||
"@backstage/plugin-techdocs-react": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FC, ReactNode } from 'react';
|
||||
import { useOutlet } from 'react-router-dom';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import {
|
||||
TechDocsIndexPageProps,
|
||||
DefaultTechDocsHome,
|
||||
} from '../home/components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const NfsTechDocsPageWrapper: FC<{ children?: ReactNode }> = ({ children }) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const generatedSubtitle = `Documentation available in ${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderPage title="Documentation" subtitle={generatedSubtitle} />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsTechDocsIndexPage = (props: TechDocsIndexPageProps) => {
|
||||
const outlet = useOutlet();
|
||||
|
||||
return (
|
||||
outlet || (
|
||||
<DefaultTechDocsHome {...props} PageWrapper={NfsTechDocsPageWrapper} />
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PropsWithChildren, useEffect } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Skeleton from '@material-ui/lab/Skeleton';
|
||||
import CodeIcon from '@material-ui/icons/Code';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { HeaderLabel } from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import {
|
||||
useTechDocsAddons,
|
||||
TechDocsAddonLocations as locations,
|
||||
useTechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs-react';
|
||||
import {
|
||||
entityPresentationApiRef,
|
||||
EntityRefLink,
|
||||
EntityRefLinks,
|
||||
getEntityRelations,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
RELATION_OWNED_BY,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { TechDocsReaderPageContent } from '../reader/components/TechDocsReaderPageContent';
|
||||
import { TechDocsReaderPageSubheader } from '../reader/components/TechDocsReaderPageSubheader';
|
||||
|
||||
const skeleton = <Skeleton animation="wave" variant="text" height={40} />;
|
||||
|
||||
const NfsTechDocsReaderPageHeader = (props: PropsWithChildren<{}>) => {
|
||||
const { children } = props;
|
||||
const addons = useTechDocsAddons();
|
||||
const configApi = useApi(configApiRef);
|
||||
const entityPresentationApi = useApi(entityPresentationApiRef);
|
||||
const { '*': path = '' } = useParams();
|
||||
|
||||
const {
|
||||
title,
|
||||
setTitle,
|
||||
subtitle,
|
||||
setSubtitle,
|
||||
entityRef,
|
||||
metadata: { value: metadata, loading: metadataLoading },
|
||||
entityMetadata: { value: entityMetadata, loading: entityMetadataLoading },
|
||||
} = useTechDocsReaderPage();
|
||||
|
||||
useEffect(() => {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTitle(metadata.site_name);
|
||||
setSubtitle(() => {
|
||||
let { site_description } = metadata;
|
||||
if (!site_description || site_description === 'None') {
|
||||
site_description = '';
|
||||
}
|
||||
return site_description;
|
||||
});
|
||||
}, [metadata, setTitle, setSubtitle]);
|
||||
|
||||
const appTitle = configApi.getOptional('app.title') || 'Backstage';
|
||||
const { locationMetadata, spec } = entityMetadata || {};
|
||||
const lifecycle = spec?.lifecycle;
|
||||
const ownedByRelations = entityMetadata
|
||||
? getEntityRelations(entityMetadata, RELATION_OWNED_BY)
|
||||
: [];
|
||||
const labels = (
|
||||
<>
|
||||
<HeaderLabel
|
||||
label={capitalize(entityMetadata?.kind || 'entity')}
|
||||
value={
|
||||
<EntityRefLink
|
||||
color="inherit"
|
||||
entityRef={entityRef}
|
||||
title={entityMetadata?.metadata.title}
|
||||
defaultKind="Component"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{ownedByRelations.length > 0 && (
|
||||
<HeaderLabel
|
||||
label="Owner"
|
||||
value={
|
||||
<EntityRefLinks
|
||||
color="inherit"
|
||||
entityRefs={ownedByRelations}
|
||||
defaultKind="group"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{lifecycle ? (
|
||||
<HeaderLabel label="Lifecycle" value={String(lifecycle)} />
|
||||
) : null}
|
||||
{locationMetadata &&
|
||||
locationMetadata.type !== 'dir' &&
|
||||
locationMetadata.type !== 'file' ? (
|
||||
<HeaderLabel
|
||||
label=""
|
||||
value={
|
||||
<Grid container direction="column" alignItems="center">
|
||||
<Grid style={{ padding: 0 }} item>
|
||||
<CodeIcon style={{ marginTop: '-25px' }} />
|
||||
</Grid>
|
||||
<Grid style={{ padding: 0 }} item>
|
||||
Source
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
url={locationMetadata.target}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const noEntMetadata = !entityMetadataLoading && entityMetadata === undefined;
|
||||
const noTdMetadata = !metadataLoading && metadata === undefined;
|
||||
if (noEntMetadata || noTdMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stringEntityRef = stringifyEntityRef(entityRef);
|
||||
const entityDisplayName =
|
||||
entityPresentationApi.forEntity(stringEntityRef).snapshot.primaryTitle;
|
||||
const removeTrailingSlash = (str: string) => str.replace(/\/$/, '');
|
||||
const normalizeAndSpace = (str: string) =>
|
||||
str.replace(/[-_]/g, ' ').split(' ').map(capitalize).join(' ');
|
||||
|
||||
let techdocsTabTitleItems: string[] = [];
|
||||
if (path !== '') {
|
||||
techdocsTabTitleItems = removeTrailingSlash(path)
|
||||
.split('/')
|
||||
.map(normalizeAndSpace);
|
||||
}
|
||||
|
||||
const tabTitleItems = [entityDisplayName, ...techdocsTabTitleItems, appTitle];
|
||||
const tabTitle = tabTitleItems.join(' | ');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet titleTemplate="%s">
|
||||
<title>{tabTitle}</title>
|
||||
</Helmet>
|
||||
<HeaderPage
|
||||
title={
|
||||
<>
|
||||
<div>{title || skeleton}</div>
|
||||
<div>{labels}</div>
|
||||
</>
|
||||
}
|
||||
subtitle={subtitle === '' ? undefined : subtitle || skeleton}
|
||||
customActions={
|
||||
<>
|
||||
{children}
|
||||
{addons.renderComponentsByLocation(locations.Header)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export type NfsTechDocsReaderLayoutProps = {
|
||||
withHeader?: boolean;
|
||||
withSearch?: boolean;
|
||||
};
|
||||
|
||||
export const NfsTechDocsReaderLayout = (
|
||||
props: NfsTechDocsReaderLayoutProps,
|
||||
) => {
|
||||
const { withSearch, withHeader = true } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{withHeader && <NfsTechDocsReaderPageHeader />}
|
||||
<TechDocsReaderPageSubheader />
|
||||
<TechDocsReaderPageContent withSearch={withSearch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -46,7 +46,6 @@ import {
|
||||
rootDocsRouteRef,
|
||||
rootRouteRef,
|
||||
} from '../routes';
|
||||
import { TechDocsReaderLayout } from '../reader';
|
||||
import {
|
||||
TechDocsAddons,
|
||||
techdocsApiRef,
|
||||
@@ -140,9 +139,7 @@ const techDocsPage = PageBlueprint.make({
|
||||
path: '/docs',
|
||||
routeRef: rootRouteRef,
|
||||
loader: () =>
|
||||
import('../home/components/TechDocsIndexPage').then(m => (
|
||||
<m.TechDocsIndexPage />
|
||||
)),
|
||||
import('./NfsTechDocsIndexPage').then(m => <m.NfsTechDocsIndexPage />),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -186,9 +183,12 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
|
||||
);
|
||||
});
|
||||
|
||||
return import('../Router').then(({ TechDocsReaderRouter }) => (
|
||||
return Promise.all([
|
||||
import('../Router'),
|
||||
import('./NfsTechDocsReaderLayout'),
|
||||
]).then(([{ TechDocsReaderRouter }, { NfsTechDocsReaderLayout }]) => (
|
||||
<TechDocsReaderRouter>
|
||||
<TechDocsReaderLayout
|
||||
<NfsTechDocsReaderLayout
|
||||
withSearch={!config.withoutSearch}
|
||||
withHeader={!config.withoutHeader}
|
||||
/>
|
||||
|
||||
@@ -140,6 +140,7 @@ export type TechDocsReaderLayoutProps = {
|
||||
*/
|
||||
export const TechDocsReaderLayout = (props: TechDocsReaderLayoutProps) => {
|
||||
const { withSearch, withHeader = true } = props;
|
||||
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
{withHeader && <TechDocsReaderPageHeader />}
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"@backstage/plugin-user-settings-common": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@backstage/ui": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
|
||||
@@ -37,8 +37,8 @@ const userSettingsPage = PageBlueprint.makeWithOverrides({
|
||||
path: '/settings',
|
||||
routeRef: settingsRouteRef,
|
||||
loader: () =>
|
||||
import('./components/SettingsPage').then(m => (
|
||||
<m.SettingsPage
|
||||
import('./components/SettingsPage/SettingsPage').then(m => (
|
||||
<m.NfsSettingsPage
|
||||
providerSettings={inputs.providerSettings?.get(
|
||||
coreExtensionData.reactElement,
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,11 @@ import { ReactElement } from 'react';
|
||||
import { UserSettingsAuthProviders } from '../AuthProviders';
|
||||
import { UserSettingsFeatureFlags } from '../FeatureFlags';
|
||||
import { UserSettingsGeneral } from '../General';
|
||||
import { SettingsLayout, SettingsLayoutRouteProps } from '../SettingsLayout';
|
||||
import { SettingsLayoutRouteProps } from '../SettingsLayout';
|
||||
import {
|
||||
NfsSettingsLayout,
|
||||
SettingsLayout,
|
||||
} from '../SettingsLayout/SettingsLayout';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { userSettingsTranslationRef } from '../../translation';
|
||||
|
||||
@@ -56,3 +60,35 @@ export const DefaultSettingsPage = (props: {
|
||||
</SettingsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsDefaultSettingsPage = (props: {
|
||||
tabs?: ReactElement<SettingsLayoutRouteProps>[];
|
||||
providerSettings?: JSX.Element;
|
||||
}) => {
|
||||
const { providerSettings, tabs } = props;
|
||||
const { t } = useTranslationRef(userSettingsTranslationRef);
|
||||
|
||||
return (
|
||||
<NfsSettingsLayout>
|
||||
<SettingsLayout.Route
|
||||
path="general"
|
||||
title={t('defaultSettingsPage.tabsTitle.general')}
|
||||
>
|
||||
<UserSettingsGeneral />
|
||||
</SettingsLayout.Route>
|
||||
<SettingsLayout.Route
|
||||
path="auth-providers"
|
||||
title={t('defaultSettingsPage.tabsTitle.authProviders')}
|
||||
>
|
||||
<UserSettingsAuthProviders providerSettings={providerSettings} />
|
||||
</SettingsLayout.Route>
|
||||
<SettingsLayout.Route
|
||||
path="feature-flags"
|
||||
title={t('defaultSettingsPage.tabsTitle.featureFlags')}
|
||||
>
|
||||
<UserSettingsFeatureFlags />
|
||||
</SettingsLayout.Route>
|
||||
{tabs}
|
||||
</NfsSettingsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
RoutedTabs,
|
||||
useSidebarPinState,
|
||||
} from '@backstage/core-components';
|
||||
import { HeaderPage } from '@backstage/ui';
|
||||
import {
|
||||
attachComponentData,
|
||||
useElementFilter,
|
||||
@@ -80,6 +81,30 @@ export const SettingsLayout = (props: SettingsLayoutProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsSettingsLayout = (props: SettingsLayoutProps) => {
|
||||
const { title, children } = props;
|
||||
const { isMobile } = useSidebarPinState();
|
||||
const { t } = useTranslationRef(userSettingsTranslationRef);
|
||||
|
||||
const routes = useElementFilter(children, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: LAYOUT_ROUTE_DATA_KEY,
|
||||
withStrictError:
|
||||
'Child of SettingsLayout must be an SettingsLayout.Route',
|
||||
})
|
||||
.getElements<SettingsLayoutRouteProps>()
|
||||
.map(child => child.props),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isMobile && <HeaderPage title={title ?? t('settingsLayout.title')} />}
|
||||
<RoutedTabs routes={routes} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
attachComponentData(SettingsLayout, LAYOUT_DATA_KEY, true);
|
||||
|
||||
SettingsLayout.Route = Route;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { useOutlet } from 'react-router-dom';
|
||||
import { DefaultSettingsPage } from '../DefaultSettingsPage';
|
||||
import { NfsDefaultSettingsPage } from '../DefaultSettingsPage/DefaultSettingsPage';
|
||||
import { useElementFilter } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
SettingsLayoutProps,
|
||||
@@ -52,3 +53,33 @@ export const SettingsPage = (props: { providerSettings?: JSX.Element }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const NfsSettingsPage = (props: { providerSettings?: JSX.Element }) => {
|
||||
const { providerSettings } = props;
|
||||
const outlet = useOutlet();
|
||||
const layout = useElementFilter(outlet, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: LAYOUT_DATA_KEY,
|
||||
})
|
||||
.getElements<SettingsLayoutProps>(),
|
||||
);
|
||||
const tabs = useElementFilter(outlet, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: LAYOUT_ROUTE_DATA_KEY,
|
||||
})
|
||||
.getElements<SettingsLayoutRouteProps>(),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(layout.length !== 0 && layout) || (
|
||||
<NfsDefaultSettingsPage
|
||||
tabs={tabs}
|
||||
providerSettings={providerSettings}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3968,6 +3968,7 @@ __metadata:
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@graphiql/react": "npm:0.29.0"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
@@ -4664,6 +4665,7 @@ __metadata:
|
||||
"@backstage/frontend-defaults": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@remixicon/react": "npm:^4.6.0"
|
||||
"@testing-library/jest-dom": "npm:^6.0.0"
|
||||
@@ -5230,6 +5232,7 @@ __metadata:
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:4.0.0-alpha.61"
|
||||
@@ -5370,6 +5373,7 @@ __metadata:
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^"
|
||||
"@backstage/plugin-devtools-react": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.9.13"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:^4.0.0-alpha.60"
|
||||
@@ -5572,6 +5576,7 @@ __metadata:
|
||||
"@backstage/plugin-devtools-common": "workspace:^"
|
||||
"@backstage/plugin-devtools-react": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.9.13"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:^4.0.0-alpha.57"
|
||||
@@ -6260,6 +6265,7 @@ __metadata:
|
||||
"@backstage/plugin-signals-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.9.13"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@testing-library/jest-dom": "npm:^6.0.0"
|
||||
@@ -7017,6 +7023,7 @@ __metadata:
|
||||
"@backstage/plugin-techdocs-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@codemirror/language": "npm:^6.0.0"
|
||||
"@codemirror/legacy-modes": "npm:^6.1.0"
|
||||
"@codemirror/view": "npm:^6.0.0"
|
||||
@@ -7298,6 +7305,7 @@ __metadata:
|
||||
"@backstage/plugin-search-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@backstage/version-bridge": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
@@ -7641,6 +7649,7 @@ __metadata:
|
||||
"@backstage/plugin-techdocs-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:4.0.0-alpha.61"
|
||||
@@ -7724,6 +7733,7 @@ __metadata:
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@backstage/ui": "workspace:^"
|
||||
"@material-ui/core": "npm:^4.12.2"
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:4.0.0-alpha.61"
|
||||
@@ -9686,7 +9696,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.14.3, @grpc/grpc-js@npm:^1.7.1":
|
||||
"@grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.7.1":
|
||||
version: 1.12.5
|
||||
resolution: "@grpc/grpc-js@npm:1.12.5"
|
||||
dependencies:
|
||||
"@grpc/proto-loader": "npm:^0.7.13"
|
||||
"@js-sdsl/ordered-map": "npm:^4.4.2"
|
||||
checksum: 10/4f8ead236dcab4d94e15e62d65ad2d93732d37f5cc52ffafe67ae00f69eae4a4c97d6d34a1b9eac9f30206468f2d15302ea6649afcba1d38929afa9d1e7c12d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.14.3":
|
||||
version: 1.14.3
|
||||
resolution: "@grpc/grpc-js@npm:1.14.3"
|
||||
dependencies:
|
||||
@@ -22498,13 +22518,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/semver@npm:^7, @types/semver@npm:^7.1.0":
|
||||
"@types/semver@npm:^7":
|
||||
version: 7.7.1
|
||||
resolution: "@types/semver@npm:7.7.1"
|
||||
checksum: 10/8f09e7e6ca3ded67d78ba7a8f7535c8d9cf8ced83c52e7f3ac3c281fe8c689c3fe475d199d94390dc04fc681d51f2358b430bb7b2e21c62de24f2bee2c719068
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/semver@npm:^7.1.0":
|
||||
version: 7.7.0
|
||||
resolution: "@types/semver@npm:7.7.0"
|
||||
checksum: 10/ee4514c6c852b1c38f951239db02f9edeea39f5310fad9396a00b51efa2a2d96b3dfca1ae84c88181ea5b7157c57d32d7ef94edacee36fbf975546396b85ba5b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/send@npm:*, @types/send@npm:<1":
|
||||
version: 0.17.6
|
||||
resolution: "@types/send@npm:0.17.6"
|
||||
@@ -24506,7 +24533,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.14.1, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0":
|
||||
"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.14.1, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0":
|
||||
version: 8.15.0
|
||||
resolution: "acorn@npm:8.15.0"
|
||||
bin:
|
||||
acorn: bin/acorn
|
||||
checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn@npm:^8.16.0":
|
||||
version: 8.16.0
|
||||
resolution: "acorn@npm:8.16.0"
|
||||
bin:
|
||||
@@ -31447,7 +31483,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-xml-parser@npm:5.4.1, fast-xml-parser@npm:^5.3.4":
|
||||
"fast-xml-parser@npm:5.4.1":
|
||||
version: 5.4.1
|
||||
resolution: "fast-xml-parser@npm:5.4.1"
|
||||
dependencies:
|
||||
@@ -31470,6 +31506,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-xml-parser@npm:^5.3.4":
|
||||
version: 5.3.5
|
||||
resolution: "fast-xml-parser@npm:5.3.5"
|
||||
dependencies:
|
||||
strnum: "npm:^2.1.2"
|
||||
bin:
|
||||
fxparser: src/cli/cli.js
|
||||
checksum: 10/913363c2cf9ab8038bd2b666698d99d44b977725f0198f3dfff3a5d34c3109ef49d3a163a0f390f69ed00ad33b81355112dec8be5e79a13f8e6c7aaf146204b8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fastest-stable-stringify@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "fastest-stable-stringify@npm:2.0.2"
|
||||
@@ -32861,13 +32908,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globals@npm:^17.0.0, globals@npm:^17.3.0":
|
||||
"globals@npm:^17.0.0":
|
||||
version: 17.4.0
|
||||
resolution: "globals@npm:17.4.0"
|
||||
checksum: 10/ffad244617e94efcb3da72b7beefc941167c21316148ce378f322db7af72db06468f370e23224b3c7b17b5173a7c75b134e5e7b0949f2828519054a76892508d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globals@npm:^17.3.0":
|
||||
version: 17.3.0
|
||||
resolution: "globals@npm:17.3.0"
|
||||
checksum: 10/44ba2b7db93eb6a2531dfba09219845e21f2e724a4f400eb59518b180b7d5bcf7f65580530e3d3023d7dc2bdbacf5d265fd87c393f567deb9a2b0472b51c9d5e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globalthis@npm:^1.0.1, globalthis@npm:^1.0.3, globalthis@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "globalthis@npm:1.0.4"
|
||||
|
||||
Reference in New Issue
Block a user