Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"@openchoreo/backstage-plugin-openchoreo-workflows-backend",
"@openchoreo/backstage-plugin-thunder-idp-client-node",
"@openchoreo/backstage-plugin-catalog-backend-module-openchoreo-users",
"@openchoreo/cell-diagram"
"@openchoreo/cell-diagram",
"@openchoreo/backstage-portal-backend"
]
],
"access": "restricted",
Expand Down
11 changes: 11 additions & 0 deletions .changeset/portal-backend-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@openchoreo/backstage-portal-backend': minor
---

Add `@openchoreo/backstage-portal-backend` — the stock portal's backend
composition (feature bundle + IDP root-router middleware) as a reusable
published package. The monorepo backend now consumes it; runtime behavior is
unchanged. The app shell also moves into the private
`@openchoreo/backstage-portal-app` package (`createPortalApp()`), leaving
`packages/app` as a thin consumer — the shared base for the new-frontend-system
migration and the custom-portal scaffold.
72 changes: 2 additions & 70 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,86 +19,18 @@
"lint": "backstage-cli package lint"
},
"dependencies": {
"@backstage-community/plugin-github-actions": "^0.20.0",
"@backstage-community/plugin-jenkins": "^0.28.0",
"@backstage/app-defaults": "^1.7.8",
"@backstage/catalog-client": "^1.15.1",
"@backstage/catalog-model": "^1.9.0",
"@backstage/cli": "^0.36.2",
"@backstage/config": "^1.3.8",
"@backstage/core-app-api": "^1.20.1",
"@backstage/core-compat-api": "^0.5.11",
"@backstage/core-components": "^0.18.10",
"@backstage/core-plugin-api": "^1.12.6",
"@backstage/frontend-app-api": "^0.16.3",
"@backstage/frontend-defaults": "^0.5.2",
"@backstage/frontend-plugin-api": "^0.17.0",
"@backstage/integration-react": "^1.2.18",
"@backstage/plugin-api-docs": "^0.14.1",
"@backstage/plugin-app": "^0.4.6",
"@backstage/plugin-app-react": "^0.2.3",
"@backstage/plugin-catalog": "^2.0.5",
"@backstage/plugin-catalog-common": "^1.1.10",
"@backstage/plugin-catalog-graph": "^0.6.4",
"@backstage/plugin-catalog-import": "^0.13.13",
"@backstage/plugin-catalog-react": "^3.0.0",
"@backstage/plugin-home": "^0.9.6",
"@backstage/plugin-kubernetes": "^0.12.19",
"@backstage/plugin-org": "^0.7.4",
"@backstage/plugin-permission-common": "^0.9.9",
"@backstage/plugin-permission-react": "^0.5.1",
"@backstage/plugin-scaffolder": "^1.37.0",
"@backstage/plugin-scaffolder-common": "^2.2.0",
"@backstage/plugin-scaffolder-react": "^2.0.0",
"@backstage/plugin-search": "^1.7.4",
"@backstage/plugin-search-react": "^1.11.4",
"@backstage/plugin-techdocs": "^1.17.6",
"@backstage/plugin-techdocs-common": "^0.1.1",
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.36",
"@backstage/plugin-techdocs-react": "^1.3.11",
"@backstage/plugin-user-settings": "^0.9.3",
"@backstage/theme": "^0.7.3",
"@backstage/ui": "^0.15.0",
"@gitbeaker/rest": "^40.6.0",
"@immobiliarelabs/backstage-plugin-gitlab": "^6.13.0",
"@material-ui/core": "4.12.4",
"@material-ui/icons": "4.11.3",
"@material-ui/lab": "4.0.0-alpha.61",
"@openchoreo/backstage-design-system": "workspace:^",
"@openchoreo/backstage-plugin": "workspace:^",
"@openchoreo/backstage-plugin-common": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-ci": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-observability": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-portal-assistant": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-workflows": "workspace:^",
"@openchoreo/backstage-plugin-platform-engineer-core": "workspace:^",
"@openchoreo/backstage-plugin-react": "workspace:^",
"@rjsf/core": "5.24.13",
"@rjsf/material-ui": "5.24.13",
"@rjsf/utils": "5.24.13",
"@rjsf/validator-ajv8": "5.24.13",
"@tanstack/react-query": "^5.101.2",
"clsx": "^2.1.1",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"devicons-react": "^1.5.0",
"json-schema": "0.4.0",
"@openchoreo/backstage-portal-app": "workspace:^",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router": "6.30.1",
"react-router-dom": "6.30.1",
"react-use": "17.6.0",
"yaml": "^2.8.1"
"react-dom": "18.3.1"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.3",
"@backstage/frontend-test-utils": "^0.6.0",
"@backstage/test-utils": "^1.7.18",
"@playwright/test": "1.56.0",
"@testing-library/dom": "9.3.4",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "14.6.1",
"@types/react-dom": "*",
"cross-env": "7.0.3"
},
Expand Down
212 changes: 2 additions & 210 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,211 +1,3 @@
import { Route } from 'react-router-dom';
import { catalogPlugin } from '@backstage/plugin-catalog';
import { catalogImportPlugin } from '@backstage/plugin-catalog-import';
import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { SearchPage } from '@backstage/plugin-search';
import {
TechDocsIndexPage,
techdocsPlugin,
TechDocsReaderPage,
} from '@backstage/plugin-techdocs';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
import { apis } from './apis';
import { CustomApiExplorerPage } from './components/catalog/CustomApiExplorerPage';
import { CustomCatalogImportPage } from './components/catalog/CustomCatalogImportPage';
import { searchPage } from './components/search/SearchPage';
import { Root } from './components/Root';
import { HomePage } from './components/Home';
import { CustomGraphNode } from '@openchoreo/backstage-plugin-react';
import { PageLoader } from '@openchoreo/backstage-design-system';
import { PlatformOverviewPage } from './components/platformOverview';
import { createPortalApp } from '@openchoreo/backstage-portal-app';

import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
import { createApp } from '@backstage/frontend-defaults';
import {
convertLegacyAppOptions,
convertLegacyAppRoot,
} from '@backstage/core-compat-api';
import { AppRouter, FlatRoutes } from '@backstage/core-app-api';

// NFS plugin features (created in Step 2 — each plugin's `/alpha` exports a
// `createFrontendPlugin` instance). These replace the API factory entries
// that previously lived in `apis.ts`.
import openchoreoPluginAlpha from '@openchoreo/backstage-plugin/alpha';
import openchoreoCiPluginAlpha from '@openchoreo/backstage-plugin-openchoreo-ci/alpha';
import openchoreoObservabilityPluginAlpha from '@openchoreo/backstage-plugin-openchoreo-observability/alpha';
import openchoreoWorkflowsPluginAlpha from '@openchoreo/backstage-plugin-openchoreo-workflows/alpha';
import platformEngineerCorePluginAlpha from '@openchoreo/backstage-plugin-platform-engineer-core/alpha';

// Upstream NFS plugin features with our overrides:
// - catalog graph default API replaced to include OpenChoreo custom relations
// - catalog entity-presentation default API replaced to add custom kind icons
// - scaffolder `page:scaffolder` disabled (our legacy <ScaffolderPage> wins)
// and form-decorators API replaced to inject the openChoreoTokenDecorator
// - customTranslationsModule reinstates the catalog-import header overrides
// that previously rode via createApp.__experimentalTranslations
import {
catalogGraphPluginAlpha,
catalogPluginAlpha,
customAppModule,
scaffolderPluginAlpha as upstreamScaffolderPluginAlpha,
} from './apis/customOverrides';

// catalog-import NFS plugin — registered so the `/catalog-import` route ref
// resolves under NFS. Our legacy `<RequirePermission><CustomCatalogImportPage /></...>`
// mount in `<FlatRoutes>` provides the actual page rendering.
import catalogImportPluginAlpha from '@backstage/plugin-catalog-import/alpha';
// api-docs and kubernetes NFS plugins — registered so that `apiDocsConfigRef`,
// `kubernetesApiRef`, etc. are present in the api holder. The host owns the
// `/api-docs` route (CustomApiExplorerPage) and the Kubernetes entity tab
// reuses upstream `EntityKubernetesContent`; without these features the apis
// they depend on are absent and the tabs throw `NotImplementedError`.
import apiDocsPluginAlpha from '@backstage/plugin-api-docs/alpha';
import kubernetesPluginAlpha from '@backstage/plugin-kubernetes/alpha';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
import { appThemes } from './themes';
import { LEGACY_KIND_ICONS } from './kindIcons';
import {
AccessControlContent,
SecretsContent,
ExecTerminalWindowPage,
} from '@openchoreo/backstage-plugin';
import {
UserSettingsPage,
SettingsLayout,
UserSettingsGeneral,
} from '@backstage/plugin-user-settings';
import { VisitListener } from '@backstage/plugin-home';

const legacyAppOptions = convertLegacyAppOptions({
apis,
icons: LEGACY_KIND_ICONS,
themes: appThemes,
});

const routes = (
<FlatRoutes>
<Route path="/" element={<HomePage />} />
{/*
`/catalog` is owned by the NFS `page:catalog` extension and
`/catalog/:namespace/:kind/:name` by `page:catalog/entity` — see
customOverrides.tsx, which overrides each loader to render the
host's `<CustomCatalogPage>` and the legacy `entityPage` JSX
respectively. The legacy `<Route path="/catalog">` mount used to
live here but double-rendered the catalog header under the NFS
compat shim.
*/}
<Route path="/docs" element={<TechDocsIndexPage />} />
<Route
path="/docs/:namespace/:kind/:name/*"
element={<TechDocsReaderPage />}
>
<TechDocsAddons>
<ReportIssue />
</TechDocsAddons>
</Route>
{/*
`/create` is owned by the NFS `page:scaffolder` extension — see
customOverrides.tsx, which overrides its loader to render
`<OpenChoreoScaffolderPage>` (the host's `<ScaffolderPage>` with
the 27 field-extension children and `CustomTemplateListPage` /
`CustomReviewStep` components). The legacy `<Route path="/create">`
mount used to live here but double-rendered the scaffolder header
under the NFS compat shim.
*/}
<Route path="/api-docs" element={<CustomApiExplorerPage />} />
<Route
path="/catalog-import"
element={
<RequirePermission permission={catalogEntityCreatePermission}>
<CustomCatalogImportPage />
</RequirePermission>
}
/>
<Route path="/search" element={<SearchPage />}>
{searchPage}
</Route>
<Route path="/settings" element={<UserSettingsPage />}>
<SettingsLayout>
<SettingsLayout.Route path="general" title="General">
<UserSettingsGeneral />
</SettingsLayout.Route>
<SettingsLayout.Route path="access-control" title="Access Control">
<AccessControlContent />
</SettingsLayout.Route>
<SettingsLayout.Route path="secrets" title="Secrets">
<SecretsContent />
</SettingsLayout.Route>
</SettingsLayout>
</Route>
<Route
path="/catalog-graph"
element={<CatalogGraphPage renderNode={CustomGraphNode} />}
/>
<Route path="/platform-overview" element={<PlatformOverviewPage />} />
{/*
Standalone full-window exec terminal, opened in a new browser tab from the
resource drawer. The page renders a fixed viewport overlay over the app
chrome, so the terminal uses the entire window.
*/}
<Route path="/exec-terminal" element={<ExecTerminalWindowPage />} />
</FlatRoutes>
);

const legacyRoot = convertLegacyAppRoot(
<>
<AlertDisplay />
<OAuthRequestDialog />
<AppRouter>
<VisitListener />
<Root>{routes}</Root>
</AppRouter>
</>,
);

const app = createApp({
features: [
// `...legacyRoot` re-emits each legacy plugin's `apis: [...]` array as
// ApiBlueprint extensions under the legacy plugin's own pluginId
// (collectLegacyRoutes). The NFS api-factory registry resolves
// same-pluginId factories last-write-wins, so the override features
// below MUST come after `...legacyRoot` to win the contest. Otherwise
// our custom catalog-graph relations, entity-presentation kind icons,
// and scaffolder form-decorator override get silently overwritten by
// upstream defaults at startup.
legacyAppOptions,
...legacyRoot,
customAppModule,
upstreamScaffolderPluginAlpha,
catalogGraphPluginAlpha,
catalogPluginAlpha,
catalogImportPluginAlpha,
apiDocsPluginAlpha,
kubernetesPluginAlpha,
openchoreoPluginAlpha,
openchoreoCiPluginAlpha,
openchoreoObservabilityPluginAlpha,
openchoreoWorkflowsPluginAlpha,
platformEngineerCorePluginAlpha,
],
bindRoutes({ bind }) {
bind(catalogPlugin.externalRoutes, {
createComponent: scaffolderPlugin.routes.root,
viewTechDoc: techdocsPlugin.routes.docRoot,
createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
});
bind(scaffolderPlugin.externalRoutes, {
registerComponent: catalogImportPlugin.routes.importPage,
viewTechDoc: techdocsPlugin.routes.docRoot,
});
},
advanced: {
// App-boot loader (waiting for config/features) — use our centered loader
// instead of the default Backstage progress bar.
loadingElement: <PageLoader />,
},
});

export default app.createRoot();
export default createPortalApp().createRoot();
30 changes: 1 addition & 29 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,18 @@
"build-image": "docker buildx build ../.. -f Dockerfile --tag openchoreo-ui:local --load"
},
"dependencies": {
"@backstage-community/plugin-jenkins-backend": "^0.24.1",
"@backstage/backend-defaults": "^0.17.1",
"@backstage/backend-plugin-api": "^1.9.1",
"@backstage/config": "^1.3.8",
"@backstage/plugin-app-backend": "^0.5.14",
"@backstage/plugin-auth-backend": "^0.29.0",
"@backstage/plugin-auth-backend-module-github-provider": "^0.5.3",
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.19",
"@backstage/plugin-auth-node": "^0.7.1",
"@backstage/plugin-catalog-backend": "^3.7.0",
"@backstage/plugin-catalog-backend-module-logs": "^0.1.22",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.20",
"@backstage/plugin-events-backend": "^0.6.2",
"@backstage/plugin-kubernetes-backend": "^0.21.4",
"@backstage/plugin-permission-backend": "^0.7.12",
"@backstage/plugin-permission-common": "^0.9.9",
"@backstage/plugin-permission-node": "^0.11.0",
"@backstage/plugin-proxy-backend": "^0.6.13",
"@backstage/plugin-scaffolder-backend": "^4.0.0",
"@backstage/plugin-scaffolder-backend-module-github": "^0.9.9",
"@backstage/plugin-search-backend": "^2.1.2",
"@backstage/plugin-search-backend-module-catalog": "^0.3.15",
"@backstage/plugin-search-backend-module-pg": "^0.5.55",
"@backstage/plugin-search-backend-module-techdocs": "^0.4.14",
"@backstage/plugin-search-backend-node": "^1.4.4",
"@backstage/plugin-techdocs-backend": "^2.2.0",
"@backstage/plugin-user-settings-backend": "^0.4.3",
"@immobiliarelabs/backstage-plugin-gitlab-backend": "^6.13.0",
"@openchoreo/backstage-plugin-auth-backend-module-openchoreo-auth": "workspace:^",
"@openchoreo/backstage-plugin-backend": "workspace:^",
"@openchoreo/backstage-plugin-catalog-backend-module": "workspace:^",
"@openchoreo/backstage-plugin-catalog-backend-module-openchoreo-users": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-ci-backend": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-observability-backend": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-portal-assistant-backend": "workspace:^",
"@openchoreo/backstage-plugin-openchoreo-workflows-backend": "workspace:^",
"@openchoreo/backstage-plugin-permission-backend-module-openchoreo-policy": "workspace:^",
"@openchoreo/backstage-plugin-platform-engineer-core-backend": "workspace:^",
"@openchoreo/backstage-plugin-scaffolder-backend-module": "workspace:^",
"@openchoreo/openchoreo-auth": "workspace:^",
"@openchoreo/backstage-portal-backend": "workspace:^",
"app": "workspace:^",
"better-sqlite3": "9.6.0",
"cookie-parser": "1.4.7",
Expand Down
Loading
Loading