diff --git a/app-config.yaml b/app-config.yaml
index d524041ef..cd0f653e0 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -14,6 +14,8 @@ app:
links:
- url: https://backstage.io/blog/
title: Backstage Blog
+ experimental:
+ packages: all
organization:
name: Backstage
diff --git a/package.json b/package.json
index 0b195754a..b83496952 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"scripts": {
"start": "backstage-cli repo start",
"start:app-migrated": "backstage-cli repo start app-migrated backend",
+ "start:app-next": "backstage-cli repo start app-next backend",
"start:otel-prerequisites": "docker-compose -f ./open-telemetry/docker-compose.yaml up",
"build:backend": "yarn workspace backend build",
"build:all": "backstage-cli repo build --all",
diff --git a/packages/app-next/.eslintrc.js b/packages/app-next/.eslintrc.js
new file mode 100644
index 000000000..e2a53a6ad
--- /dev/null
+++ b/packages/app-next/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/packages/app-next/config.d.ts b/packages/app-next/config.d.ts
new file mode 100644
index 000000000..a92c543d4
--- /dev/null
+++ b/packages/app-next/config.d.ts
@@ -0,0 +1,14 @@
+export interface Config {
+ /**
+ * @visibility frontend
+ */
+ notificationsTester?: {
+ /**
+ * Flag to enable or disable the tester
+ * Default is enabled
+ *
+ * @visibility frontend
+ */
+ enabled: boolean;
+ };
+}
diff --git a/packages/app-next/e2e-tests/app.test.ts b/packages/app-next/e2e-tests/app.test.ts
new file mode 100644
index 000000000..f0124e6c0
--- /dev/null
+++ b/packages/app-next/e2e-tests/app.test.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 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 { test, expect } from '@playwright/test';
+
+test('App should render the welcome page', async ({ page }) => {
+ await page.goto('/');
+ await expect(page.getByText('My Company Catalog')).toBeVisible();
+});
diff --git a/packages/app-next/package.json b/packages/app-next/package.json
new file mode 100644
index 000000000..7c932bab9
--- /dev/null
+++ b/packages/app-next/package.json
@@ -0,0 +1,84 @@
+{
+ "name": "app-next",
+ "version": "0.0.0",
+ "private": true,
+ "backstage": {
+ "role": "frontend"
+ },
+ "bundled": true,
+ "dependencies": {
+ "@backstage-community/plugin-badges": "^0.9.0",
+ "@backstage-community/plugin-cost-insights": "^0.15.1",
+ "@backstage-community/plugin-explore": "^0.9.0",
+ "@backstage-community/plugin-github-actions": "^0.11.0",
+ "@backstage-community/plugin-graphiql": "^0.4.1",
+ "@backstage-community/plugin-tech-radar": "^1.6.0",
+ "@backstage-community/plugin-todo": "^0.9.0",
+ "@backstage/canon": "backstage:^",
+ "@backstage/cli": "backstage:^",
+ "@backstage/core-app-api": "backstage:^",
+ "@backstage/core-compat-api": "backstage:^",
+ "@backstage/core-components": "backstage:^",
+ "@backstage/core-plugin-api": "backstage:^",
+ "@backstage/frontend-defaults": "backstage:^",
+ "@backstage/frontend-plugin-api": "backstage:^",
+ "@backstage/integration-react": "backstage:^",
+ "@backstage/plugin-api-docs": "backstage:^",
+ "@backstage/plugin-catalog": "backstage:^",
+ "@backstage/plugin-catalog-graph": "backstage:^",
+ "@backstage/plugin-catalog-react": "backstage:^",
+ "@backstage/plugin-home": "backstage:^",
+ "@backstage/plugin-kubernetes": "backstage:^",
+ "@backstage/plugin-notifications": "backstage:^",
+ "@backstage/plugin-org": "backstage:^",
+ "@backstage/plugin-scaffolder": "backstage:^",
+ "@backstage/plugin-search": "backstage:^",
+ "@backstage/plugin-search-react": "backstage:^",
+ "@backstage/plugin-signals": "backstage:^",
+ "@backstage/plugin-techdocs": "backstage:^",
+ "@backstage/plugin-techdocs-module-addons-contrib": "backstage:^",
+ "@backstage/plugin-techdocs-react": "backstage:^",
+ "@backstage/plugin-user-settings": "backstage:^",
+ "@backstage/theme": "backstage:^",
+ "@material-ui/core": "^4.11.0",
+ "@material-ui/icons": "^4.9.1",
+ "backstage-plugin-techdocs-addon-mermaid": "^0.21.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.3.0",
+ "react-router-dom": "^6.3.0"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.32.3",
+ "@testing-library/dom": "^10.1.0",
+ "@testing-library/jest-dom": "^6.0.0",
+ "@testing-library/react": "^16.0.0",
+ "@types/d3": "^7.4.3",
+ "@types/node": "^22.0.0",
+ "@types/react-dom": "*"
+ },
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "test": "backstage-cli package test",
+ "lint": "backstage-cli package lint",
+ "clean": "backstage-cli package clean"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "files": [
+ "dist",
+ "config.d.ts"
+ ],
+ "configSchema": "config.d.ts"
+}
diff --git a/packages/app-next/public/android-chrome-192x192.png b/packages/app-next/public/android-chrome-192x192.png
new file mode 100644
index 000000000..eec0ae25b
Binary files /dev/null and b/packages/app-next/public/android-chrome-192x192.png differ
diff --git a/packages/app-next/public/apple-touch-icon.png b/packages/app-next/public/apple-touch-icon.png
new file mode 100644
index 000000000..3158830ac
Binary files /dev/null and b/packages/app-next/public/apple-touch-icon.png differ
diff --git a/packages/app-next/public/favicon-16x16.png b/packages/app-next/public/favicon-16x16.png
new file mode 100644
index 000000000..58cf61a35
Binary files /dev/null and b/packages/app-next/public/favicon-16x16.png differ
diff --git a/packages/app-next/public/favicon-32x32.png b/packages/app-next/public/favicon-32x32.png
new file mode 100644
index 000000000..c0915ece7
Binary files /dev/null and b/packages/app-next/public/favicon-32x32.png differ
diff --git a/packages/app-next/public/favicon.ico b/packages/app-next/public/favicon.ico
new file mode 100644
index 000000000..5e45e5dfb
Binary files /dev/null and b/packages/app-next/public/favicon.ico differ
diff --git a/packages/app-next/public/google-cloud.png b/packages/app-next/public/google-cloud.png
new file mode 100644
index 000000000..09b3c6ea4
Binary files /dev/null and b/packages/app-next/public/google-cloud.png differ
diff --git a/packages/app-next/public/graphiql.png b/packages/app-next/public/graphiql.png
new file mode 100644
index 000000000..3a2b884c3
Binary files /dev/null and b/packages/app-next/public/graphiql.png differ
diff --git a/packages/app-next/public/index.html b/packages/app-next/public/index.html
new file mode 100644
index 000000000..76b78b0a3
--- /dev/null
+++ b/packages/app-next/public/index.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= config.getString('app.title') %>
+
+
+
+
+
+
+
diff --git a/packages/app-next/public/manifest.json b/packages/app-next/public/manifest.json
new file mode 100644
index 000000000..4a7c1b4ec
--- /dev/null
+++ b/packages/app-next/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "Backstage",
+ "name": "Backstage",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "48x48",
+ "type": "image/png"
+ }
+ ],
+ "start_url": "./index.html",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/packages/app-next/public/robots.txt b/packages/app-next/public/robots.txt
new file mode 100644
index 000000000..b21f0887a
--- /dev/null
+++ b/packages/app-next/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow: /
diff --git a/packages/app-next/public/safari-pinned-tab.svg b/packages/app-next/public/safari-pinned-tab.svg
new file mode 100644
index 000000000..0f500b300
--- /dev/null
+++ b/packages/app-next/public/safari-pinned-tab.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/app-next/public/tech-radar.png b/packages/app-next/public/tech-radar.png
new file mode 100644
index 000000000..a68b502fd
Binary files /dev/null and b/packages/app-next/public/tech-radar.png differ
diff --git a/packages/app-next/src/App.test.tsx b/packages/app-next/src/App.test.tsx
new file mode 100644
index 000000000..639036375
--- /dev/null
+++ b/packages/app-next/src/App.test.tsx
@@ -0,0 +1,27 @@
+import { render, waitFor } from '@testing-library/react';
+import app from './App';
+
+describe('App', () => {
+ it('should render', async () => {
+ process.env = {
+ NODE_ENV: 'test',
+ APP_CONFIG: [
+ {
+ data: {
+ app: {
+ title: 'Test',
+ support: { url: 'http://localhost:7007/support' },
+ },
+ backend: { baseUrl: 'http://localhost:7007' },
+ },
+ context: 'test',
+ },
+ ] as any,
+ };
+
+ const rendered = render(app);
+ await waitFor(() => {
+ expect(rendered.baseElement).toBeInTheDocument();
+ });
+ });
+});
diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx
new file mode 100644
index 000000000..45d690557
--- /dev/null
+++ b/packages/app-next/src/App.tsx
@@ -0,0 +1,256 @@
+import { badgesPlugin } from './plugins';
+
+import { ProxiedSignInPage } from '@backstage/core-components';
+import { FlatRoutes } from '@backstage/core-app-api';
+import { CatalogIndexPage, catalogPlugin } from '@backstage/plugin-catalog';
+import { default as alphaCatalogPlugin } from '@backstage/plugin-catalog/alpha';
+import { catalogGraphPlugin } from '@backstage/plugin-catalog-graph';
+import {
+ CostInsightsLabelDataflowInstructionsPage,
+ CostInsightsPage,
+ CostInsightsProjectGrowthInstructionsPage,
+} from '@backstage-community/plugin-cost-insights';
+import { ExplorePage } from '@backstage-community/plugin-explore';
+import { Navigate, Route } from 'react-router';
+import {
+ TechDocsIndexPage,
+ TechDocsReaderPage,
+ techdocsPlugin,
+} from '@backstage/plugin-techdocs';
+import { UnifiedThemeProvider, themes } from '@backstage/theme';
+
+import { GraphiQLPage } from '@backstage-community/plugin-graphiql';
+
+import {
+ SettingsLayout,
+ UserSettingsPage,
+} from '@backstage/plugin-user-settings';
+import { apertureTheme } from './theme/aperture';
+import { apis } from './apis';
+
+import { orgPlugin } from '@backstage/plugin-org';
+
+import { CssBaseline } from '@material-ui/core';
+import { HomepageCompositionRoot, VisitListener } from '@backstage/plugin-home';
+import { CustomizableHomePage } from './components/home/CustomizableHomePage';
+import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
+import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+import {
+ ExpandableNavigation,
+ LightBox,
+ ReportIssue,
+ TextSize,
+} from '@backstage/plugin-techdocs-module-addons-contrib';
+import { Mermaid } from 'backstage-plugin-techdocs-addon-mermaid';
+import { SignalsDisplay } from '@backstage/plugin-signals';
+import { NotificationSettings } from './components/settings/NotificationSettings';
+
+// New Frontend System Imports
+import { createApp } from '@backstage/frontend-defaults';
+import {
+ compatWrapper,
+ convertLegacyApp,
+ convertLegacyAppOptions,
+ convertLegacyRouteRef,
+ convertLegacyRouteRefs,
+} from '@backstage/core-compat-api';
+import {
+ AppRootElementBlueprint,
+ createFrontendModule,
+ SignInPageBlueprint,
+ ThemeBlueprint,
+} from '@backstage/frontend-plugin-api';
+import { rootNav } from './components/Root';
+import {
+ EntityKindPicker,
+ EntityTypePicker,
+ UserListPicker,
+ EntityOwnerPicker,
+ EntityLifecyclePicker,
+ EntityTagPicker,
+ EntityProcessingStatusPicker,
+ EntityNamespacePicker,
+} from '@backstage/plugin-catalog-react';
+
+const routes = (
+
+ } />
+ }>
+
+
+ } />
+ }
+ />
+ }
+ />
+ } />
+ }
+ >
+
+
+
+
+
+
+
+
+
+ } />
+ } />
+
+ }>
+
+
+
+
+
+);
+
+const legacyFeatures = convertLegacyApp(routes);
+
+const signalsDisplayExtension = AppRootElementBlueprint.make({
+ name: 'signals-display-extension',
+ params: {
+ element: compatWrapper(),
+ },
+});
+
+const visitListenerExtension = AppRootElementBlueprint.make({
+ name: 'visit-listener-extension',
+ params: {
+ element: ,
+ },
+});
+
+const optionsModule = convertLegacyAppOptions({
+ // TODO:(awanlin) the badges plugin doesn't support the new frontend system yet
+ plugins: [badgesPlugin],
+});
+
+const proxiedSignInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props => (
+
+ ),
+ },
+});
+
+const lightThemeExtension = ThemeBlueprint.make({
+ name: 'light',
+ params: {
+ theme: {
+ id: 'light',
+ title: 'Light',
+ variant: 'light',
+ Provider: ({ children }) => (
+
+ ),
+ },
+ },
+});
+
+const darkThemeExtension = ThemeBlueprint.make({
+ name: 'dark',
+ params: {
+ theme: {
+ id: 'dark',
+ title: 'Dark',
+ variant: 'dark',
+ Provider: ({ children }) => (
+
+ ),
+ },
+ },
+});
+
+const apertureThemeExtension = ThemeBlueprint.make({
+ name: 'aperture',
+ params: {
+ theme: {
+ id: 'aperture',
+ title: 'Aperture',
+ variant: 'light',
+ Provider: ({ children }) => (
+
+
+ {children}
+
+ ),
+ },
+ },
+});
+
+const catalogPluginOverride = alphaCatalogPlugin.withOverrides({
+ extensions: [
+ alphaCatalogPlugin.getExtension('page:catalog').override({
+ params: {
+ loader: async () =>
+ compatWrapper(
+
+
+
+
+
+
+
+
+
+ >
+ }
+ />,
+ ),
+ },
+ }),
+ ],
+});
+
+const app = createApp({
+ features: [
+ optionsModule,
+ ...legacyFeatures,
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [
+ ...apis,
+ proxiedSignInPage,
+ lightThemeExtension,
+ darkThemeExtension,
+ apertureThemeExtension,
+ rootNav,
+ signalsDisplayExtension,
+ visitListenerExtension,
+ ],
+ }),
+ catalogPluginOverride,
+ ],
+ bindRoutes({ bind }) {
+ bind(convertLegacyRouteRefs(catalogPlugin.externalRoutes), {
+ createComponent: convertLegacyRouteRef(scaffolderPlugin.routes.root),
+ viewTechDoc: convertLegacyRouteRef(techdocsPlugin.routes.docRoot),
+ createFromTemplate: convertLegacyRouteRef(
+ scaffolderPlugin.routes.selectedTemplate,
+ ),
+ });
+ bind(convertLegacyRouteRefs(scaffolderPlugin.externalRoutes), {
+ viewTechDoc: convertLegacyRouteRef(techdocsPlugin.routes.docRoot),
+ });
+ bind(convertLegacyRouteRefs(catalogGraphPlugin.externalRoutes), {
+ catalogEntity: convertLegacyRouteRef(catalogPlugin.routes.catalogEntity),
+ });
+ bind(convertLegacyRouteRefs(orgPlugin.externalRoutes), {
+ catalogIndex: convertLegacyRouteRef(catalogPlugin.routes.catalogIndex),
+ });
+ },
+});
+
+export default app.createRoot();
diff --git a/packages/app-next/src/apis.ts b/packages/app-next/src/apis.ts
new file mode 100644
index 000000000..853d671d7
--- /dev/null
+++ b/packages/app-next/src/apis.ts
@@ -0,0 +1,140 @@
+import {
+ graphQlBrowseApiRef,
+ GraphQLEndpoints,
+} from '@backstage-community/plugin-graphiql';
+import {
+ costInsightsApiRef,
+ ExampleCostInsightsClient,
+} from '@backstage-community/plugin-cost-insights';
+import {
+ ScmAuth,
+ ScmIntegrationsApi,
+ scmIntegrationsApiRef,
+} from '@backstage/integration-react';
+
+import {
+ createApiFactory,
+ githubAuthApiRef,
+ discoveryApiRef,
+ oauthRequestApiRef,
+ errorApiRef,
+ configApiRef,
+ identityApiRef,
+} from '@backstage/core-plugin-api';
+
+import { GithubAuth } from '@backstage/core-app-api';
+import { visitsApiRef, VisitsWebStorageApi } from '@backstage/plugin-home';
+
+// New Frontend System imports
+import { ApiBlueprint } from '@backstage/frontend-plugin-api';
+
+const scmIntegrationsApi = ApiBlueprint.make({
+ name: 'scm-integrations',
+ params: {
+ factory: createApiFactory({
+ api: scmIntegrationsApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
+ }),
+ },
+});
+
+const scmAuthApi = ApiBlueprint.make({
+ name: 'scm-auth',
+ params: {
+ factory: ScmAuth.createDefaultApiFactory(),
+ },
+});
+
+const githubAuthApi = ApiBlueprint.make({
+ name: 'github-auth',
+ params: {
+ factory: createApiFactory({
+ api: githubAuthApiRef,
+ deps: {
+ configApi: configApiRef,
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
+ GithubAuth.create({
+ discoveryApi,
+ oauthRequestApi,
+ defaultScopes: ['read:user'],
+ environment: configApi.getString('auth.environment'),
+ }),
+ }),
+ },
+});
+
+const graphQlBrowseApi = ApiBlueprint.make({
+ name: 'graphql-browse',
+ params: {
+ factory: createApiFactory({
+ api: graphQlBrowseApiRef,
+ deps: {
+ errorApi: errorApiRef,
+ graphGithubAuthApi: githubAuthApiRef,
+ discoveryApi: discoveryApiRef,
+ },
+ factory: ({ errorApi, graphGithubAuthApi, discoveryApi }) =>
+ GraphQLEndpoints.from([
+ GraphQLEndpoints.create({
+ id: 'backstage',
+ title: 'GraphQL Backend',
+ url: discoveryApi.getBaseUrl('graphql'),
+ }),
+ GraphQLEndpoints.github({
+ id: 'github',
+ title: 'GitHub',
+ errorApi,
+ githubAuthApi: graphGithubAuthApi,
+ }),
+ GraphQLEndpoints.create({
+ id: 'gitlab',
+ title: 'GitLab',
+ url: 'https://gitlab.com/api/graphql',
+ }),
+ GraphQLEndpoints.create({
+ id: 'swapi',
+ title: 'SWAPI',
+ url: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
+ }),
+ ]),
+ }),
+ },
+});
+
+const costInsightsApi = ApiBlueprint.make({
+ name: 'cost-insights',
+ params: {
+ factory: createApiFactory(
+ costInsightsApiRef,
+ new ExampleCostInsightsClient(),
+ ),
+ },
+});
+
+const visitsApi = ApiBlueprint.make({
+ name: 'visits',
+ params: {
+ factory: createApiFactory({
+ api: visitsApiRef,
+ deps: {
+ identityApi: identityApiRef,
+ errorApi: errorApiRef,
+ },
+ factory: ({ identityApi, errorApi }) =>
+ VisitsWebStorageApi.create({ identityApi, errorApi }),
+ }),
+ },
+});
+
+export const apis = [
+ scmIntegrationsApi,
+ scmAuthApi,
+ githubAuthApi,
+ graphQlBrowseApi,
+ costInsightsApi,
+ visitsApi,
+];
diff --git a/packages/app-next/src/components/Root/ApertureLogoFull.tsx b/packages/app-next/src/components/Root/ApertureLogoFull.tsx
new file mode 100644
index 000000000..eb09a575f
--- /dev/null
+++ b/packages/app-next/src/components/Root/ApertureLogoFull.tsx
@@ -0,0 +1,79 @@
+import { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles({
+ svg: {
+ fill: '#0099ff',
+ width: 'auto',
+ height: 40,
+ },
+});
+
+export const ApertureLogoFull = () => {
+ const classes = useStyles();
+
+ return (
+
+ );
+};
diff --git a/packages/app-next/src/components/Root/ApertureLogoIcon.tsx b/packages/app-next/src/components/Root/ApertureLogoIcon.tsx
new file mode 100644
index 000000000..f91114847
--- /dev/null
+++ b/packages/app-next/src/components/Root/ApertureLogoIcon.tsx
@@ -0,0 +1,40 @@
+import { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles({
+ svg: {
+ fill: '#0099ff',
+ width: 'auto',
+ height: 40,
+ },
+});
+
+export const ApertureLogoIcon = () => {
+ const classes = useStyles();
+
+ return (
+
+ );
+};
diff --git a/packages/app-next/src/components/Root/LogoFull.tsx b/packages/app-next/src/components/Root/LogoFull.tsx
new file mode 100644
index 000000000..4508fc450
--- /dev/null
+++ b/packages/app-next/src/components/Root/LogoFull.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles({
+ svg: {
+ width: 'auto',
+ height: 30,
+ },
+ path: {
+ fill: '#7df3e1',
+ },
+});
+const LogoFull = () => {
+ const classes = useStyles();
+
+ return (
+
+ );
+};
+
+export default LogoFull;
diff --git a/packages/app-next/src/components/Root/LogoIcon.tsx b/packages/app-next/src/components/Root/LogoIcon.tsx
new file mode 100644
index 000000000..02455eb6d
--- /dev/null
+++ b/packages/app-next/src/components/Root/LogoIcon.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles({
+ svg: {
+ width: 'auto',
+ height: 28,
+ },
+ path: {
+ fill: '#7df3e1',
+ },
+});
+
+const LogoIcon = () => {
+ const classes = useStyles();
+
+ return (
+
+ );
+};
+
+export default LogoIcon;
diff --git a/packages/app-next/src/components/Root/Root.tsx b/packages/app-next/src/components/Root/Root.tsx
new file mode 100644
index 000000000..f1852ecb8
--- /dev/null
+++ b/packages/app-next/src/components/Root/Root.tsx
@@ -0,0 +1,136 @@
+import { Link, Theme, makeStyles } from '@material-ui/core';
+import HomeIcon from '@material-ui/icons/Home';
+import ExtensionIcon from '@material-ui/icons/Extension';
+import MapIcon from '@material-ui/icons/MyLocation';
+import LayersIcon from '@material-ui/icons/Layers';
+import LibraryBooks from '@material-ui/icons/LibraryBooks';
+import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
+import MoneyIcon from '@material-ui/icons/MonetizationOn';
+import LogoFull from './LogoFull';
+import LogoIcon from './LogoIcon';
+import { NavLink } from 'react-router-dom';
+import { GraphiQLIcon } from '@backstage-community/plugin-graphiql';
+import {
+ Settings as SidebarSettings,
+ UserSettingsSignInAvatar,
+} from '@backstage/plugin-user-settings';
+import { SidebarSearchModal } from '@backstage/plugin-search';
+import {
+ Sidebar,
+ sidebarConfig,
+ SidebarItem,
+ SidebarDivider,
+ SidebarSpace,
+ SidebarGroup,
+ useSidebarOpenState,
+} from '@backstage/core-components';
+import MenuIcon from '@material-ui/icons/Menu';
+import SearchIcon from '@material-ui/icons/Search';
+import { appThemeApiRef, useApi } from '@backstage/core-plugin-api';
+import { ApertureLogoFull } from './ApertureLogoFull';
+import { ApertureLogoIcon } from './ApertureLogoIcon';
+import CategoryIcon from '@material-ui/icons/Category';
+import { MyGroupsSidebarItem } from '@backstage/plugin-org';
+import GroupIcon from '@material-ui/icons/People';
+import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
+import { compatWrapper } from '@backstage/core-compat-api';
+import {
+ coreExtensionData,
+ createExtension,
+} from '@backstage/frontend-plugin-api';
+
+const useSidebarLogoStyles = makeStyles({
+ root: {
+ width: sidebarConfig.drawerWidthClosed,
+ height: 3 * sidebarConfig.logoHeight,
+ display: 'flex',
+ flexFlow: 'row nowrap',
+ alignItems: 'center',
+ marginBottom: -14,
+ },
+ link: props => ({
+ width: sidebarConfig.drawerWidthClosed,
+ marginLeft: props.themeId === 'aperture' ? 15 : 24,
+ }),
+});
+
+const SidebarLogo = () => {
+ const { isOpen } = useSidebarOpenState();
+
+ const appThemeApi = useApi(appThemeApiRef);
+ const themeId = appThemeApi.getActiveThemeId();
+ const classes = useSidebarLogoStyles({ themeId: themeId! });
+
+ const fullLogo = themeId === 'aperture' ? : ;
+ const iconLogo = themeId === 'aperture' ? : ;
+
+ return (
+
+
+ {isOpen ? fullLogo : iconLogo}
+
+
+ );
+};
+
+export const rootNav = createExtension({
+ name: 'nav',
+ attachTo: { id: 'app/layout', input: 'nav' },
+ output: [coreExtensionData.reactElement],
+ factory() {
+ return [
+ coreExtensionData.reactElement(
+ compatWrapper(
+
+
+ } to="/search">
+
+
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ to="/settings"
+ >
+
+
+ ,
+ ),
+ ),
+ ];
+ },
+});
diff --git a/packages/app-next/src/components/Root/index.ts b/packages/app-next/src/components/Root/index.ts
new file mode 100644
index 000000000..81685adca
--- /dev/null
+++ b/packages/app-next/src/components/Root/index.ts
@@ -0,0 +1 @@
+export { rootNav } from './Root';
diff --git a/packages/app-next/src/components/catalog/EntityPage.tsx b/packages/app-next/src/components/catalog/EntityPage.tsx
new file mode 100644
index 000000000..1258a24d8
--- /dev/null
+++ b/packages/app-next/src/components/catalog/EntityPage.tsx
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { useMemo, useState } from 'react';
+import { Button, Grid } from '@material-ui/core';
+import BadgeIcon from '@material-ui/icons/CallToAction';
+import {
+ EntityApiDefinitionCard,
+ EntityConsumedApisCard,
+ EntityConsumingComponentsCard,
+ EntityHasApisCard,
+ EntityProvidedApisCard,
+ EntityProvidingComponentsCard,
+} from '@backstage/plugin-api-docs';
+import { EntityBadgesDialog } from '@backstage-community/plugin-badges';
+import {
+ EntityAboutCard,
+ EntityDependsOnComponentsCard,
+ EntityDependsOnResourcesCard,
+ EntityHasComponentsCard,
+ EntityHasResourcesCard,
+ EntityHasSubcomponentsCard,
+ EntityHasSystemsCard,
+ EntityLayout,
+ EntityLinksCard,
+ EntityOrphanWarning,
+ EntityProcessingErrorsPanel,
+ EntityRelationWarning,
+ EntitySwitch,
+ hasCatalogProcessingErrors,
+ hasRelationWarnings,
+ isComponentType,
+ isKind,
+ isOrphan,
+} from '@backstage/plugin-catalog';
+import {
+ EntityGithubActionsContent,
+ EntityRecentGithubActionsRunsCard,
+ isGithubActionsAvailable,
+} from '@backstage-community/plugin-github-actions';
+import {
+ EntityGroupProfileCard,
+ EntityMembersListCard,
+ EntityOwnershipCard,
+ EntityUserProfileCard,
+} from '@backstage/plugin-org';
+import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
+import { EntityTodoContent } from '@backstage-community/plugin-todo';
+import { EmptyState } from '@backstage/core-components';
+import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph';
+import {
+ EntityKubernetesContent,
+ isKubernetesAvailable,
+} from '@backstage/plugin-kubernetes';
+import {
+ ExpandableNavigation,
+ LightBox,
+ ReportIssue,
+ TextSize,
+} from '@backstage/plugin-techdocs-module-addons-contrib';
+import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+import { Mermaid } from 'backstage-plugin-techdocs-addon-mermaid';
+
+const EntityLayoutWrapper = (props: { children?: React.ReactNode }) => {
+ const [badgesDialogOpen, setBadgesDialogOpen] = useState(false);
+
+ const extraMenuItems = useMemo(() => {
+ return [
+ {
+ title: 'Badges',
+ Icon: BadgeIcon,
+ onClick: () => setBadgesDialogOpen(true),
+ },
+ ];
+ }, []);
+
+ return (
+ <>
+
+ {props.children}
+
+ setBadgesDialogOpen(false)}
+ />
+ >
+ );
+};
+
+const entityWarningContent = (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+);
+
+const cicdContent = (
+
+
+
+
+
+
+
+ Read more
+
+ }
+ />
+
+
+);
+
+const cicdCard = (
+
+
+
+
+
+
+
+);
+
+const techdocsContentWithAddons = (
+
+
+
+
+
+
+
+
+
+);
+
+const overviewContent = (
+
+ {entityWarningContent}
+
+
+
+
+
+
+
+
+
+
+
+
+ {cicdCard}
+
+
+
+
+
+);
+
+const serviceEntityPage = (
+
+
+ {overviewContent}
+
+
+
+
+
+
+
+ {cicdContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {techdocsContentWithAddons}
+
+
+
+
+
+
+);
+
+const websiteEntityPage = (
+
+
+ {overviewContent}
+
+
+
+
+
+
+
+ {cicdContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {techdocsContentWithAddons}
+
+
+
+
+
+
+);
+
+const defaultEntityPage = (
+
+
+ {overviewContent}
+
+
+
+ {techdocsContentWithAddons}
+
+
+
+
+
+
+);
+
+const componentPage = (
+
+
+ {serviceEntityPage}
+
+
+
+ {websiteEntityPage}
+
+
+ {defaultEntityPage}
+
+);
+
+const apiPage = (
+
+
+
+ {entityWarningContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const userPage = (
+
+
+
+ {entityWarningContent}
+
+
+
+
+
+
+
+
+
+);
+
+const groupPage = (
+
+
+
+ {entityWarningContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const systemPage = (
+
+
+
+ {entityWarningContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const domainPage = (
+
+
+
+ {entityWarningContent}
+
+
+
+
+
+
+
+
+
+);
+
+export const entityPage = (
+
+
+
+
+
+
+
+
+ {defaultEntityPage}
+
+);
diff --git a/packages/app-next/src/components/home/CustomizableHomePage.tsx b/packages/app-next/src/components/home/CustomizableHomePage.tsx
new file mode 100644
index 000000000..122c07f08
--- /dev/null
+++ b/packages/app-next/src/components/home/CustomizableHomePage.tsx
@@ -0,0 +1,79 @@
+import { Page, Content } from '@backstage/core-components';
+import {
+ HomePageCompanyLogo,
+ TemplateBackstageLogo,
+ HomePageStarredEntities,
+ HomePageToolkit,
+ CustomHomepageGrid,
+ HomePageRandomJoke,
+ HomePageTopVisited,
+ HomePageRecentlyVisited,
+} from '@backstage/plugin-home';
+import { HomePageSearchBar } from '@backstage/plugin-search';
+import { Grid } from '@material-ui/core';
+
+import { tools, useLogoStyles } from './shared';
+
+const defaultConfig = [
+ {
+ component: 'HomePageSearchBar',
+ x: 0,
+ y: 0,
+ width: 24,
+ height: 2,
+ },
+ {
+ component: 'HomePageRecentlyVisited',
+ x: 0,
+ y: 1,
+ width: 5,
+ height: 4,
+ },
+ {
+ component: 'HomePageTopVisited',
+ x: 5,
+ y: 1,
+ width: 5,
+ height: 4,
+ },
+ {
+ component: 'HomePageStarredEntities',
+ x: 0,
+ y: 2,
+ width: 6,
+ height: 4,
+ },
+ {
+ component: 'HomePageToolkit',
+ x: 6,
+ y: 6,
+ width: 4,
+ height: 4,
+ },
+];
+
+export const CustomizableHomePage = () => {
+ const { svg, path, container } = useLogoStyles();
+
+ return (
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/app-next/src/components/home/shared.tsx b/packages/app-next/src/components/home/shared.tsx
new file mode 100644
index 000000000..f182c939b
--- /dev/null
+++ b/packages/app-next/src/components/home/shared.tsx
@@ -0,0 +1,43 @@
+import { TemplateBackstageLogoIcon } from '@backstage/plugin-home';
+import { makeStyles } from '@material-ui/core/styles';
+
+export const useLogoStyles = makeStyles(theme => ({
+ container: {
+ margin: theme.spacing(5, 0),
+ },
+ svg: {
+ width: 'auto',
+ height: 100,
+ },
+ path: {
+ fill: '#7df3e1',
+ },
+}));
+
+export const tools = [
+ {
+ url: 'https://backstage.io/docs',
+ label: 'Docs',
+ icon: ,
+ },
+ {
+ url: 'https://github.com/backstage/backstage',
+ label: 'GitHub',
+ icon: ,
+ },
+ {
+ url: 'https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md',
+ label: 'Contributing',
+ icon: ,
+ },
+ {
+ url: 'https://backstage.io/plugins',
+ label: 'Plugins Directory',
+ icon: ,
+ },
+ {
+ url: 'https://github.com/backstage/backstage/issues/new/choose',
+ label: 'Submit New Issue',
+ icon: ,
+ },
+];
diff --git a/packages/app-next/src/components/search/SearchPage.tsx b/packages/app-next/src/components/search/SearchPage.tsx
new file mode 100644
index 000000000..f4aa8cf48
--- /dev/null
+++ b/packages/app-next/src/components/search/SearchPage.tsx
@@ -0,0 +1,169 @@
+import {
+ CatalogIcon,
+ Content,
+ DocsIcon,
+ Header,
+ Page,
+} from '@backstage/core-components';
+import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
+import { SearchType } from '@backstage/plugin-search';
+import {
+ DefaultResultListItem,
+ SearchBar,
+ SearchFilter,
+ SearchResult,
+ SearchResultPager,
+ useSearch,
+} from '@backstage/plugin-search-react';
+import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
+import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core';
+
+import { ToolSearchResultListItem } from '@backstage-community/plugin-explore';
+import { useApi } from '@backstage/core-plugin-api';
+import {
+ catalogApiRef,
+ CATALOG_FILTER_EXISTS,
+} from '@backstage/plugin-catalog-react';
+import BuildIcon from '@material-ui/icons/Build';
+
+const useStyles = makeStyles((theme: Theme) => ({
+ bar: {
+ padding: theme.spacing(1, 0),
+ },
+ filters: {
+ padding: theme.spacing(2),
+ marginTop: theme.spacing(2),
+ },
+ filter: {
+ '& + &': {
+ marginTop: theme.spacing(2.5),
+ },
+ },
+}));
+
+const SearchPage = () => {
+ const classes = useStyles();
+ const { types } = useSearch();
+ const catalogApi = useApi(catalogApiRef);
+ return (
+
+
+
+
+
+
+
+
+ ,
+ },
+ {
+ value: 'techdocs',
+ name: 'Documentation',
+ icon: ,
+ },
+ {
+ value: 'tools',
+ name: 'Tools',
+ icon: ,
+ },
+ ]}
+ />
+
+ {types.includes('techdocs') && (
+ {
+ // Return a list of entities which are documented.
+ const { items } = await catalogApi.getEntities({
+ fields: ['metadata.name'],
+ filter: {
+ 'metadata.annotations.backstage.io/techdocs-ref':
+ CATALOG_FILTER_EXISTS,
+ },
+ });
+
+ const names = items.map(entity => entity.metadata.name);
+ names.sort();
+ return names;
+ }}
+ />
+ )}
+
+
+
+
+
+
+ {({ results }) => (
+
+ {results.map(({ type, document, highlight, rank }) => {
+ switch (type) {
+ case 'software-catalog':
+ return (
+
+ );
+ case 'techdocs':
+ return (
+
+ );
+ case 'tools':
+ return (
+
+ );
+ default:
+ return (
+
+ );
+ }
+ })}
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export const searchPage = ;
diff --git a/packages/app-next/src/components/settings/NotificationSettings.tsx b/packages/app-next/src/components/settings/NotificationSettings.tsx
new file mode 100644
index 000000000..b240893da
--- /dev/null
+++ b/packages/app-next/src/components/settings/NotificationSettings.tsx
@@ -0,0 +1,66 @@
+import { Box, Button, Grid, Typography } from '@material-ui/core';
+import {
+ configApiRef,
+ discoveryApiRef,
+ errorApiRef,
+ fetchApiRef,
+ useApi,
+} from '@backstage/core-plugin-api';
+
+import { InfoCard } from '@backstage/core-components';
+
+import { UserNotificationSettingsCard } from '@backstage/plugin-notifications';
+
+export const NotificationSettings = () => {
+ const config = useApi(configApiRef);
+ const fetchApi = useApi(fetchApiRef);
+ const discovery = useApi(discoveryApiRef);
+ const errorApi = useApi(errorApiRef);
+
+ const isEnabled =
+ config.getOptionalBoolean('notificationsTester.enabled') ?? true;
+
+ const handleNotifyClick = async () => {
+ const notificationTesterUrl = await discovery.getBaseUrl(
+ 'notifications-tester',
+ );
+ const response = await fetchApi.fetch(`${notificationTesterUrl}/test`, {
+ method: 'POST',
+ });
+ if (!response.ok) {
+ errorApi.post(
+ new Error(`Failed to send notification: ${response.status}`),
+ );
+ }
+ };
+
+ return (
+
+
+
+
+ {isEnabled && (
+
+
+
+
+
+ Note: this card is not part of the default Notifications Setting
+ and was added to be able to try out the Notification system for
+ this Demo site.
+
+
+
+
+ )}
+
+ );
+};
diff --git a/packages/app-next/src/index.tsx b/packages/app-next/src/index.tsx
new file mode 100644
index 000000000..2e4bb6bd6
--- /dev/null
+++ b/packages/app-next/src/index.tsx
@@ -0,0 +1,5 @@
+import ReactDOM from 'react-dom/client';
+import app from './App';
+import '@backstage/canon/css/styles.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(app);
diff --git a/packages/app-next/src/plugins.ts b/packages/app-next/src/plugins.ts
new file mode 100644
index 000000000..552c32a1f
--- /dev/null
+++ b/packages/app-next/src/plugins.ts
@@ -0,0 +1 @@
+export { badgesPlugin } from '@backstage-community/plugin-badges';
diff --git a/packages/app-next/src/setupTests.ts b/packages/app-next/src/setupTests.ts
new file mode 100644
index 000000000..7b0828bfa
--- /dev/null
+++ b/packages/app-next/src/setupTests.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom';
diff --git a/packages/app-next/src/theme/aperture.ts b/packages/app-next/src/theme/aperture.ts
new file mode 100644
index 000000000..95304b5f7
--- /dev/null
+++ b/packages/app-next/src/theme/aperture.ts
@@ -0,0 +1,295 @@
+import {
+ createBaseThemeOptions,
+ pageTheme as defaultPageThemes,
+ PageTheme,
+ palettes,
+ createUnifiedTheme,
+} from '@backstage/theme';
+
+import { alpha } from '@material-ui/core/styles';
+
+const pageThemesFontColorOverride: Record = {};
+Object.keys(defaultPageThemes).map(key => {
+ pageThemesFontColorOverride[key] = {
+ ...defaultPageThemes[key],
+ fontColor: '#172B4D',
+ };
+});
+
+export const apertureTheme = createUnifiedTheme({
+ ...createBaseThemeOptions({
+ palette: {
+ ...palettes.light,
+ primary: {
+ main: '#0052CC',
+ light: '#4C9AFF',
+ dark: '#172B4D',
+ },
+ secondary: {
+ main: '#FF5630',
+ light: '#FFAB00',
+ dark: '#6554C0',
+ },
+ grey: {
+ 50: '#C1C7D0',
+ 100: '#7A869A',
+ 200: '#6B778C',
+ 300: '#5E6C84',
+ 400: '#505F79',
+ 500: '#42526E',
+ 600: '#344563',
+ 700: '#253858',
+ 800: '#172B4D',
+ 900: '#091E42',
+ },
+ error: {
+ main: '#FF5630',
+ light: '#FF8F73',
+ dark: '#DE350B',
+ },
+ warning: {
+ main: '#FFAB00',
+ light: '#FFE380',
+ dark: '#FF8B00',
+ },
+ success: {
+ main: '#36B37E',
+ light: '#79F2C0',
+ dark: '#006644',
+ },
+ info: {
+ main: '#0065FF',
+ light: '#4C9AFF',
+ dark: '#0747A6',
+ },
+ navigation: {
+ ...palettes.light.navigation,
+ background: '#172B4D',
+ color: '#FFFFFF',
+ indicator: '#2684FF',
+ navItem: {
+ hoverBackground: 'rgba(116,118,121,0.6)',
+ },
+ },
+ text: {
+ primary: '#172B48',
+ },
+ background: {
+ default: '#FFFFFF',
+ },
+ },
+ }),
+ typography: {
+ htmlFontSize: 16,
+ fontFamily: 'Roboto, sans-serif',
+ h1: {
+ fontSize: 54,
+ fontWeight: 700,
+ marginBottom: 10,
+ },
+ h2: {
+ fontSize: 40,
+ fontWeight: 700,
+ marginBottom: 8,
+ },
+ h3: {
+ fontSize: 32,
+ fontWeight: 700,
+ marginBottom: 6,
+ },
+ h4: {
+ fontWeight: 700,
+ fontSize: 28,
+ marginBottom: 6,
+ },
+ h5: {
+ fontWeight: 700,
+ fontSize: 24,
+ marginBottom: 4,
+ },
+ h6: {
+ fontWeight: 700,
+ fontSize: 20,
+ marginBottom: 2,
+ },
+ },
+ pageTheme: pageThemesFontColorOverride,
+ defaultPageTheme: 'home',
+ components: {
+ BackstageHeader: {
+ styleOverrides: {
+ header: ({ theme }) => ({
+ backgroundImage: 'unset',
+ boxShadow: 'unset',
+ paddingBottom: theme.spacing(1),
+ }),
+ title: ({ theme }) => ({
+ color: theme.page.fontColor,
+ fontWeight: 900,
+ }),
+ subtitle: ({ theme }) => ({
+ color: alpha(theme.page.fontColor, 0.8),
+ }),
+ type: ({ theme }) => ({
+ color: alpha(theme.page.fontColor, 0.8),
+ }),
+ },
+ },
+ BackstageHeaderTabs: {
+ styleOverrides: {
+ defaultTab: {
+ fontSize: 'inherit',
+ textTransform: 'none',
+ },
+ },
+ },
+ BackstageOpenedDropdown: {
+ styleOverrides: {
+ icon: {
+ '& path': {
+ fill: '#FFFFFF',
+ },
+ },
+ },
+ },
+ BackstageTable: {
+ styleOverrides: {
+ root: {
+ '&> :first-child': {
+ borderBottom: '1px solid #D5D5D5',
+ boxShadow: 'none',
+ },
+ '& th': {
+ borderTop: 'none',
+ textTransform: 'none !important',
+ },
+ },
+ },
+ },
+ CatalogReactUserListPicker: {
+ styleOverrides: {
+ title: {
+ textTransform: 'none',
+ },
+ },
+ },
+ MuiAlert: {
+ styleOverrides: {
+ root: {
+ borderRadius: 0,
+ },
+ standardError: ({ theme }) => ({
+ color: '#FFFFFF',
+ backgroundColor: theme.palette.error.dark,
+ '& $icon': {
+ color: '#FFFFFF',
+ },
+ }),
+ standardInfo: ({ theme }) => ({
+ color: '#FFFFFF',
+ backgroundColor: theme.palette.primary.dark,
+ '& $icon': {
+ color: '#FFFFFF',
+ },
+ }),
+ standardSuccess: ({ theme }) => ({
+ color: '#FFFFFF',
+ backgroundColor: theme.palette.success.dark,
+ '& $icon': {
+ color: '#FFFFFF',
+ },
+ }),
+ standardWarning: ({ theme }) => ({
+ color: theme.palette.grey[700],
+ backgroundColor: theme.palette.secondary.light,
+ '& $icon': {
+ color: theme.palette.grey[700],
+ },
+ }),
+ },
+ },
+ MuiAutocomplete: {
+ styleOverrides: {
+ root: {
+ '&[aria-expanded=true]': {
+ backgroundColor: '#26385A',
+ color: '#FFFFFF',
+ },
+ '&[aria-expanded=true] path': {
+ fill: '#FFFFFF',
+ },
+ },
+ },
+ },
+ MuiBackdrop: {
+ styleOverrides: {
+ root: {
+ backgroundColor: 'rgba(9,30,69,0.54)',
+ },
+ },
+ },
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: 3,
+ textTransform: 'none',
+ },
+ contained: {
+ boxShadow: 'none',
+ },
+ },
+ },
+ MuiChip: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: 3,
+ backgroundColor: theme.palette.grey[50],
+ color: theme.palette.primary.dark,
+ margin: 4,
+ }),
+ },
+ },
+ MuiSelect: {
+ styleOverrides: {
+ select: {
+ '&[aria-expanded]': {
+ backgroundColor: '#26385A',
+ color: '#FFFFFF',
+ },
+ },
+ },
+ },
+ MuiSwitch: {
+ styleOverrides: {
+ root: {
+ padding: 10,
+ },
+ switchBase: {
+ padding: 12,
+ },
+ thumb: {
+ backgroundColor: '#FFFFFF',
+ height: 14,
+ width: 14,
+ },
+ track: {
+ borderRadius: 9,
+ },
+ },
+ },
+ MuiTabs: {
+ styleOverrides: {
+ indicator: {
+ transition: 'none',
+ },
+ },
+ },
+ MuiTypography: {
+ styleOverrides: {
+ button: {
+ textTransform: 'none',
+ },
+ },
+ },
+ },
+});
diff --git a/yarn.lock b/yarn.lock
index 502abd4a2..de5d349df 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -18546,6 +18546,60 @@ __metadata:
languageName: unknown
linkType: soft
+"app-next@workspace:packages/app-next":
+ version: 0.0.0-use.local
+ resolution: "app-next@workspace:packages/app-next"
+ dependencies:
+ "@backstage-community/plugin-badges": "npm:^0.9.0"
+ "@backstage-community/plugin-cost-insights": "npm:^0.15.1"
+ "@backstage-community/plugin-explore": "npm:^0.9.0"
+ "@backstage-community/plugin-github-actions": "npm:^0.11.0"
+ "@backstage-community/plugin-graphiql": "npm:^0.4.1"
+ "@backstage-community/plugin-tech-radar": "npm:^1.6.0"
+ "@backstage-community/plugin-todo": "npm:^0.9.0"
+ "@backstage/canon": "backstage:^"
+ "@backstage/cli": "backstage:^"
+ "@backstage/core-app-api": "backstage:^"
+ "@backstage/core-compat-api": "backstage:^"
+ "@backstage/core-components": "backstage:^"
+ "@backstage/core-plugin-api": "backstage:^"
+ "@backstage/frontend-defaults": "backstage:^"
+ "@backstage/frontend-plugin-api": "backstage:^"
+ "@backstage/integration-react": "backstage:^"
+ "@backstage/plugin-api-docs": "backstage:^"
+ "@backstage/plugin-catalog": "backstage:^"
+ "@backstage/plugin-catalog-graph": "backstage:^"
+ "@backstage/plugin-catalog-react": "backstage:^"
+ "@backstage/plugin-home": "backstage:^"
+ "@backstage/plugin-kubernetes": "backstage:^"
+ "@backstage/plugin-notifications": "backstage:^"
+ "@backstage/plugin-org": "backstage:^"
+ "@backstage/plugin-scaffolder": "backstage:^"
+ "@backstage/plugin-search": "backstage:^"
+ "@backstage/plugin-search-react": "backstage:^"
+ "@backstage/plugin-signals": "backstage:^"
+ "@backstage/plugin-techdocs": "backstage:^"
+ "@backstage/plugin-techdocs-module-addons-contrib": "backstage:^"
+ "@backstage/plugin-techdocs-react": "backstage:^"
+ "@backstage/plugin-user-settings": "backstage:^"
+ "@backstage/theme": "backstage:^"
+ "@material-ui/core": "npm:^4.11.0"
+ "@material-ui/icons": "npm:^4.9.1"
+ "@playwright/test": "npm:^1.32.3"
+ "@testing-library/dom": "npm:^10.1.0"
+ "@testing-library/jest-dom": "npm:^6.0.0"
+ "@testing-library/react": "npm:^16.0.0"
+ "@types/d3": "npm:^7.4.3"
+ "@types/node": "npm:^22.0.0"
+ "@types/react-dom": "npm:*"
+ backstage-plugin-techdocs-addon-mermaid: "npm:^0.21.0"
+ react: "npm:^18.2.0"
+ react-dom: "npm:^18.2.0"
+ react-router: "npm:^6.3.0"
+ react-router-dom: "npm:^6.3.0"
+ languageName: unknown
+ linkType: soft
+
"app@npm:^0.0.0, app@workspace:packages/app":
version: 0.0.0-use.local
resolution: "app@workspace:packages/app"