From 1450e5779f42a98089984b942461eece5db3af7c Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Wed, 29 Apr 2026 12:45:25 +0300 Subject: [PATCH 1/2] Add Rehydrate, Remove Resources Tab and RBAC Sidebar menu --- .../dcm/.changeset/kind-ravens-rehydrate.md | 25 + workspaces/dcm/packages/app/package.json | 1 - workspaces/dcm/packages/app/src/App.tsx | 2 - .../packages/app/src/components/Root/Root.tsx | 12 - .../dcm/plugins/dcm-common/report.api.md | 114 ++- .../dcm-common/src/clients/CatalogApi.ts | 8 + .../src/clients/CatalogClient.test.ts | 64 ++ .../dcm-common/src/clients/CatalogClient.ts | 12 + .../dcm-common/src/clients/PlacementApi.ts | 54 -- .../src/clients/PlacementClient.test.ts | 122 --- .../dcm-common/src/clients/PlacementClient.ts | 77 -- .../dcm-common/src/clients/ResourcesApi.ts | 35 + .../dcm-common/src/clients/ResourcesClient.ts | 53 ++ .../plugins/dcm-common/src/clients/index.ts | 4 +- .../dcm/plugins/dcm-common/src/types/index.ts | 2 +- .../plugins/dcm-common/src/types/placement.ts | 56 -- .../plugins/dcm-common/src/types/resources.ts | 65 ++ workspaces/dcm/plugins/dcm/report.api.md | 8 +- workspaces/dcm/plugins/dcm/src/apis.ts | 11 +- .../dcm/src/components/DcmCrudTabLayout.tsx | 2 + .../dcm/src/components/DcmEntitiesTable.tsx | 98 +- .../src/components/DcmRequestHistoryTable.tsx | 15 +- .../dcm/src/components/DcmSuccessSnackbar.tsx | 52 ++ .../dcm/src/components/dcmTabListHelpers.tsx | 82 +- .../dcm/plugins/dcm/src/hooks/useCrudTab.ts | 35 +- .../dcm/src/hooks/usePersistedPageSize.ts | 63 ++ workspaces/dcm/plugins/dcm/src/index.ts | 2 +- .../CatalogItemInstancesTabContent.tsx | 111 ++- .../catalog-items/CatalogItemsTabContent.tsx | 1 + .../data-center/EnvironmentsTabContent.tsx | 17 +- .../components/EntitiesTab.tsx | 89 +- .../src/pages/policies/PoliciesTabContent.tsx | 9 +- .../pages/providers/ProvidersTabContent.tsx | 1 + .../pages/resources/ResourcesTabContent.tsx | 301 +++--- .../components/ResourceFormFields.tsx | 185 ---- .../pages/resources/resourceFormTypes.test.ts | 86 -- .../src/pages/resources/resourceFormTypes.ts | 75 -- .../components/SpecEntitiesTab.tsx | 88 +- .../service-spec/ServiceSpecsTabContent.tsx | 17 +- .../service-types/ServiceTypesTabContent.tsx | 61 +- workspaces/dcm/plugins/dcm/src/plugin.ts | 8 +- workspaces/dcm/yarn.lock | 863 +----------------- 42 files changed, 963 insertions(+), 2023 deletions(-) create mode 100644 workspaces/dcm/.changeset/kind-ravens-rehydrate.md create mode 100644 workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.test.ts delete mode 100644 workspaces/dcm/plugins/dcm-common/src/clients/PlacementApi.ts delete mode 100644 workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.test.ts delete mode 100644 workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.ts create mode 100644 workspaces/dcm/plugins/dcm-common/src/clients/ResourcesApi.ts create mode 100644 workspaces/dcm/plugins/dcm-common/src/clients/ResourcesClient.ts delete mode 100644 workspaces/dcm/plugins/dcm-common/src/types/placement.ts create mode 100644 workspaces/dcm/plugins/dcm-common/src/types/resources.ts create mode 100644 workspaces/dcm/plugins/dcm/src/components/DcmSuccessSnackbar.tsx create mode 100644 workspaces/dcm/plugins/dcm/src/hooks/usePersistedPageSize.ts delete mode 100644 workspaces/dcm/plugins/dcm/src/pages/resources/components/ResourceFormFields.tsx delete mode 100644 workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.test.ts delete mode 100644 workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.ts diff --git a/workspaces/dcm/.changeset/kind-ravens-rehydrate.md b/workspaces/dcm/.changeset/kind-ravens-rehydrate.md new file mode 100644 index 00000000000..3818b4a8fb0 --- /dev/null +++ b/workspaces/dcm/.changeset/kind-ravens-rehydrate.md @@ -0,0 +1,25 @@ +--- +'@red-hat-developer-hub/backstage-plugin-dcm': minor +'@red-hat-developer-hub/backstage-plugin-dcm-common': minor +--- + +DCM UI and catalog client updates for the example app and published plugins. + +**Example app (`packages/app`) — RBAC navigation** + +- Removed the **RBAC** sidebar entry under Administration and the `/rbac` route. +- Dropped the `@backstage-community/plugin-rbac` frontend dependency. The RBAC backend plugin may remain for permissions; only the menu and page were removed. + +**`@red-hat-developer-hub/backstage-plugin-dcm` — Resources tab** + +- Removed the **Resources** tab from the Data Center page and all **Placement / `resources`** proxy usage from the plugin. +- Removed `placementApiRef`, `resources` route ref, and the `PlacementClient` integration from the plugin surface. + +**`@red-hat-developer-hub/backstage-plugin-dcm-common` — placement API removed; catalog rehydrate** + +- Removed the Placement API client, types, and tests tied to the internal `resources` API. +- Added **`rehydrateCatalogItemInstance`** on `CatalogApi` / `CatalogClient`: `POST .../catalog-item-instances/{id}:rehydrate`. + +**Catalog item instances UI** + +- Added a **Rehydrate** action (outlined button with refresh icon) on each instance row, success and error snackbars, and a unit test for the new client method. diff --git a/workspaces/dcm/packages/app/package.json b/workspaces/dcm/packages/app/package.json index 7723577751b..6afdd739d8c 100644 --- a/workspaces/dcm/packages/app/package.json +++ b/workspaces/dcm/packages/app/package.json @@ -19,7 +19,6 @@ "lint": "backstage-cli package lint" }, "dependencies": { - "@backstage-community/plugin-rbac": "1.33.2", "@backstage/app-defaults": "^1.5.16", "@backstage/catalog-model": "^1.7.3", "@backstage/cli": "^0.35.2", diff --git a/workspaces/dcm/packages/app/src/App.tsx b/workspaces/dcm/packages/app/src/App.tsx index a94ac817203..0645c19004b 100644 --- a/workspaces/dcm/packages/app/src/App.tsx +++ b/workspaces/dcm/packages/app/src/App.tsx @@ -52,7 +52,6 @@ import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; import { DcmPage } from '@red-hat-developer-hub/backstage-plugin-dcm'; import { useRhdhTheme } from './hooks/useRhdhTheme'; -import { RbacPage } from '@backstage-community/plugin-rbac'; import '@patternfly/patternfly/patternfly.css'; import '@patternfly/patternfly/patternfly-charts.css'; @@ -123,7 +122,6 @@ const routes = ( } /> } /> } /> - } /> ); diff --git a/workspaces/dcm/packages/app/src/components/Root/Root.tsx b/workspaces/dcm/packages/app/src/components/Root/Root.tsx index 04521b2b32a..33e668dd589 100644 --- a/workspaces/dcm/packages/app/src/components/Root/Root.tsx +++ b/workspaces/dcm/packages/app/src/components/Root/Root.tsx @@ -41,7 +41,6 @@ import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; import SecurityIcon from '@material-ui/icons/Security'; import StorageIcon from '@material-ui/icons/Storage'; -import VpnKeyIcon from '@material-ui/icons/VpnKey'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import LogoFull from './LogoFull'; @@ -235,7 +234,6 @@ export const Root = ({ children }: PropsWithChildren<{}>) => { const location = useLocation(); usePatternFlyTheme(); const isDcmActive = location.pathname.startsWith('/dcm'); - const isRbacActive = location.pathname.startsWith('/rbac'); return ( @@ -274,16 +272,6 @@ export const Root = ({ children }: PropsWithChildren<{}>) => { isDcmActive ? classes.submenuItemActive : classes.inactiveItem }`} /> - diff --git a/workspaces/dcm/plugins/dcm-common/report.api.md b/workspaces/dcm/plugins/dcm-common/report.api.md index d1c1cbeff0d..15f4709dd78 100644 --- a/workspaces/dcm/plugins/dcm-common/report.api.md +++ b/workspaces/dcm/plugins/dcm-common/report.api.md @@ -35,6 +35,9 @@ export interface CatalogApi { listCatalogItems(): Promise; // (undocumented) listServiceTypes(): Promise; + rehydrateCatalogItemInstance( + catalogItemInstanceId: string, + ): Promise; // (undocumented) updateCatalogItem( catalogItemId: string, @@ -71,6 +74,10 @@ export class CatalogClient extends DcmBaseClient implements CatalogApi { // (undocumented) listServiceTypes(): Promise; // (undocumented) + rehydrateCatalogItemInstance( + catalogItemInstanceId: string, + ): Promise; + // (undocumented) protected readonly serviceName = 'Catalog'; // (undocumented) updateCatalogItem( @@ -264,46 +271,15 @@ export interface FieldConfigurationDependsOn { } // @public -export function parseDcmEntityStatus(raw: string): DcmEntityStatus | undefined; - -// @public -export interface PlacementApi { - createResource(resource: Resource, id?: string): Promise; - deleteResource(resourceId: string): Promise; - getResource(resourceId: string): Promise; - listResources(options?: { - provider?: string; - maxPageSize?: number; - pageToken?: string; - }): Promise; - rehydrateResource( - resourceId: string, - request: RehydrateRequest, - ): Promise; +export interface ListServiceTypeInstancesParams { + max_page_size?: number; + page_token?: string; + provider?: string; + show_deleted?: boolean; } // @public -export class PlacementClient extends DcmBaseClient implements PlacementApi { - // (undocumented) - createResource(resource: Resource, id?: string): Promise; - // (undocumented) - deleteResource(resourceId: string): Promise; - // (undocumented) - getResource(resourceId: string): Promise; - // (undocumented) - listResources(options?: { - provider?: string; - maxPageSize?: number; - pageToken?: string; - }): Promise; - // (undocumented) - rehydrateResource( - resourceId: string, - request: RehydrateRequest, - ): Promise; - // (undocumented) - protected readonly serviceName = 'Placement'; -} +export function parseDcmEntityStatus(raw: string): DcmEntityStatus | undefined; // @public export interface Policy { @@ -449,24 +425,6 @@ export class ProvidersClient extends DcmBaseClient implements ProvidersApi { // @public export type ProviderStatus = 'registered' | 'updated'; -// @public -export interface RehydrateRequest { - // (undocumented) - new_resource_id: string; -} - -// @public -export interface Resource { - approval_status?: string; - catalog_item_instance_id: string; - create_time?: string; - id?: string; - path?: string; - provider_name?: string; - spec: Record; - update_time?: string; -} - // @public export interface ResourceCapacity { // (undocumented) @@ -480,11 +438,20 @@ export interface ResourceCapacity { } // @public -export interface ResourceList { +export interface ResourcesApi { + listServiceTypeInstances( + params?: ListServiceTypeInstancesParams, + ): Promise; +} + +// @public +export class ResourcesClient extends DcmBaseClient implements ResourcesApi { // (undocumented) - next_page_token?: string; + listServiceTypeInstances( + params?: ListServiceTypeInstancesParams, + ): Promise; // (undocumented) - resources: Resource[]; + protected readonly serviceName = 'Resources'; } // @public @@ -506,6 +473,37 @@ export interface ServiceType { update_time?: string; } +// @public +export interface ServiceTypeInstance { + // (undocumented) + create_time?: string; + // (undocumented) + delete_time?: string; + deleted?: boolean; + id: string; + path?: string; + provider_name?: string; + spec?: ServiceTypeInstanceSpec; + status?: string; + // (undocumented) + update_time?: string; +} + +// @public +export interface ServiceTypeInstanceList { + // (undocumented) + instances?: ServiceTypeInstance[]; + // (undocumented) + next_page_token?: string; +} + +// @public +export interface ServiceTypeInstanceSpec { + // (undocumented) + [key: string]: unknown; + service_type?: string; +} + // @public export interface ServiceTypeList { // (undocumented) diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogApi.ts b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogApi.ts index d08e2742238..a12fc771166 100644 --- a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogApi.ts +++ b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogApi.ts @@ -52,5 +52,13 @@ export interface CatalogApi { createCatalogItemInstance( instance: CatalogItemInstance, ): Promise; + /** + * Triggers catalog item instance rehydrate (placement); may assign a new resource id. + * + * @public + */ + rehydrateCatalogItemInstance( + catalogItemInstanceId: string, + ): Promise; deleteCatalogItemInstance(catalogItemInstanceId: string): Promise; } diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.test.ts b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.test.ts new file mode 100644 index 00000000000..50679fdc72b --- /dev/null +++ b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 type { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; +import { CatalogClient } from './CatalogClient'; +import type { CatalogItemInstance } from '../types/catalog'; + +const BASE_URL = 'http://localhost/api/dcm'; + +const MOCK_INSTANCE: CatalogItemInstance = { + api_version: 'v1alpha1', + display_name: 'Test instance', + uid: 'inst-1', + spec: { + catalog_item_id: 'ci-1', + user_values: [], + }, + resource_id: 'res-new', +}; + +function makeClient(fetchFn: jest.Mock) { + const discoveryApi: DiscoveryApi = { + getBaseUrl: jest.fn().mockResolvedValue(BASE_URL), + }; + const fetchApi: FetchApi = { fetch: fetchFn }; + return new CatalogClient({ discoveryApi, fetchApi }); +} + +function okJson(data: unknown): Response { + return { + status: 200, + ok: true, + json: async () => data, + } as unknown as Response; +} + +describe('CatalogClient', () => { + it('rehydrateCatalogItemInstance calls POST catalog-item-instances/{id}:rehydrate', async () => { + const fetchFn = jest.fn().mockResolvedValue(okJson(MOCK_INSTANCE)); + const client = makeClient(fetchFn); + + const result = await client.rehydrateCatalogItemInstance('inst-1'); + + expect(result).toEqual(MOCK_INSTANCE); + const [url, init] = fetchFn.mock.calls[0]; + expect(url).toBe( + `${BASE_URL}/proxy/catalog-item-instances/inst-1:rehydrate`, + ); + expect(init.method).toBe('POST'); + }); +}); diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts index 4a7818f0c5a..5dd263f6c26 100644 --- a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts +++ b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts @@ -111,6 +111,18 @@ export class CatalogClient extends DcmBaseClient implements CatalogApi { }); } + /** + * @public + */ + async rehydrateCatalogItemInstance( + catalogItemInstanceId: string, + ): Promise { + return this.fetch( + `catalog-item-instances/${catalogItemInstanceId}:rehydrate`, + { method: 'POST' }, + ); + } + async deleteCatalogItemInstance( catalogItemInstanceId: string, ): Promise { diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/PlacementApi.ts b/workspaces/dcm/plugins/dcm-common/src/clients/PlacementApi.ts deleted file mode 100644 index c1b8caa6129..00000000000 --- a/workspaces/dcm/plugins/dcm-common/src/clients/PlacementApi.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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 type { - Resource, - ResourceList, - RehydrateRequest, -} from '../types/placement'; - -/** - * Interface for the DCM Placement Manager API client. - * - * @public - */ -export interface PlacementApi { - /** GET /resources — list all resources, optionally filtered by provider. */ - listResources(options?: { - provider?: string; - maxPageSize?: number; - pageToken?: string; - }): Promise; - - /** GET /resources/:resourceId — fetch a single resource. */ - getResource(resourceId: string): Promise; - - /** - * POST /resources — create a new resource. - * @param resource - The resource body (catalog_item_instance_id + spec). - * @param id - Optional client-assigned resource ID (query param). - */ - createResource(resource: Resource, id?: string): Promise; - - /** DELETE /resources/:resourceId — delete a resource. */ - deleteResource(resourceId: string): Promise; - - /** POST /resources/:resourceId:rehydrate — re-evaluate a resource against current policies. */ - rehydrateResource( - resourceId: string, - request: RehydrateRequest, - ): Promise; -} diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.test.ts b/workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.test.ts deleted file mode 100644 index fed7d414af1..00000000000 --- a/workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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 type { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; -import { PlacementClient } from './PlacementClient'; -import type { Resource, RehydrateRequest } from '../types/placement'; - -const BASE_URL = 'http://localhost/api/dcm'; - -const MOCK_RESOURCE: Resource = { - catalog_item_instance_id: 'instance-abc', - spec: { memory: '4Gi' }, -}; - -function makeClient(fetchFn: jest.Mock) { - const discoveryApi: DiscoveryApi = { - getBaseUrl: jest.fn().mockResolvedValue(BASE_URL), - }; - const fetchApi: FetchApi = { fetch: fetchFn }; - return new PlacementClient({ discoveryApi, fetchApi }); -} - -function okJson(data: unknown): Response { - return { - status: 200, - ok: true, - json: async () => data, - } as unknown as Response; -} - -describe('PlacementClient', () => { - it('listResources calls GET /resources without query params by default', async () => { - const fetchFn = jest - .fn() - .mockResolvedValue(okJson({ resources: [MOCK_RESOURCE] })); - const client = makeClient(fetchFn); - - await client.listResources(); - - expect(fetchFn).toHaveBeenCalledWith( - `${BASE_URL}/proxy/resources`, - expect.any(Object), - ); - }); - - it('listResources appends query params when options are provided', async () => { - const fetchFn = jest.fn().mockResolvedValue(okJson({ resources: [] })); - const client = makeClient(fetchFn); - - await client.listResources({ - provider: 'prov-1', - maxPageSize: 10, - pageToken: 'tok', - }); - - const calledUrl = fetchFn.mock.calls[0][0] as string; - expect(calledUrl).toContain('provider=prov-1'); - expect(calledUrl).toContain('max_page_size=10'); - expect(calledUrl).toContain('page_token=tok'); - }); - - it('createResource calls POST /resources without id query param by default', async () => { - const fetchFn = jest.fn().mockResolvedValue(okJson(MOCK_RESOURCE)); - const client = makeClient(fetchFn); - - await client.createResource(MOCK_RESOURCE); - - const [url, init] = fetchFn.mock.calls[0]; - expect(url).toBe(`${BASE_URL}/proxy/resources`); - expect(init.method).toBe('POST'); - }); - - it('createResource appends id query param when provided', async () => { - const fetchFn = jest.fn().mockResolvedValue(okJson(MOCK_RESOURCE)); - const client = makeClient(fetchFn); - - await client.createResource(MOCK_RESOURCE, 'custom-id'); - - const calledUrl = fetchFn.mock.calls[0][0] as string; - expect(calledUrl).toContain('id=custom-id'); - }); - - it('deleteResource calls DELETE /resources/{id} and returns undefined', async () => { - const fetchFn = jest - .fn() - .mockResolvedValue({ status: 204, ok: true } as Response); - const client = makeClient(fetchFn); - - const result = await client.deleteResource('res-1'); - - expect(result).toBeUndefined(); - const [url, init] = fetchFn.mock.calls[0]; - expect(url).toBe(`${BASE_URL}/proxy/resources/res-1`); - expect(init.method).toBe('DELETE'); - }); - - it('rehydrateResource calls POST /resources/{id}:rehydrate', async () => { - const rehydrateReq: RehydrateRequest = { new_resource_id: 'new-res-2' }; - const fetchFn = jest.fn().mockResolvedValue(okJson(MOCK_RESOURCE)); - const client = makeClient(fetchFn); - - await client.rehydrateResource('res-1', rehydrateReq); - - const [url, init] = fetchFn.mock.calls[0]; - expect(url).toBe(`${BASE_URL}/proxy/resources/res-1:rehydrate`); - expect(init.method).toBe('POST'); - expect(JSON.parse(init.body)).toEqual(rehydrateReq); - }); -}); diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.ts b/workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.ts deleted file mode 100644 index ccdae64cb5e..00000000000 --- a/workspaces/dcm/plugins/dcm-common/src/clients/PlacementClient.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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 type { - Resource, - ResourceList, - RehydrateRequest, -} from '../types/placement'; -import type { PlacementApi } from './PlacementApi'; -import { DcmBaseClient } from './DcmBaseClient'; - -/** - * Calls the DCM Placement Manager API through the dcm-backend secure proxy. - * - * All requests are sent to `/api/dcm/proxy/` where the backend - * strips the `/proxy` prefix and forwards to: - * `{dcm.apiGatewayUrl}/api/v1alpha1/` - * - * @public - */ -export class PlacementClient extends DcmBaseClient implements PlacementApi { - protected readonly serviceName = 'Placement'; - - async listResources(options?: { - provider?: string; - maxPageSize?: number; - pageToken?: string; - }): Promise { - const params = new URLSearchParams(); - if (options?.provider) params.set('provider', options.provider); - if (options?.maxPageSize) - params.set('max_page_size', String(options.maxPageSize)); - if (options?.pageToken) params.set('page_token', options.pageToken); - const qs = params.toString(); - const path = qs ? `resources?${qs}` : 'resources'; - return this.fetch(path); - } - - async getResource(resourceId: string): Promise { - return this.fetch(`resources/${resourceId}`); - } - - async createResource(resource: Resource, id?: string): Promise { - const qs = id ? `?id=${encodeURIComponent(id)}` : ''; - return this.fetch(`resources${qs}`, { - method: 'POST', - body: JSON.stringify(resource), - }); - } - - async deleteResource(resourceId: string): Promise { - return this.fetch(`resources/${resourceId}`, { method: 'DELETE' }); - } - - async rehydrateResource( - resourceId: string, - request: RehydrateRequest, - ): Promise { - return this.fetch(`resources/${resourceId}:rehydrate`, { - method: 'POST', - body: JSON.stringify(request), - }); - } -} diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/ResourcesApi.ts b/workspaces/dcm/plugins/dcm-common/src/clients/ResourcesApi.ts new file mode 100644 index 00000000000..166fbeef3fd --- /dev/null +++ b/workspaces/dcm/plugins/dcm-common/src/clients/ResourcesApi.ts @@ -0,0 +1,35 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 type { + ListServiceTypeInstancesParams, + ServiceTypeInstanceList, +} from '../types/resources'; + +/** + * Client interface for the DCM Resources (service-type-instances) API. + * + * @public + */ +export interface ResourcesApi { + /** + * Returns a paginated list of service type instances. + * Maps to `GET /service-type-instances`. + */ + listServiceTypeInstances( + params?: ListServiceTypeInstancesParams, + ): Promise; +} diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/ResourcesClient.ts b/workspaces/dcm/plugins/dcm-common/src/clients/ResourcesClient.ts new file mode 100644 index 00000000000..cb6becc0a20 --- /dev/null +++ b/workspaces/dcm/plugins/dcm-common/src/clients/ResourcesClient.ts @@ -0,0 +1,53 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { DcmBaseClient } from './DcmBaseClient'; +import type { ResourcesApi } from './ResourcesApi'; +import type { + ListServiceTypeInstancesParams, + ServiceTypeInstanceList, +} from '../types/resources'; + +/** + * Default implementation of {@link ResourcesApi}. + * + * Routes calls through the dcm-backend secure proxy to the + * `/service-type-instances` endpoint of the DCM API Gateway. + * + * @public + */ +export class ResourcesClient extends DcmBaseClient implements ResourcesApi { + protected readonly serviceName = 'Resources'; + + /** @public */ + async listServiceTypeInstances( + params: ListServiceTypeInstancesParams = {}, + ): Promise { + const query = new URLSearchParams(); + if (params.provider !== undefined) query.set('provider', params.provider); + if (params.show_deleted !== undefined) + query.set('show_deleted', String(params.show_deleted)); + if (params.max_page_size !== undefined) + query.set('max_page_size', String(params.max_page_size)); + if (params.page_token !== undefined) + query.set('page_token', params.page_token); + const qs = query.toString(); + const suffix = qs ? `?${qs}` : ''; + return this.fetch( + `service-type-instances${suffix}`, + ); + } +} diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/index.ts b/workspaces/dcm/plugins/dcm-common/src/clients/index.ts index c037865014a..6165140e508 100644 --- a/workspaces/dcm/plugins/dcm-common/src/clients/index.ts +++ b/workspaces/dcm/plugins/dcm-common/src/clients/index.ts @@ -15,12 +15,12 @@ */ export type { CatalogApi } from './CatalogApi'; -export type { PlacementApi } from './PlacementApi'; export type { PolicyManagerApi } from './PolicyManagerApi'; export type { ProvidersApi } from './ProvidersApi'; +export type { ResourcesApi } from './ResourcesApi'; export { DcmBaseClient } from './DcmBaseClient'; export { CatalogClient } from './CatalogClient'; -export { PlacementClient } from './PlacementClient'; export { PolicyManagerClient } from './PolicyManagerClient'; export { ProvidersClient } from './ProvidersClient'; +export { ResourcesClient } from './ResourcesClient'; diff --git a/workspaces/dcm/plugins/dcm-common/src/types/index.ts b/workspaces/dcm/plugins/dcm-common/src/types/index.ts index dcd8b06ef08..b70f1518af3 100644 --- a/workspaces/dcm/plugins/dcm-common/src/types/index.ts +++ b/workspaces/dcm/plugins/dcm-common/src/types/index.ts @@ -16,6 +16,6 @@ export * from './catalog'; export * from './common'; -export * from './placement'; export * from './policy-manager'; export * from './providers'; +export * from './resources'; diff --git a/workspaces/dcm/plugins/dcm-common/src/types/placement.ts b/workspaces/dcm/plugins/dcm-common/src/types/placement.ts deleted file mode 100644 index 2bd1f19ec10..00000000000 --- a/workspaces/dcm/plugins/dcm-common/src/types/placement.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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. - */ - -/** - * Full resource representation. - * Based on the Placement Management API schema (placement.yaml). - * @public - */ -export interface Resource { - /** Unique identifier (readOnly, server-generated or client-provided via query param). */ - id?: string; - /** Resource path identifier (readOnly). */ - path?: string; - /** Name of the assigned Service Provider (readOnly). */ - provider_name?: string; - /** The catalog item instance this resource was created from. */ - catalog_item_instance_id: string; - /** Service specification JSON — must have at least one property. */ - spec: Record; - /** Approval status returned by the Policy Engine (readOnly). */ - approval_status?: string; - /** ISO 8601 creation timestamp (readOnly). */ - create_time?: string; - /** ISO 8601 last-update timestamp (readOnly). */ - update_time?: string; -} - -/** - * Paginated list of resources. - * @public - */ -export interface ResourceList { - resources: Resource[]; - next_page_token?: string; -} - -/** - * Request body for the rehydrate operation. - * @public - */ -export interface RehydrateRequest { - new_resource_id: string; -} diff --git a/workspaces/dcm/plugins/dcm-common/src/types/resources.ts b/workspaces/dcm/plugins/dcm-common/src/types/resources.ts new file mode 100644 index 00000000000..1c8b6dc8009 --- /dev/null +++ b/workspaces/dcm/plugins/dcm-common/src/types/resources.ts @@ -0,0 +1,65 @@ +/* + * Copyright Red Hat, Inc. + * + * 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. + */ + +/** + * DCM Resources API types — derived from the service-type-instances OpenAPI spec. + * + * @public + */ + +/** Spec payload of a {@link ServiceTypeInstance}. */ +export interface ServiceTypeInstanceSpec { + /** The service type this instance belongs to (e.g. "vm"). */ + service_type?: string; + [key: string]: unknown; +} + +/** A provisioned service type instance managed by DCM. */ +export interface ServiceTypeInstance { + /** Unique identifier (readOnly). */ + id: string; + /** Resource path (readOnly). */ + path?: string; + /** The provider that manages this instance. */ + provider_name?: string; + /** Instance spec containing service_type and other provider-specific fields. */ + spec?: ServiceTypeInstanceSpec; + /** Current lifecycle status. */ + status?: string; + /** Whether this instance has been soft-deleted. */ + deleted?: boolean; + create_time?: string; + update_time?: string; + delete_time?: string; +} + +/** Paginated list of {@link ServiceTypeInstance} resources. */ +export interface ServiceTypeInstanceList { + instances?: ServiceTypeInstance[]; + next_page_token?: string; +} + +/** Query parameters accepted by the list endpoint. */ +export interface ListServiceTypeInstancesParams { + /** Filter by service provider name. */ + provider?: string; + /** When true, soft-deleted instances are included alongside active ones. */ + show_deleted?: boolean; + /** Maximum number of results per page (1–100, default 100). */ + max_page_size?: number; + /** Opaque pagination token returned by a previous response. */ + page_token?: string; +} diff --git a/workspaces/dcm/plugins/dcm/report.api.md b/workspaces/dcm/plugins/dcm/report.api.md index 2d6497df25b..74134060c02 100644 --- a/workspaces/dcm/plugins/dcm/report.api.md +++ b/workspaces/dcm/plugins/dcm/report.api.md @@ -8,9 +8,9 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import type { CatalogApi } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { PathParams } from '@backstage/core-plugin-api'; -import type { PlacementApi } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import type { PolicyManagerApi } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import type { ProvidersApi } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; +import type { ResourcesApi } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import { RouteRef } from '@backstage/core-plugin-api'; import { SubRouteRef } from '@backstage/core-plugin-api'; import { Theme } from '@material-ui/core/styles'; @@ -42,15 +42,15 @@ export const dcmPlugin: BackstagePlugin< // @public export function isDarkMode(theme: Theme): boolean; -// @public -export const placementApiRef: ApiRef; - // @public export const policyManagerApiRef: ApiRef; // @public export const providersApiRef: ApiRef; +// @public +export const resourcesApiRef: ApiRef; + // @public export const RhdhLogoFull: () => JSX_2.Element; diff --git a/workspaces/dcm/plugins/dcm/src/apis.ts b/workspaces/dcm/plugins/dcm/src/apis.ts index bf825d295df..eb11b6d43eb 100644 --- a/workspaces/dcm/plugins/dcm/src/apis.ts +++ b/workspaces/dcm/plugins/dcm/src/apis.ts @@ -17,9 +17,9 @@ import { createApiRef } from '@backstage/core-plugin-api'; import type { CatalogApi, - PlacementApi, PolicyManagerApi, ProvidersApi, + ResourcesApi, } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; /** @@ -57,13 +57,12 @@ export const providersApiRef = createApiRef({ }); /** - * Backstage API ref for the DCM Placement Manager service. + * Backstage API ref for the DCM Resources service. * - * Provides list, create, delete, and rehydrate operations for Resources - * via the dcm-backend secure proxy. + * Provides read access to service type instances via the dcm-backend secure proxy. * * @public */ -export const placementApiRef = createApiRef({ - id: 'plugin.dcm.placement', +export const resourcesApiRef = createApiRef({ + id: 'plugin.dcm.resources', }); diff --git a/workspaces/dcm/plugins/dcm/src/components/DcmCrudTabLayout.tsx b/workspaces/dcm/plugins/dcm/src/components/DcmCrudTabLayout.tsx index 7b2815bf9c7..9ea2cb099b3 100644 --- a/workspaces/dcm/plugins/dcm/src/components/DcmCrudTabLayout.tsx +++ b/workspaces/dcm/plugins/dcm/src/components/DcmCrudTabLayout.tsx @@ -170,6 +170,8 @@ export function DcmCrudTabLayout({ sorting: true, padding: 'default', toolbar: false, + /** Avoid blank rows padding the table to `pageSize` when fewer rows exist. */ + emptyRowsWhenPaging: false, }} totalCount={filtered.length} page={page} diff --git a/workspaces/dcm/plugins/dcm/src/components/DcmEntitiesTable.tsx b/workspaces/dcm/plugins/dcm/src/components/DcmEntitiesTable.tsx index c7c3eb7928e..c7d098bf4e2 100644 --- a/workspaces/dcm/plugins/dcm/src/components/DcmEntitiesTable.tsx +++ b/workspaces/dcm/plugins/dcm/src/components/DcmEntitiesTable.tsx @@ -14,10 +14,13 @@ * limitations under the License. */ -import { useMemo, useState } from 'react'; -import { Link, TableColumn } from '@backstage/core-components'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { InfoCard, Link, Table, TableColumn } from '@backstage/core-components'; import { Box, IconButton, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import { usePersistedPageSize } from '../hooks/usePersistedPageSize'; +import { useDcmStyles } from './dcmStyles'; +import { DcmRequestHistoryFilter as DcmTableFilterField } from './DcmRequestHistoryTable'; import { DCM_ENTITY_STATUS, displayDcmEntityStatus, @@ -185,3 +188,94 @@ export const DCM_ENTITY_TABLE_COLUMNS: TableColumn[] = [ /** Text filter used in entity detail tables (shared implementation with request history). */ export { DcmRequestHistoryFilter as DcmTableFilterField } from './DcmRequestHistoryTable'; + +export type DcmEntitiesCardProps = Readonly<{ + /** Rows already pre-filtered to the parent entity (environment or spec). */ + allEntities: DcmEntityRow[]; + /** localStorage key for persisting the chosen page size. */ + storageKey: string; + /** + * When this value changes (e.g. navigating to a different environment) the + * current page resets to 0 so the user is not left on a non-existent page. + */ + resetKey?: string | number; +}>; + +/** + * Shared paginated entity table with filter and persistent page-size, used by + * both the environment-details and service-spec-details entity tabs. + */ +export function DcmEntitiesCard({ + allEntities, + storageKey, + resetKey, +}: DcmEntitiesCardProps) { + const classes = useDcmStyles(); + + const { filter, setFilter, filteredEntities, entityRowCount } = + useDcmEntityListState(allEntities); + + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = usePersistedPageSize(storageKey); + + useEffect(() => { + setPage(0); + }, [filter, resetKey]); + + const paginatedEntities = useMemo(() => { + const start = page * pageSize; + return filteredEntities.slice(start, start + pageSize); + }, [filteredEntities, page, pageSize]); + + const handlePageChange = useCallback( + (newPage: number, newPageSize: number) => { + setPage(newPage); + setPageSize(newPageSize); + }, + [setPageSize], + ); + + const handleRowsPerPageChange = useCallback( + (newPageSize: number) => { + setPageSize(newPageSize); + setPage(0); + }, + [setPageSize], + ); + + return ( + + } + className={classes.tableCard} + > + + + data={paginatedEntities} + columns={DCM_ENTITY_TABLE_COLUMNS} + options={{ + paging: true, + pageSize, + pageSizeOptions: [5, 10, 25, 50], + search: false, + sorting: true, + padding: 'default', + toolbar: false, + emptyRowsWhenPaging: false, + }} + totalCount={filteredEntities.length} + page={page} + onPageChange={handlePageChange} + onRowsPerPageChange={handleRowsPerPageChange} + localization={{ pagination: { labelRowsPerPage: 'rows' } }} + /> + + + ); +} diff --git a/workspaces/dcm/plugins/dcm/src/components/DcmRequestHistoryTable.tsx b/workspaces/dcm/plugins/dcm/src/components/DcmRequestHistoryTable.tsx index 031900b2197..df415101284 100644 --- a/workspaces/dcm/plugins/dcm/src/components/DcmRequestHistoryTable.tsx +++ b/workspaces/dcm/plugins/dcm/src/components/DcmRequestHistoryTable.tsx @@ -21,6 +21,7 @@ import { type Dispatch, type SetStateAction, } from 'react'; +import { usePersistedPageSize } from '../hooks/usePersistedPageSize'; import { InfoCard, Link, Table, TableColumn } from '@backstage/core-components'; import { Box, @@ -90,7 +91,7 @@ export function useDcmRequestHistoryListState( ) { const [filter, setFilter] = useState(''); const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = usePersistedPageSize('request-history'); const filteredHistory = useMemo(() => { if (!filter.trim()) return allRows; @@ -112,10 +113,13 @@ export function useDcmRequestHistoryListState( const handlePageChange = useCallback((newPage: number) => { setPage(newPage); }, []); - const handleRowsPerPageChange = useCallback((newPageSize: number) => { - setPageSize(newPageSize); - setPage(0); - }, []); + const handleRowsPerPageChange = useCallback( + (newPageSize: number) => { + setPageSize(newPageSize); + setPage(0); + }, + [setPageSize], + ); return { filter, @@ -284,6 +288,7 @@ export function DcmRequestHistoryTableCard( sorting: true, padding: 'default', toolbar: false, + emptyRowsWhenPaging: false, }} totalCount={filteredHistory.length} page={page} diff --git a/workspaces/dcm/plugins/dcm/src/components/DcmSuccessSnackbar.tsx b/workspaces/dcm/plugins/dcm/src/components/DcmSuccessSnackbar.tsx new file mode 100644 index 00000000000..53319f0013c --- /dev/null +++ b/workspaces/dcm/plugins/dcm/src/components/DcmSuccessSnackbar.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { Snackbar } from '@material-ui/core'; +import MuiAlert from '@material-ui/lab/Alert'; + +export type DcmSuccessSnackbarProps = Readonly<{ + /** The message to display, or null/undefined when hidden. */ + message: string | null | undefined; + onClose: () => void; + autoHideDuration?: number; +}>; + +/** + * Displays a transient success notification at the bottom-center of the screen. + */ +export function DcmSuccessSnackbar({ + message, + onClose, + autoHideDuration = 6000, +}: DcmSuccessSnackbarProps) { + return ( + + + {message} + + + ); +} diff --git a/workspaces/dcm/plugins/dcm/src/components/dcmTabListHelpers.tsx b/workspaces/dcm/plugins/dcm/src/components/dcmTabListHelpers.tsx index 620b58e6411..753651515f0 100644 --- a/workspaces/dcm/plugins/dcm/src/components/dcmTabListHelpers.tsx +++ b/workspaces/dcm/plugins/dcm/src/components/dcmTabListHelpers.tsx @@ -15,7 +15,7 @@ */ import type { Dispatch, SetStateAction } from 'react'; -import type { TableColumn } from '@backstage/core-components'; +import { InfoCard, Table, type TableColumn } from '@backstage/core-components'; import { Box, IconButton, @@ -24,6 +24,7 @@ import { Tooltip, } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import { useDcmStyles } from './dcmStyles'; import CloseIcon from '@material-ui/icons/Close'; import DeleteIcon from '@material-ui/icons/Delete'; import EditIcon from '@material-ui/icons/Edit'; @@ -99,6 +100,85 @@ function ActionsCell({ onEdit, onDelete }: ActionsCellProps) { ); } +export type DcmSearchTableCardProps = Readonly<{ + title: string; + /** Already-paginated rows to render. */ + data: T[]; + columns: TableColumn[]; + /** Total rows after filtering (drives the pagination footer). */ + totalCount: number; + page: number; + pageSize: number; + setPage: Dispatch>; + setPageSize: Dispatch>; + search: string; + setSearch: Dispatch>; + pageSizeOptions?: number[]; +}>; + +/** + * Shared InfoCard + Table layout used by read-only data-center tabs (service + * types, resources, …). Handles the search action, pagination wiring, and + * all the standard table options so individual tabs only provide data & columns. + */ +export function DcmSearchTableCard({ + title, + data, + columns, + totalCount, + page, + pageSize, + setPage, + setPageSize, + search, + setSearch, + pageSizeOptions = [5, 10, 25], +}: DcmSearchTableCardProps) { + const classes = useDcmStyles(); + return ( + + } + className={classes.dataCard} + titleTypographyProps={{ className: classes.cardTitle }} + > + + + data={data} + columns={columns} + options={{ + paging: true, + pageSize, + pageSizeOptions, + search: false, + sorting: true, + padding: 'default', + toolbar: false, + emptyRowsWhenPaging: false, + }} + totalCount={totalCount} + page={page} + onPageChange={(p, ps) => { + setPage(p); + setPageSize(ps); + }} + onRowsPerPageChange={ps => { + setPageSize(ps); + setPage(0); + }} + localization={{ pagination: { labelRowsPerPage: 'rows' } }} + /> + + + ); +} + export function createEditDeleteColumn(props: { onEdit: (row: T) => void; onDelete: (row: T) => void; diff --git a/workspaces/dcm/plugins/dcm/src/hooks/useCrudTab.ts b/workspaces/dcm/plugins/dcm/src/hooks/useCrudTab.ts index 062c032c978..d510d817abf 100644 --- a/workspaces/dcm/plugins/dcm/src/hooks/useCrudTab.ts +++ b/workspaces/dcm/plugins/dcm/src/hooks/useCrudTab.ts @@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { extractApiError } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; +import { usePersistedPageSize } from './usePersistedPageSize'; // Module-level state-updater factories — defined outside the hook so they do // not contribute to lexical function-nesting depth (typescript:S2004). @@ -83,6 +84,12 @@ export interface UseCrudTabOptions> { * Required when updateFn is provided. */ itemToForm?: (item: T) => F; + /** + * When provided, the selected page-size is persisted to `localStorage` under + * `dcm:pageSize:` so the user's preference survives refreshes. + * Each table should use a unique key (e.g. `'providers'`, `'policies'`). + */ + storageKey?: string; } type TouchedMap = Partial>; @@ -179,7 +186,9 @@ export function useCrudTab>( // ── Search + pagination ────────────────────────────────────────────────── const [search, setSearch] = useState(''); const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = usePersistedPageSize( + options.storageKey ?? '', + ); // ── Create dialog ──────────────────────────────────────────────────────── const [createOpen, setCreateOpen] = useState(false); @@ -251,15 +260,21 @@ export function useCrudTab>( return filtered.slice(start, start + pageSize); }, [filtered, page, pageSize]); - const onPageChange = useCallback((p: number, ps: number) => { - setPage(p); - setPageSize(ps); - }, []); - - const onRowsPerPageChange = useCallback((ps: number) => { - setPageSize(ps); - setPage(0); - }, []); + const onPageChange = useCallback( + (p: number, ps: number) => { + setPage(p); + setPageSize(ps); + }, + [setPageSize], + ); + + const onRowsPerPageChange = useCallback( + (ps: number) => { + setPageSize(ps); + setPage(0); + }, + [setPageSize], + ); // ── Create ─────────────────────────────────────────────────────────────── const handleOpenCreate = useCallback(() => { diff --git a/workspaces/dcm/plugins/dcm/src/hooks/usePersistedPageSize.ts b/workspaces/dcm/plugins/dcm/src/hooks/usePersistedPageSize.ts new file mode 100644 index 00000000000..b8527f5a51e --- /dev/null +++ b/workspaces/dcm/plugins/dcm/src/hooks/usePersistedPageSize.ts @@ -0,0 +1,63 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { useEffect, useState } from 'react'; + +const KEY_PREFIX = 'dcm:pageSize:'; + +function readStored(key: string, defaultSize: number): number { + try { + const raw = localStorage.getItem(key); + if (raw !== null) { + const parsed = Number.parseInt(raw, 10); + if (!Number.isNaN(parsed) && parsed > 0) return parsed; + } + } catch { + // localStorage may be unavailable (e.g. private-browsing restrictions) + } + return defaultSize; +} + +/** + * Behaves like `useState(defaultSize)` but persists the chosen page-size to + * `localStorage` under `dcm:pageSize:` so the user's preference + * survives page refreshes and navigation. + * + * When `storageKey` is an empty string no persistence is applied and the hook + * falls back to a plain in-memory default. + */ +export function usePersistedPageSize( + storageKey: string, + defaultSize = 5, +): [number, React.Dispatch>] { + const fullKey = storageKey ? `${KEY_PREFIX}${storageKey}` : ''; + + const [pageSize, setPageSize] = useState(() => + fullKey ? readStored(fullKey, defaultSize) : defaultSize, + ); + + useEffect(() => { + if (fullKey) { + try { + localStorage.setItem(fullKey, String(pageSize)); + } catch { + // Ignore write failures silently + } + } + }, [fullKey, pageSize]); + + return [pageSize, setPageSize]; +} diff --git a/workspaces/dcm/plugins/dcm/src/index.ts b/workspaces/dcm/plugins/dcm/src/index.ts index aef3b93c8af..0d6b83d6656 100644 --- a/workspaces/dcm/plugins/dcm/src/index.ts +++ b/workspaces/dcm/plugins/dcm/src/index.ts @@ -17,9 +17,9 @@ export { Router } from './Router'; export { dcmPlugin, DcmPage } from './plugin'; export { catalogApiRef, - placementApiRef, policyManagerApiRef, providersApiRef, + resourcesApiRef, } from './apis'; export { isDarkMode, useIsDarkMode } from './components/dcmTheme'; export { RhdhLogoFull } from './components/RhdhLogoFull'; diff --git a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx index cf1448497d5..1df828a57c2 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx @@ -17,7 +17,7 @@ import { useCallback, useMemo, useState } from 'react'; import { TableColumn } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { Chip, IconButton, Tooltip, Typography } from '@material-ui/core'; +import { Box, Chip, IconButton, Tooltip, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles(() => ({ @@ -27,16 +27,29 @@ const useStyles = makeStyles(() => ({ apiVersionChip: { maxWidth: 140, }, + actionsCell: { + display: 'flex', + alignItems: 'center', + flexWrap: 'nowrap', + gap: 4, + }, + /** Lets Tooltip wrap disabled IconButton (ref + layout) without raw ``. */ + tooltipTrigger: { + display: 'inline-flex', + }, })); +import AutorenewIcon from '@material-ui/icons/Autorenew'; import DeleteIcon from '@material-ui/icons/Delete'; import type { CatalogItem, CatalogItemInstance, } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; +import { extractApiError } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import { catalogApiRef } from '../../apis'; import { DcmCrudTabLayout } from '../../components/DcmCrudTabLayout'; import { DcmDeleteDialog } from '../../components/DcmDeleteDialog'; import { DcmErrorSnackbar } from '../../components/DcmErrorSnackbar'; +import { DcmSuccessSnackbar } from '../../components/DcmSuccessSnackbar'; import { DcmFormDialog } from '../../components/DcmFormDialog'; import { DcmFormDialogActions } from '../../components/DcmFormDialogActions'; import { DcmEmptyCell, TruncatedText } from '../../components/TruncatedText'; @@ -55,6 +68,9 @@ export function CatalogItemInstancesTabContent() { const catalogApi = useApi(catalogApiRef); const [catalogItems, setCatalogItems] = useState([]); + const [rehydratingId, setRehydratingId] = useState(null); + const [rehydrateError, setRehydrateError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); const crud = useCrudTab({ loadFn: async () => { @@ -77,9 +93,30 @@ export function CatalogItemInstancesTabContent() { ], emptyForm: emptyInstanceForm, isValid: isInstanceFormValid, + storageKey: 'catalog-item-instances', }); - const { handleOpenDelete } = crud; + const { handleOpenDelete, setItems } = crud; + + const handleRehydrate = useCallback( + async (inst: CatalogItemInstance) => { + const id = inst.uid ?? ''; + if (!id) return; + setRehydratingId(id); + setRehydrateError(null); + setSuccessMessage(null); + try { + const updated = await catalogApi.rehydrateCatalogItemInstance(id); + setItems(prev => prev.map(row => (row.uid === id ? updated : row))); + setSuccessMessage('Catalog item instance rehydrated successfully.'); + } catch (err) { + setRehydrateError(extractApiError(err)); + } finally { + setRehydratingId(null); + } + }, + [catalogApi, setItems], + ); const catalogItemName = useCallback( (id: string) => { @@ -159,21 +196,55 @@ export function CatalogItemInstancesTabContent() { title: 'Actions', field: 'actions', sorting: false, - width: '80px', - render: inst => ( - - handleOpenDelete(inst)} - > - - - - ), + width: '120px', + render: inst => { + const busy = rehydratingId === (inst.uid ?? ''); + return ( + + + + handleRehydrate(inst)} + > + + + + + + + handleOpenDelete(inst)} + > + + + + + + ); + }, }, ], - [classes, handleOpenDelete, catalogItemName], + [ + classes, + handleOpenDelete, + handleRehydrate, + catalogItemName, + rehydratingId, + ], ); type ScalarTouched = Partial< @@ -248,6 +319,16 @@ export function CatalogItemInstancesTabContent() { error={crud.deleteError} onClose={() => crud.setDeleteError(null)} /> + + setRehydrateError(null)} + /> + + setSuccessMessage(null)} + /> ); } diff --git a/workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx index e1a43b82a7b..079313af729 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/catalog-items/CatalogItemsTabContent.tsx @@ -123,6 +123,7 @@ export function CatalogItemsTabContent() { emptyForm: emptyCatalogItemForm, isValid: isCatalogItemFormValid, itemToForm: catalogItemToForm, + storageKey: 'catalog-items', }); const columns = useMemo[]>( diff --git a/workspaces/dcm/plugins/dcm/src/pages/data-center/EnvironmentsTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/data-center/EnvironmentsTabContent.tsx index 8e441b057c5..4437a4b08b0 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/data-center/EnvironmentsTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/data-center/EnvironmentsTabContent.tsx @@ -16,6 +16,7 @@ // Migrate to use useCrudTab + DcmCrudTabLayout when it is re-activated. import { useMemo, useState, useCallback, useEffect } from 'react'; +import { usePersistedPageSize } from '../../hooks/usePersistedPageSize'; import { Table, TableColumn, @@ -122,7 +123,7 @@ export function EnvironmentsTabContent() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = usePersistedPageSize('environments'); const [registerModalOpen, setRegisterModalOpen] = useState(false); useEffect(() => { @@ -282,13 +283,16 @@ export function EnvironmentsTabContent() { setPage(newPage); setPageSize(newPageSize); }, - [], + [setPageSize], ); - const handleRowsPerPageChange = useCallback((newPageSize: number) => { - setPageSize(newPageSize); - setPage(0); - }, []); + const handleRowsPerPageChange = useCallback( + (newPageSize: number) => { + setPageSize(newPageSize); + setPage(0); + }, + [setPageSize], + ); const { underlineLink } = classes; @@ -407,6 +411,7 @@ export function EnvironmentsTabContent() { sorting: true, padding: 'default', toolbar: false, + emptyRowsWhenPaging: false, }} totalCount={filteredEnvironments.length} page={page} diff --git a/workspaces/dcm/plugins/dcm/src/pages/environment-details/components/EntitiesTab.tsx b/workspaces/dcm/plugins/dcm/src/pages/environment-details/components/EntitiesTab.tsx index c0ef09c4435..33080048ac9 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/environment-details/components/EntitiesTab.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/environment-details/components/EntitiesTab.tsx @@ -14,94 +14,21 @@ * limitations under the License. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { InfoCard, Table } from '@backstage/core-components'; -import { Box } from '@material-ui/core'; -import { - DCM_ENTITY_TABLE_COLUMNS, - DcmTableFilterField, - useDcmEntityListState, -} from '../../../components/DcmEntitiesTable'; -import { - MOCK_ENTITIES, - type Environment, - type EntityRow, -} from '../../../data/environments'; -import { useDcmStyles } from '../../../components/dcmStyles'; +import { useMemo } from 'react'; +import { DcmEntitiesCard } from '../../../components/DcmEntitiesTable'; +import { MOCK_ENTITIES, type Environment } from '../../../data/environments'; export function EntitiesTab(props: Readonly<{ env: Environment }>) { const { env } = props; - const classes = useDcmStyles(); - const allEntitiesForEnv = useMemo( () => MOCK_ENTITIES.filter(e => e.envId === env.id), [env.id], ); - - const { filter, setFilter, filteredEntities, entityRowCount } = - useDcmEntityListState(allEntitiesForEnv); - - const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); - - useEffect(() => { - setPage(0); - }, [filter, env.id]); - - const paginatedEntities = useMemo(() => { - const start = page * pageSize; - return filteredEntities.slice(start, start + pageSize); - }, [filteredEntities, page, pageSize]); - - const handlePageChange = useCallback( - (newPage: number, newPageSize: number) => { - setPage(newPage); - setPageSize(newPageSize); - }, - [], - ); - - const handleRowsPerPageChange = useCallback((newPageSize: number) => { - setPageSize(newPageSize); - setPage(0); - }, []); - return ( - - } - className={classes.tableCard} - > - - - data={paginatedEntities} - columns={DCM_ENTITY_TABLE_COLUMNS} - options={{ - paging: true, - pageSize, - pageSizeOptions: [5, 10, 25, 50], - search: false, - sorting: true, - padding: 'default', - toolbar: false, - }} - totalCount={filteredEntities.length} - page={page} - onPageChange={handlePageChange} - onRowsPerPageChange={handleRowsPerPageChange} - localization={{ - pagination: { - labelRowsPerPage: 'rows', - }, - }} - /> - - + ); } diff --git a/workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx index 7fd07d8e795..be7c67f1f1c 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/policies/PoliciesTabContent.tsx @@ -106,6 +106,7 @@ export function PoliciesTabContent() { emptyForm: emptyPolicyForm, isValid: isPolicyFormValid, itemToForm: policyToForm, + storageKey: 'policies', }); const { setItems, handleOpenEdit, handleOpenDelete } = crud; @@ -209,7 +210,11 @@ export function PoliciesTabContent() { title={isEnabled ? 'Disable policy' : 'Enable policy'} placement="top" > - + {isToggling ? ( )} - + []>( diff --git a/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx index 6f337751dfa..4e171e143b0 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx @@ -14,248 +14,151 @@ * limitations under the License. */ -import { useMemo, useState } from 'react'; -import { TableColumn } from '@backstage/core-components'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { TableColumn, Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { Box, Chip, IconButton, Tooltip, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; - -const useStyles = makeStyles(() => ({ - nameCellBox: { - minWidth: 0, - }, -})); -import DeleteIcon from '@material-ui/icons/Delete'; -import type { - CatalogItemInstance, - Resource, -} from '@red-hat-developer-hub/backstage-plugin-dcm-common'; -import { catalogApiRef, placementApiRef } from '../../apis'; -import { DcmCrudTabLayout } from '../../components/DcmCrudTabLayout'; -import { DcmDeleteDialog } from '../../components/DcmDeleteDialog'; -import { DcmErrorSnackbar } from '../../components/DcmErrorSnackbar'; -import { DcmFormDialog } from '../../components/DcmFormDialog'; -import { DcmFormDialogActions } from '../../components/DcmFormDialogActions'; -import { DcmEmptyCell, TruncatedText } from '../../components/TruncatedText'; -import { useCrudTab } from '../../hooks/useCrudTab'; +import { Box, Chip, Typography } from '@material-ui/core'; +import type { ServiceTypeInstance } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; +import { resourcesApiRef } from '../../apis'; +import { DcmSearchTableCard } from '../../components/dcmTabListHelpers'; +import { useDcmStyles } from '../../components/dcmStyles'; import emptyIllustration from '../../assets/environments-empty-state.png'; -import { ResourceFormFields } from './components/ResourceFormFields'; -import { - emptyResourceForm, - formToResource, - isResourceFormValid, -} from './resourceFormTypes'; -import type { ResourceForm } from './resourceFormTypes'; +import { DcmDataCenterTabEmptyState } from '../../components/DcmDataCenterTabEmptyState'; +import { DcmEmptyCell, TruncatedText } from '../../components/TruncatedText'; +import { usePersistedPageSize } from '../../hooks/usePersistedPageSize'; export function ResourcesTabContent() { - const classes = useStyles(); - const placementApi = useApi(placementApiRef); - const catalogApi = useApi(catalogApiRef); + const classes = useDcmStyles(); + const resourcesApi = useApi(resourcesApiRef); - const [instances, setInstances] = useState([]); + const [instances, setInstances] = useState([]); + const [loading, setLoading] = useState(true); + const [search, setSearch] = useState(''); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = usePersistedPageSize('resources'); - const crud = useCrudTab({ - loadFn: async () => { - const [resList, instList] = await Promise.all([ - placementApi.listResources(), - catalogApi.listCatalogItemInstances(), - ]); - setInstances(instList.results ?? []); - return resList.resources ?? []; - }, - createFn: form => { - const clientId = form.id.trim() || undefined; - return placementApi.createResource(formToResource(form), clientId); - }, - deleteFn: id => placementApi.deleteResource(id), - getId: r => r.id ?? '', - getSearchText: r => [ - r.id, - r.catalog_item_instance_id, - r.provider_name, - r.approval_status, - ], - emptyForm: emptyResourceForm, - isValid: isResourceFormValid, - }); + const load = useCallback(() => { + setLoading(true); + resourcesApi + .listServiceTypeInstances() + .then(res => setInstances(res.instances ?? [])) + .catch(() => setInstances([])) + .finally(() => setLoading(false)); + }, [resourcesApi]); - const instanceNameById = useMemo(() => { - const map = new Map(); - instances.forEach(inst => { - if (inst.uid) map.set(inst.uid, inst.display_name || inst.uid); - }); - return map; - }, [instances]); + useEffect(() => { + load(); + }, [load]); - const columns = useMemo[]>( + const filtered = useMemo(() => { + if (!search.trim()) return instances; + const q = search.toLowerCase(); + return instances.filter( + inst => + (inst.id ?? '').toLowerCase().includes(q) || + (inst.spec?.service_type ?? '').toLowerCase().includes(q) || + (inst.provider_name ?? '').toLowerCase().includes(q) || + (inst.status ?? '').toLowerCase().includes(q), + ); + }, [instances, search]); + + const paginated = useMemo(() => { + const start = page * pageSize; + return filtered.slice(start, start + pageSize); + }, [filtered, page, pageSize]); + + const columns = useMemo[]>( () => [ { - title: 'Resource ID', + title: 'ID', field: 'id', - render: r => ( - - } - /> - {r.path && ( - } - /> - )} - - ), - }, - { - title: 'Catalog instance', - field: 'catalog_item_instance_id', - render: r => ( + render: inst => ( } /> ), }, + { + title: 'Service type', + field: 'spec.service_type', + render: inst => + inst.spec?.service_type ? ( + + ) : ( + + ), + }, { title: 'Provider', field: 'provider_name', - render: r => ( + render: inst => ( } /> ), }, { - title: 'Approval', - field: 'approval_status', - render: r => - r.approval_status ? ( - + title: 'Status', + field: 'status', + render: inst => + inst.status ? ( + {inst.status} ) : ( - - — - + ), }, { title: 'Created', field: 'create_time', - render: r => - r.create_time ? ( + render: inst => + inst.create_time ? ( - {new Date(r.create_time).toLocaleDateString()} + {new Date(inst.create_time).toLocaleDateString()} ) : ( - - — - + ), }, - { - title: 'Actions', - field: 'actions', - sorting: false, - width: '80px', - render: r => ( - - - crud.handleOpenDelete(r)} - disabled={!r.id} - > - - - - - ), - }, ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [classes, crud.handleOpenDelete, instanceNameById], + [classes], ); - return ( - <> - - items={crud.items} - filtered={crud.filtered} - paginated={crud.paginated} - columns={columns} - loading={crud.loading} - loadError={crud.loadError} - onRetry={crud.reload} - search={crud.search} - onSearchChange={crud.setSearch} - page={crud.page} - pageSize={crud.pageSize} - onPageChange={crud.onPageChange} - onRowsPerPageChange={crud.onRowsPerPageChange} - emptyTitle="No resources provisioned" - emptyDescription="Resources are created by the Placement Manager when a catalog item instance is submitted. Use the Create button to provision a new resource." - primaryActionLabel="Create" - onPrimaryAction={crud.handleOpenCreate} - illustrationSrc={emptyIllustration} - entityLabel="Resources" - /> + if (loading) return ; - - } - > - + {instances.length === 0 ? ( + - - - - - crud.setDeleteError(null)} - /> - + ) : ( + + title={`Resources (${filtered.length})`} + data={paginated} + columns={columns} + totalCount={filtered.length} + page={page} + pageSize={pageSize} + setPage={setPage} + setPageSize={setPageSize} + search={search} + setSearch={setSearch} + /> + )} + ); } diff --git a/workspaces/dcm/plugins/dcm/src/pages/resources/components/ResourceFormFields.tsx b/workspaces/dcm/plugins/dcm/src/pages/resources/components/ResourceFormFields.tsx deleted file mode 100644 index 8ad32f609c9..00000000000 --- a/workspaces/dcm/plugins/dcm/src/pages/resources/components/ResourceFormFields.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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, useRef } from 'react'; -import { - Box, - Button, - FormControl, - FormHelperText, - InputLabel, - MenuItem, - OutlinedInput, - Select, - TextField, - Tooltip, -} from '@material-ui/core'; -import CloudUploadIcon from '@material-ui/icons/CloudUpload'; -import { makeStyles } from '@material-ui/core/styles'; - -const useStyles = makeStyles(() => ({ - hiddenInput: { - display: 'none', - }, - monoInput: { - fontFamily: 'monospace', - fontSize: 13, - }, -})); -import type { CatalogItemInstance } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; -import { ResourceForm, validateResourceForm } from '../resourceFormTypes'; - -type TouchedMap = Partial>; - -export type ResourceFormFieldsProps = Readonly<{ - form: ResourceForm; - setForm: React.Dispatch>; - touched: TouchedMap; - setTouched: React.Dispatch>; - instances: CatalogItemInstance[]; -}>; - -export function ResourceFormFields({ - form, - setForm, - touched, - setTouched, - instances, -}: ResourceFormFieldsProps) { - const classes = useStyles(); - const fileInputRef = useRef(null); - const errors = useMemo(() => validateResourceForm(form), [form]); - - const set = - (field: keyof ResourceForm) => - (e: React.ChangeEvent) => { - setForm(prev => ({ ...prev, [field]: e.target.value })); - setTouched(prev => ({ ...prev, [field]: true })); - }; - - const handleFileUpload = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - e.target.value = ''; - const text = await file.text(); - try { - const parsed = JSON.parse(text); - setForm(prev => ({ ...prev, spec: JSON.stringify(parsed, null, 2) })); - } catch { - setForm(prev => ({ ...prev, spec: text })); - } - setTouched(prev => ({ ...prev, spec: true })); - }; - - return ( - - - Catalog item instance * - - {touched.catalog_item_instance_id && - errors.catalog_item_instance_id && ( - {errors.catalog_item_instance_id} - )} - - - setTouched(prev => ({ ...prev, spec: true }))} - fullWidth - variant="outlined" - multiline - minRows={6} - placeholder={'{\n "cpu": 2,\n "memory": "4Gi"\n}'} - inputProps={{ className: classes.monoInput }} - /> - - - - - - setTouched(prev => ({ ...prev, id: true }))} - fullWidth - variant="outlined" - size="small" - placeholder="e.g. my-resource-01" - /> - - ); -} diff --git a/workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.test.ts b/workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.test.ts deleted file mode 100644 index 3acece96ad5..00000000000 --- a/workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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 { - emptyResourceForm, - formToResource, - isResourceFormValid, - validateResourceForm, -} from './resourceFormTypes'; - -describe('validateResourceForm', () => { - const valid = () => ({ - ...emptyResourceForm(), - catalog_item_instance_id: 'inst-1', - spec: '{"cpu":2}', - }); - - it('returns no errors for a valid form', () => { - expect(validateResourceForm(valid())).toEqual({}); - }); - - it('requires catalog_item_instance_id', () => { - const errors = validateResourceForm({ - ...valid(), - catalog_item_instance_id: '', - }); - expect(errors.catalog_item_instance_id).toBeDefined(); - }); - - it('requires spec to be valid non-empty JSON', () => { - expect(validateResourceForm({ ...valid(), spec: '{}' }).spec).toBeDefined(); - expect( - validateResourceForm({ ...valid(), spec: 'not-json' }).spec, - ).toBeDefined(); - expect( - validateResourceForm({ ...valid(), spec: '{"a":1}' }).spec, - ).toBeUndefined(); - }); - - it('validates the optional id pattern', () => { - expect( - validateResourceForm({ ...valid(), id: 'Valid-ID' }).id, - ).toBeDefined(); - expect( - validateResourceForm({ ...valid(), id: 'valid-id' }).id, - ).toBeUndefined(); - expect(validateResourceForm({ ...valid(), id: '' }).id).toBeUndefined(); - }); -}); - -describe('isResourceFormValid', () => { - it('returns true for a valid form', () => { - expect( - isResourceFormValid({ - catalog_item_instance_id: 'inst-1', - spec: '{"key":"value"}', - id: '', - }), - ).toBe(true); - }); -}); - -describe('formToResource', () => { - it('parses the spec JSON string into an object', () => { - const resource = formToResource({ - catalog_item_instance_id: 'inst-1', - spec: '{"cpu":4,"memory":"8Gi"}', - id: '', - }); - expect(resource.spec).toEqual({ cpu: 4, memory: '8Gi' }); - expect(resource.catalog_item_instance_id).toBe('inst-1'); - }); -}); diff --git a/workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.ts b/workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.ts deleted file mode 100644 index 89a27b6212b..00000000000 --- a/workspaces/dcm/plugins/dcm/src/pages/resources/resourceFormTypes.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Red Hat, Inc. - * - * 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 * as yup from 'yup'; -import type { Resource } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; -import { createYupValidator } from '../../utils/createYupValidator'; - -export type ResourceForm = { - catalog_item_instance_id: string; - spec: string; - /** Optional client-assigned ID (sent as a query parameter). */ - id: string; -}; - -const ID_PATTERN = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/; - -const resourceSchema = yup.object({ - catalog_item_instance_id: yup - .string() - .required('Catalog item instance is required'), - spec: yup - .string() - .required('Spec is required') - .test( - 'valid-json', - 'Must be valid JSON with at least one property', - val => { - try { - const parsed = JSON.parse(val ?? ''); - return ( - typeof parsed === 'object' && - parsed !== null && - Object.keys(parsed).length > 0 - ); - } catch { - return false; - } - }, - ), - id: yup - .string() - .optional() - .test( - 'id-pattern', - 'Must be 1\u201363 lowercase alphanumeric characters or hyphens, not starting or ending with a hyphen', - val => !val || ID_PATTERN.test(val), - ), -}); - -export const { validate: validateResourceForm, isValid: isResourceFormValid } = - createYupValidator(resourceSchema); - -export function emptyResourceForm(): ResourceForm { - return { catalog_item_instance_id: '', spec: '{}', id: '' }; -} - -export function formToResource(f: ResourceForm): Resource { - return { - catalog_item_instance_id: f.catalog_item_instance_id, - spec: JSON.parse(f.spec) as Record, - }; -} diff --git a/workspaces/dcm/plugins/dcm/src/pages/service-spec-details/components/SpecEntitiesTab.tsx b/workspaces/dcm/plugins/dcm/src/pages/service-spec-details/components/SpecEntitiesTab.tsx index 6eafb2be3dc..c1480c9861c 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/service-spec-details/components/SpecEntitiesTab.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/service-spec-details/components/SpecEntitiesTab.tsx @@ -14,94 +14,24 @@ * limitations under the License. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { InfoCard, Table } from '@backstage/core-components'; -import { Box } from '@material-ui/core'; +import { useMemo } from 'react'; +import { DcmEntitiesCard } from '../../../components/DcmEntitiesTable'; import { - DCM_ENTITY_TABLE_COLUMNS, - DcmTableFilterField, - useDcmEntityListState, -} from '../../../components/DcmEntitiesTable'; -import type { - ServiceSpec, - ServiceSpecEntityRow, + MOCK_SERVICE_SPEC_ENTITIES, + type ServiceSpec, } from '../../../data/service-specs'; -import { MOCK_SERVICE_SPEC_ENTITIES } from '../../../data/service-specs'; -import { useDcmStyles } from '../../../components/dcmStyles'; export function SpecEntitiesTab(props: Readonly<{ spec: ServiceSpec }>) { const { spec } = props; - const classes = useDcmStyles(); - const allEntitiesForSpec = useMemo( () => MOCK_SERVICE_SPEC_ENTITIES.filter(e => e.specId === spec.id), [spec.id], ); - - const { filter, setFilter, filteredEntities, entityRowCount } = - useDcmEntityListState(allEntitiesForSpec); - - const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); - - useEffect(() => { - setPage(0); - }, [filter, spec.id]); - - const paginatedEntities = useMemo(() => { - const start = page * pageSize; - return filteredEntities.slice(start, start + pageSize); - }, [filteredEntities, page, pageSize]); - - const handlePageChange = useCallback( - (newPage: number, newPageSize: number) => { - setPage(newPage); - setPageSize(newPageSize); - }, - [], - ); - - const handleRowsPerPageChange = useCallback((newPageSize: number) => { - setPageSize(newPageSize); - setPage(0); - }, []); - return ( - - } - className={classes.tableCard} - > - - - data={paginatedEntities} - columns={DCM_ENTITY_TABLE_COLUMNS} - options={{ - paging: true, - pageSize, - pageSizeOptions: [5, 10, 25, 50], - search: false, - sorting: true, - padding: 'default', - toolbar: false, - }} - totalCount={filteredEntities.length} - page={page} - onPageChange={handlePageChange} - onRowsPerPageChange={handleRowsPerPageChange} - localization={{ - pagination: { - labelRowsPerPage: 'rows', - }, - }} - /> - - + ); } diff --git a/workspaces/dcm/plugins/dcm/src/pages/service-spec/ServiceSpecsTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/service-spec/ServiceSpecsTabContent.tsx index 6914a872dac..5e3a441c557 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/service-spec/ServiceSpecsTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/service-spec/ServiceSpecsTabContent.tsx @@ -16,6 +16,7 @@ // Migrate to use useCrudTab + DcmCrudTabLayout when it is re-activated. import { useMemo, useState, useCallback, useEffect } from 'react'; +import { usePersistedPageSize } from '../../hooks/usePersistedPageSize'; import { Table, TableColumn, @@ -176,7 +177,7 @@ export const ServiceSpecsTabContent = () => { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = usePersistedPageSize('service-specs'); const [createModalOpen, setCreateModalOpen] = useState(false); useEffect(() => { @@ -322,13 +323,16 @@ export const ServiceSpecsTabContent = () => { setPage(newPage); setPageSize(newPageSize); }, - [], + [setPageSize], ); - const handleRowsPerPageChange = useCallback((newPageSize: number) => { - setPageSize(newPageSize); - setPage(0); - }, []); + const handleRowsPerPageChange = useCallback( + (newPageSize: number) => { + setPageSize(newPageSize); + setPage(0); + }, + [setPageSize], + ); const { underlineLink } = classes; @@ -470,6 +474,7 @@ export const ServiceSpecsTabContent = () => { sorting: true, padding: 'default', toolbar: false, + emptyRowsWhenPaging: false, }} totalCount={filteredSpecs.length} page={page} diff --git a/workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx index 217903734cb..af53f548053 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/service-types/ServiceTypesTabContent.tsx @@ -15,17 +15,13 @@ */ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { - Table, - TableColumn, - InfoCard, - Progress, -} from '@backstage/core-components'; +import { usePersistedPageSize } from '../../hooks/usePersistedPageSize'; +import { TableColumn, Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { Box, Chip, Typography } from '@material-ui/core'; import type { ServiceType } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import { catalogApiRef } from '../../apis'; -import { DcmSearchCardAction } from '../../components/dcmTabListHelpers'; +import { DcmSearchTableCard } from '../../components/dcmTabListHelpers'; import { useDcmStyles } from '../../components/dcmStyles'; import emptyIllustration from '../../assets/environments-empty-state.png'; import { DcmDataCenterTabEmptyState } from '../../components/DcmDataCenterTabEmptyState'; @@ -41,7 +37,7 @@ export function ServiceTypesTabContent() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(5); + const [pageSize, setPageSize] = usePersistedPageSize('service-types'); const load = useCallback(() => { setLoading(true); @@ -153,45 +149,18 @@ export function ServiceTypesTabContent() { illustrationSrc={emptyIllustration} /> ) : ( - title={`Service types (${filtered.length})`} - action={ - - } - className={classes.dataCard} - titleTypographyProps={{ className: classes.cardTitle }} - > - - - data={paginated} - columns={columns} - options={{ - paging: true, - pageSize, - pageSizeOptions: [5, 10, 25], - search: false, - sorting: true, - padding: 'default', - toolbar: false, - }} - totalCount={filtered.length} - page={page} - onPageChange={(p, ps) => { - setPage(p); - setPageSize(ps); - }} - onRowsPerPageChange={ps => { - setPageSize(ps); - setPage(0); - }} - localization={{ pagination: { labelRowsPerPage: 'rows' } }} - /> - - + data={paginated} + columns={columns} + totalCount={filtered.length} + page={page} + pageSize={pageSize} + setPage={setPage} + setPageSize={setPageSize} + search={search} + setSearch={setSearch} + /> )} ); diff --git a/workspaces/dcm/plugins/dcm/src/plugin.ts b/workspaces/dcm/plugins/dcm/src/plugin.ts index e7bdc923921..56d913bf467 100644 --- a/workspaces/dcm/plugins/dcm/src/plugin.ts +++ b/workspaces/dcm/plugins/dcm/src/plugin.ts @@ -22,9 +22,9 @@ import { } from '@backstage/core-plugin-api'; import { CatalogClient, - PlacementClient, PolicyManagerClient, ProvidersClient, + ResourcesClient, } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import { @@ -41,9 +41,9 @@ import { } from './routes'; import { catalogApiRef, - placementApiRef, policyManagerApiRef, providersApiRef, + resourcesApiRef, } from './apis'; /** @@ -88,10 +88,10 @@ export const dcmPlugin = createPlugin({ }, }), createApiFactory({ - api: placementApiRef, + api: resourcesApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, factory({ discoveryApi, fetchApi }) { - return new PlacementClient({ discoveryApi, fetchApi }); + return new ResourcesClient({ discoveryApi, fetchApi }); }, }), ], diff --git a/workspaces/dcm/yarn.lock b/workspaces/dcm/yarn.lock index e3f339e55bf..344ff9b3779 100644 --- a/workspaces/dcm/yarn.lock +++ b/workspaces/dcm/yarn.lock @@ -1526,7 +1526,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.28.6 resolution: "@babel/runtime@npm:7.28.6" checksum: 10/fbcd439cb74d4a681958eb064c509829e3f46d8a4bfaaf441baa81bb6733d1e680bccc676c813883d7741bcaada1d0d04b15aa320ef280b5734e2192b50decf9 @@ -1617,38 +1617,6 @@ __metadata: languageName: node linkType: hard -"@backstage-community/plugin-rbac@npm:1.33.2": - version: 1.33.2 - resolution: "@backstage-community/plugin-rbac@npm:1.33.2" - dependencies: - "@backstage-community/plugin-rbac-common": "npm:^1.12.2" - "@backstage/catalog-model": "npm:^1.7.0" - "@backstage/core-components": "npm:^0.15.1" - "@backstage/core-plugin-api": "npm:^1.10.0" - "@backstage/plugin-catalog": "npm:^1.24.0" - "@backstage/plugin-catalog-common": "npm:^1.1.0" - "@backstage/plugin-permission-common": "npm:^0.8.1" - "@backstage/plugin-permission-react": "npm:^0.4.27" - "@backstage/theme": "npm:^0.6.0" - "@janus-idp/shared-react": "npm:^2.13.1" - "@mui/icons-material": "npm:5.16.4" - "@mui/material": "npm:^5.14.18" - "@mui/styles": "npm:^6.1.7" - "@rjsf/core": "npm:^5.21.2" - "@rjsf/mui": "npm:^5.21.2" - "@rjsf/utils": "npm:^5.21.2" - "@rjsf/validator-ajv8": "npm:^5.21.2" - autosuggest-highlight: "npm:^3.3.4" - formik: "npm:^2.4.5" - react-use: "npm:^17.4.0" - yup: "npm:^1.3.2" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: ^6.0.0 - checksum: 10/a5cede17430dc9f8160c08f3230e1112e26c50b8698e75a273741be81e504c3e9a375a5100860874d30cf59127b024626f0ec6033d72e7922ba7806f6b68a831 - languageName: node - linkType: hard - "@backstage/app-defaults@npm:^1.5.16, @backstage/app-defaults@npm:^1.7.4": version: 1.7.4 resolution: "@backstage/app-defaults@npm:1.7.4" @@ -2505,112 +2473,6 @@ __metadata: languageName: node linkType: hard -"@backstage/core-components@npm:^0.15.1": - version: 0.15.1 - resolution: "@backstage/core-components@npm:0.15.1" - dependencies: - "@backstage/config": "npm:^1.2.0" - "@backstage/core-plugin-api": "npm:^1.10.0" - "@backstage/errors": "npm:^1.2.4" - "@backstage/theme": "npm:^0.6.0" - "@backstage/version-bridge": "npm:^1.0.10" - "@date-io/core": "npm:^1.3.13" - "@material-table/core": "npm:^3.1.0" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - "@material-ui/lab": "npm:4.0.0-alpha.61" - "@react-hookz/web": "npm:^24.0.0" - "@types/react-sparklines": "npm:^1.7.0" - ansi-regex: "npm:^6.0.1" - classnames: "npm:^2.2.6" - d3-selection: "npm:^3.0.0" - d3-shape: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - dagre: "npm:^0.8.5" - linkify-react: "npm:4.1.3" - linkifyjs: "npm:4.1.3" - lodash: "npm:^4.17.21" - pluralize: "npm:^8.0.0" - qs: "npm:^6.9.4" - rc-progress: "npm:3.5.1" - react-helmet: "npm:6.1.0" - react-hook-form: "npm:^7.12.2" - react-idle-timer: "npm:5.7.2" - react-markdown: "npm:^8.0.0" - react-sparklines: "npm:^1.7.0" - react-syntax-highlighter: "npm:^15.4.5" - react-use: "npm:^17.3.2" - react-virtualized-auto-sizer: "npm:^1.0.11" - react-window: "npm:^1.8.6" - remark-gfm: "npm:^3.0.1" - zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" - peerDependencies: - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/48c765a7219255b4f230af13b6ba3e78cf0b23f1642a8175b6fa2498b7655dec662ea356f92fbbd9d834b55f8923540f879c0529a0bff0772ebff676152a2275 - languageName: node - linkType: hard - -"@backstage/core-components@npm:^0.16.3": - version: 0.16.4 - resolution: "@backstage/core-components@npm:0.16.4" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-plugin-api": "npm:^1.10.4" - "@backstage/errors": "npm:^1.2.7" - "@backstage/theme": "npm:^0.6.4" - "@backstage/version-bridge": "npm:^1.0.11" - "@dagrejs/dagre": "npm:^1.1.4" - "@date-io/core": "npm:^1.3.13" - "@material-table/core": "npm:^3.1.0" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - "@material-ui/lab": "npm:4.0.0-alpha.61" - "@react-hookz/web": "npm:^24.0.0" - "@testing-library/react": "npm:^16.0.0" - "@types/react-sparklines": "npm:^1.7.0" - ansi-regex: "npm:^6.0.1" - classnames: "npm:^2.2.6" - d3-selection: "npm:^3.0.0" - d3-shape: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - js-yaml: "npm:^4.1.0" - linkify-react: "npm:4.1.3" - linkifyjs: "npm:4.1.3" - lodash: "npm:^4.17.21" - pluralize: "npm:^8.0.0" - qs: "npm:^6.9.4" - rc-progress: "npm:3.5.1" - react-helmet: "npm:6.1.0" - react-hook-form: "npm:^7.12.2" - react-idle-timer: "npm:5.7.2" - react-markdown: "npm:^8.0.0" - react-sparklines: "npm:^1.7.0" - react-syntax-highlighter: "npm:^15.4.5" - react-use: "npm:^17.3.2" - react-virtualized-auto-sizer: "npm:^1.0.11" - react-window: "npm:^1.8.6" - remark-gfm: "npm:^3.0.1" - zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/472f4a17edc740cec15041612068320114b0128d6917d6968db8b435c3f4c2f002be939978b29efb12ce32c3d660b28e9de051ac1104c14f4eae2b6129c5ab49 - languageName: node - linkType: hard - "@backstage/core-components@npm:^0.17.1, @backstage/core-components@npm:^0.17.4, @backstage/core-components@npm:^0.17.5": version: 0.17.5 resolution: "@backstage/core-components@npm:0.17.5" @@ -2723,7 +2585,7 @@ __metadata: languageName: node linkType: hard -"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.3, @backstage/core-plugin-api@npm:^1.10.4, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.10.9, @backstage/core-plugin-api@npm:^1.11.0, @backstage/core-plugin-api@npm:^1.11.1, @backstage/core-plugin-api@npm:^1.12.1, @backstage/core-plugin-api@npm:^1.12.2": +"@backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.10.9, @backstage/core-plugin-api@npm:^1.11.0, @backstage/core-plugin-api@npm:^1.11.1, @backstage/core-plugin-api@npm:^1.12.1, @backstage/core-plugin-api@npm:^1.12.2": version: 1.12.2 resolution: "@backstage/core-plugin-api@npm:1.12.2" dependencies: @@ -3699,7 +3561,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-catalog-common@npm:^1.1.0, @backstage/plugin-catalog-common@npm:^1.1.3, @backstage/plugin-catalog-common@npm:^1.1.5, @backstage/plugin-catalog-common@npm:^1.1.7": +"@backstage/plugin-catalog-common@npm:^1.1.3, @backstage/plugin-catalog-common@npm:^1.1.5, @backstage/plugin-catalog-common@npm:^1.1.7": version: 1.1.7 resolution: "@backstage/plugin-catalog-common@npm:1.1.7" dependencies: @@ -3839,7 +3701,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-catalog@npm:^1.24.0, @backstage/plugin-catalog@npm:^1.26.1, @backstage/plugin-catalog@npm:^1.31.3": +"@backstage/plugin-catalog@npm:^1.26.1, @backstage/plugin-catalog@npm:^1.31.3": version: 1.32.2 resolution: "@backstage/plugin-catalog@npm:1.32.2" dependencies: @@ -3921,58 +3783,6 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-kubernetes-common@npm:^0.9.2, @backstage/plugin-kubernetes-common@npm:^0.9.9": - version: 0.9.9 - resolution: "@backstage/plugin-kubernetes-common@npm:0.9.9" - dependencies: - "@backstage/catalog-model": "npm:^1.7.6" - "@backstage/plugin-permission-common": "npm:^0.9.3" - "@backstage/types": "npm:^1.2.2" - "@kubernetes/client-node": "npm:1.4.0" - kubernetes-models: "npm:^4.3.1" - lodash: "npm:^4.17.21" - luxon: "npm:^3.0.0" - checksum: 10/30590e6cba4bfe792a5c06d651b5112ca4b6ef7d60dc4b278c8ab9fc057a805c5ff83a25a716e389486fc9141b200ea3a73559d92b34f1475c8544a7e5b7e3a3 - languageName: node - linkType: hard - -"@backstage/plugin-kubernetes-react@npm:^0.5.3": - version: 0.5.15 - resolution: "@backstage/plugin-kubernetes-react@npm:0.5.15" - dependencies: - "@backstage/catalog-model": "npm:^1.7.6" - "@backstage/core-components": "npm:^0.18.5" - "@backstage/core-plugin-api": "npm:^1.12.1" - "@backstage/errors": "npm:^1.2.7" - "@backstage/plugin-kubernetes-common": "npm:^0.9.9" - "@backstage/types": "npm:^1.2.2" - "@kubernetes-models/apimachinery": "npm:^2.0.0" - "@kubernetes-models/base": "npm:^5.0.0" - "@kubernetes/client-node": "npm:1.4.0" - "@material-ui/core": "npm:^4.9.13" - "@material-ui/icons": "npm:^4.11.3" - "@material-ui/lab": "npm:^4.0.0-alpha.61" - "@xterm/addon-attach": "npm:^0.11.0" - "@xterm/addon-fit": "npm:^0.10.0" - "@xterm/xterm": "npm:^5.5.0" - cronstrue: "npm:^2.32.0" - js-yaml: "npm:^4.1.0" - kubernetes-models: "npm:^4.3.1" - lodash: "npm:^4.17.21" - luxon: "npm:^3.0.0" - react-use: "npm:^17.4.0" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - react-router-dom: ^6.3.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/aeda86a3f41bfd5e0d3a043e10b188e440078fe954e602e18b8a1d43b7da5168ab017545ce2de1217747ac7909ddce9f7acb27b243f4b00f7e5dfd24bc599c4b - languageName: node - linkType: hard - "@backstage/plugin-org@npm:^0.6.35": version: 0.6.48 resolution: "@backstage/plugin-org@npm:0.6.48" @@ -4110,7 +3920,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-react@npm:^0.4.27, @backstage/plugin-permission-react@npm:^0.4.30, @backstage/plugin-permission-react@npm:^0.4.36, @backstage/plugin-permission-react@npm:^0.4.39": +"@backstage/plugin-permission-react@npm:^0.4.30, @backstage/plugin-permission-react@npm:^0.4.36, @backstage/plugin-permission-react@npm:^0.4.39": version: 0.4.39 resolution: "@backstage/plugin-permission-react@npm:0.4.39" dependencies: @@ -5045,7 +4855,7 @@ __metadata: languageName: node linkType: hard -"@backstage/theme@npm:^0.6.0, @backstage/theme@npm:^0.6.4, @backstage/theme@npm:^0.6.8": +"@backstage/theme@npm:^0.6.8": version: 0.6.8 resolution: "@backstage/theme@npm:0.6.8" dependencies: @@ -5113,7 +4923,7 @@ __metadata: languageName: node linkType: hard -"@backstage/version-bridge@npm:^1.0.10, @backstage/version-bridge@npm:^1.0.11": +"@backstage/version-bridge@npm:^1.0.11": version: 1.0.11 resolution: "@backstage/version-bridge@npm:1.0.11" peerDependencies: @@ -7021,31 +6831,6 @@ __metadata: languageName: node linkType: hard -"@janus-idp/shared-react@npm:^2.13.1": - version: 2.18.0 - resolution: "@janus-idp/shared-react@npm:2.18.0" - dependencies: - "@backstage/catalog-model": "npm:^1.7.3" - "@backstage/core-components": "npm:^0.16.3" - "@backstage/core-plugin-api": "npm:^1.10.3" - "@backstage/plugin-kubernetes-common": "npm:^0.9.2" - "@backstage/plugin-kubernetes-react": "npm:^0.5.3" - "@kubernetes/client-node": "npm:1.0.0-rc7" - "@material-ui/core": "npm:^4.12.4" - "@mui/icons-material": "npm:5.15.17" - "@mui/material": "npm:^5.15.17" - classnames: "npm:^2.3.2" - date-fns: "npm:^2.30.0" - file-saver: "npm:^2.0.5" - lodash: "npm:^4.17.21" - mathjs: "npm:^11.11.2" - react-use: "npm:^17.5.0" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10/1bbf14ac27c14483ea237ba6a6fb9f0b3e6f872d6af019e1e48e12e365e9a423f4b44b1007aeffc20082315d5b1294d136eabc14e09966f4f46b31ee116ef998 - languageName: node - linkType: hard - "@jest/create-cache-key-function@npm:^30.0.0": version: 30.2.0 resolution: "@jest/create-cache-key-function@npm:30.2.0" @@ -7524,46 +7309,6 @@ __metadata: languageName: node linkType: hard -"@kubernetes-models/apimachinery@npm:^2.0.0, @kubernetes-models/apimachinery@npm:^2.2.0": - version: 2.2.0 - resolution: "@kubernetes-models/apimachinery@npm:2.2.0" - dependencies: - "@kubernetes-models/base": "npm:^5.0.1" - "@kubernetes-models/validate": "npm:^4.0.0" - "@swc/helpers": "npm:^0.5.8" - checksum: 10/b266c6e82f570a048404a29255722e825a9b06eda27f70bd86dc0538afe8cd90861ce600f57c32b34a3e4eefd97fb93def6ab1143aa34e29f447f77474ac1cc6 - languageName: node - linkType: hard - -"@kubernetes-models/base@npm:^5.0.0, @kubernetes-models/base@npm:^5.0.1": - version: 5.0.1 - resolution: "@kubernetes-models/base@npm:5.0.1" - dependencies: - "@kubernetes-models/validate": "npm:^4.0.0" - is-plain-object: "npm:^5.0.0" - tslib: "npm:^2.4.0" - checksum: 10/560de40c1c59cbfda18fac5191ebe096b0996b8cb6ec27898d8575b3249a9a7c1cd06950240b263b118ccdf54ee7ddffc4bb5936f0275a2e8fd6684d058eb345 - languageName: node - linkType: hard - -"@kubernetes-models/validate@npm:^4.0.0": - version: 4.0.0 - resolution: "@kubernetes-models/validate@npm:4.0.0" - dependencies: - ajv: "npm:^8.12.0" - ajv-formats: "npm:^2.1.1" - ajv-formats-draft2019: "npm:^1.6.1" - ajv-i18n: "npm:^4.2.0" - is-cidr: "npm:^4.0.0" - re2-wasm: "npm:^1.0.2" - tslib: "npm:^2.4.0" - dependenciesMeta: - re2-wasm: - optional: true - checksum: 10/948c86c1178710debd91756db9f92b3d1e5a706d1520d855b12bf9de378d8bab2e7c8f353ed1ec5271dff7175bb39202857216f0c047663e0809977bb34fde5d - languageName: node - linkType: hard - "@kubernetes/client-node@npm:0.20.0": version: 0.20.0 resolution: "@kubernetes/client-node@npm:0.20.0" @@ -7590,57 +7335,6 @@ __metadata: languageName: node linkType: hard -"@kubernetes/client-node@npm:1.0.0-rc7": - version: 1.0.0-rc7 - resolution: "@kubernetes/client-node@npm:1.0.0-rc7" - dependencies: - "@types/js-yaml": "npm:^4.0.1" - "@types/node": "npm:^22.0.0" - "@types/node-fetch": "npm:^2.6.9" - "@types/stream-buffers": "npm:^3.0.3" - "@types/tar": "npm:^6.1.1" - "@types/ws": "npm:^8.5.4" - form-data: "npm:^4.0.0" - isomorphic-ws: "npm:^5.0.0" - js-yaml: "npm:^4.1.0" - jsonpath-plus: "npm:^10.0.0" - node-fetch: "npm:^2.6.9" - openid-client: "npm:^5.6.5" - rfc4648: "npm:^1.3.0" - stream-buffers: "npm:^3.0.2" - tar: "npm:^7.0.0" - tmp-promise: "npm:^3.0.2" - tslib: "npm:^2.5.0" - url-parse: "npm:^1.4.3" - ws: "npm:^8.18.0" - checksum: 10/dad4432c97d24410f3af293b64e3e261d3807d2e23d5b2e022e34d59b0fddd1e26087900695b13917941b40dda02dc6af3e04f08ffb3a6a28b1b23a8dc7543bb - languageName: node - linkType: hard - -"@kubernetes/client-node@npm:1.4.0": - version: 1.4.0 - resolution: "@kubernetes/client-node@npm:1.4.0" - dependencies: - "@types/js-yaml": "npm:^4.0.1" - "@types/node": "npm:^24.0.0" - "@types/node-fetch": "npm:^2.6.13" - "@types/stream-buffers": "npm:^3.0.3" - form-data: "npm:^4.0.0" - hpagent: "npm:^1.2.0" - isomorphic-ws: "npm:^5.0.0" - js-yaml: "npm:^4.1.0" - jsonpath-plus: "npm:^10.3.0" - node-fetch: "npm:^2.7.0" - openid-client: "npm:^6.1.3" - rfc4648: "npm:^1.3.0" - socks-proxy-agent: "npm:^8.0.4" - stream-buffers: "npm:^3.0.2" - tar-fs: "npm:^3.0.9" - ws: "npm:^8.18.2" - checksum: 10/67237d327d44bc2b77dc68d44f03699e71ff85bc120b086565bab8446cf288a3da5132f7444bbfe971fbe1c1a544a1bc4c13bd7ffb7a745b3b810662017af8dd - languageName: node - linkType: hard - "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.5 resolution: "@leichtgewicht/ip-codec@npm:2.0.5" @@ -7765,7 +7459,7 @@ __metadata: languageName: node linkType: hard -"@material-ui/icons@npm:^4.11.3, @material-ui/icons@npm:^4.9.1": +"@material-ui/icons@npm:^4.9.1": version: 4.11.3 resolution: "@material-ui/icons@npm:4.11.3" dependencies: @@ -8339,38 +8033,6 @@ __metadata: languageName: node linkType: hard -"@mui/icons-material@npm:5.15.17": - version: 5.15.17 - resolution: "@mui/icons-material@npm:5.15.17" - dependencies: - "@babel/runtime": "npm:^7.23.9" - peerDependencies: - "@mui/material": ^5.0.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/fa7c33e1a47dbc85bf994e3b1c08dc0f504fd60a1b8bcee61c83c1b0fc126134eaff1b8acf894721c5ed8b4bbb48c4ba6b846e73a541314a72870990089c0426 - languageName: node - linkType: hard - -"@mui/icons-material@npm:5.16.4": - version: 5.16.4 - resolution: "@mui/icons-material@npm:5.16.4" - dependencies: - "@babel/runtime": "npm:^7.23.9" - peerDependencies: - "@mui/material": ^5.0.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/6f163af896ad0c60d3c5c539d8a68b407c6bd1203184129a6b72d4b911ff8bc9a92448cfb6aaf5a23f46a810224e172321dfa37089f07a4d04dded4a87546d8e - languageName: node - linkType: hard - "@mui/icons-material@npm:^5.17.1": version: 5.18.0 resolution: "@mui/icons-material@npm:5.18.0" @@ -8387,7 +8049,7 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.12.2, @mui/material@npm:^5.14.18, @mui/material@npm:^5.15.17": +"@mui/material@npm:^5.12.2": version: 5.18.0 resolution: "@mui/material@npm:5.18.0" dependencies: @@ -8437,23 +8099,6 @@ __metadata: languageName: node linkType: hard -"@mui/private-theming@npm:^6.4.9": - version: 6.4.9 - resolution: "@mui/private-theming@npm:6.4.9" - dependencies: - "@babel/runtime": "npm:^7.26.0" - "@mui/utils": "npm:^6.4.9" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/d2e6b24001e82cb3a0c6115a54610ba0ee0abea64befe7fb5567fb8753fdc40b86eb77d399b1c014b1b8425c250f7bb3ee32171bb538f19bda5ce7a0d897a102 - languageName: node - linkType: hard - "@mui/styled-engine@npm:^5.18.0": version: 5.18.0 resolution: "@mui/styled-engine@npm:5.18.0" @@ -8476,37 +8121,6 @@ __metadata: languageName: node linkType: hard -"@mui/styles@npm:^6.1.7": - version: 6.5.0 - resolution: "@mui/styles@npm:6.5.0" - dependencies: - "@babel/runtime": "npm:^7.26.0" - "@emotion/hash": "npm:^0.9.2" - "@mui/private-theming": "npm:^6.4.9" - "@mui/types": "npm:~7.2.24" - "@mui/utils": "npm:^6.4.9" - clsx: "npm:^2.1.1" - csstype: "npm:^3.1.3" - hoist-non-react-statics: "npm:^3.3.2" - jss: "npm:^10.10.0" - jss-plugin-camel-case: "npm:^10.10.0" - jss-plugin-default-unit: "npm:^10.10.0" - jss-plugin-global: "npm:^10.10.0" - jss-plugin-nested: "npm:^10.10.0" - jss-plugin-props-sort: "npm:^10.10.0" - jss-plugin-rule-value-function: "npm:^10.10.0" - jss-plugin-vendor-prefixer: "npm:^10.10.0" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/86fe34fa59498234e6b2801aae59c942410df9a7abbf502bc96d7a744d08cdbf5889654e04d9098a5ac87730ed9d532002ac78ec61c6c6ceed299ed77c1b692d - languageName: node - linkType: hard - "@mui/system@npm:^5.18.0": version: 5.18.0 resolution: "@mui/system@npm:5.18.0" @@ -8535,7 +8149,7 @@ __metadata: languageName: node linkType: hard -"@mui/types@npm:~7.2.15, @mui/types@npm:~7.2.24": +"@mui/types@npm:~7.2.15": version: 7.2.24 resolution: "@mui/types@npm:7.2.24" peerDependencies: @@ -8567,26 +8181,6 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^6.4.9": - version: 6.4.9 - resolution: "@mui/utils@npm:6.4.9" - dependencies: - "@babel/runtime": "npm:^7.26.0" - "@mui/types": "npm:~7.2.24" - "@types/prop-types": "npm:^15.7.14" - clsx: "npm:^2.1.1" - prop-types: "npm:^15.8.1" - react-is: "npm:^19.0.0" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/4769956cc79283f9813d2984cd92b647528e4456eaea582c60b599d2db0a132ae4a10bf03ad28e8241463cd0ec2df1d56c0c4958f7c39d998241f93fc91c3ad4 - languageName: node - linkType: hard - "@n1ru4l/push-pull-async-iterable-iterator@npm:^3.1.0": version: 3.2.0 resolution: "@n1ru4l/push-pull-async-iterable-iterator@npm:3.2.0" @@ -12096,7 +11690,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/core@npm:5.24.13, @rjsf/core@npm:^5.21.2": +"@rjsf/core@npm:5.24.13": version: 5.24.13 resolution: "@rjsf/core@npm:5.24.13" dependencies: @@ -12124,22 +11718,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/mui@npm:^5.21.2": - version: 5.24.13 - resolution: "@rjsf/mui@npm:5.24.13" - peerDependencies: - "@emotion/react": ^11.7.0 - "@emotion/styled": ^11.6.0 - "@mui/icons-material": ^5.2.0 || ^6.0.0 - "@mui/material": ^5.2.2 || ^6.0.0 - "@rjsf/core": ^5.24.x - "@rjsf/utils": ^5.24.x - react: ">=17" - checksum: 10/fedc93d4ce0bf2fe2d5b8ff3ef4857093d3d6fdb6983dd0728bbab9f7ddca9a28139c476aa948bae06a2f5d0d877bb75d56426c33ed2c6433a235e94f46d6a9a - languageName: node - linkType: hard - -"@rjsf/utils@npm:5.24.13, @rjsf/utils@npm:^5.21.2": +"@rjsf/utils@npm:5.24.13": version: 5.24.13 resolution: "@rjsf/utils@npm:5.24.13" dependencies: @@ -12154,7 +11733,7 @@ __metadata: languageName: node linkType: hard -"@rjsf/validator-ajv8@npm:5.24.13, @rjsf/validator-ajv8@npm:^5.21.2": +"@rjsf/validator-ajv8@npm:5.24.13": version: 5.24.13 resolution: "@rjsf/validator-ajv8@npm:5.24.13" dependencies: @@ -14447,7 +14026,7 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:^0.5.0, @swc/helpers@npm:^0.5.17, @swc/helpers@npm:^0.5.8": +"@swc/helpers@npm:^0.5.0, @swc/helpers@npm:^0.5.17": version: 0.5.18 resolution: "@swc/helpers@npm:0.5.18" dependencies: @@ -14959,7 +14538,7 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1": +"@types/hoist-non-react-statics@npm:^3.3.0": version: 3.3.7 resolution: "@types/hoist-non-react-statics@npm:3.3.7" dependencies: @@ -15156,16 +14735,6 @@ __metadata: languageName: node linkType: hard -"@types/node-fetch@npm:^2.6.13, @types/node-fetch@npm:^2.6.9": - version: 2.6.13 - resolution: "@types/node-fetch@npm:2.6.13" - dependencies: - "@types/node": "npm:*" - form-data: "npm:^4.0.4" - checksum: 10/944d52214791ebba482ca1393a4f0d62b0dbac5f7343ff42c128b75d5356d8bcefd4df77771b55c1acd19d118e16e9bd5d2792819c51bc13402d1c87c0975435 - languageName: node - linkType: hard - "@types/node-forge@npm:^1.3.0": version: 1.3.14 resolution: "@types/node-forge@npm:1.3.14" @@ -15209,24 +14778,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.0.0": - version: 22.19.11 - resolution: "@types/node@npm:22.19.11" - dependencies: - undici-types: "npm:~6.21.0" - checksum: 10/bb899551432ce2bf3e1a175d58802f25dfb6a96e574ce1f0056d55d0ce48513961976ee3621215565142a8ad99a2464d9c384fa53ccba0650def031d218800f7 - languageName: node - linkType: hard - -"@types/node@npm:^24.0.0": - version: 24.10.13 - resolution: "@types/node@npm:24.10.13" - dependencies: - undici-types: "npm:~7.16.0" - checksum: 10/c779d14cf17758a9d603e0c0978c8045bd070e259617ec16f4ddac419ec11aac0e0cf2bb013dab008fdbd2f46c39f82397884662bbdfabeb19bef0079679ea48 - languageName: node - linkType: hard - "@types/oauth@npm:*": version: 0.9.6 resolution: "@types/oauth@npm:0.9.6" @@ -15287,7 +14838,7 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.14, @types/prop-types@npm:^15.7.3": +"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.3": version: 15.7.15 resolution: "@types/prop-types@npm:15.7.15" checksum: 10/31aa2f59b28f24da6fb4f1d70807dae2aedfce090ec63eaf9ea01727a9533ef6eaf017de5bff99fbccad7d1c9e644f52c6c2ba30869465dd22b1a7221c29f356 @@ -15517,15 +15068,6 @@ __metadata: languageName: node linkType: hard -"@types/stream-buffers@npm:^3.0.3": - version: 3.0.8 - resolution: "@types/stream-buffers@npm:3.0.8" - dependencies: - "@types/node": "npm:*" - checksum: 10/2e269491769f3c529236cdd743a505d1dca52d14b3672714730d8940000d948bdf9468b2e54609b9d591d31e51e8e043eb44a830a6189bc727bd9dfc4dd70cdf - languageName: node - linkType: hard - "@types/styled-jsx@npm:^2.2.8": version: 2.2.9 resolution: "@types/styled-jsx@npm:2.2.9" @@ -15556,16 +15098,6 @@ __metadata: languageName: node linkType: hard -"@types/tar@npm:^6.1.1": - version: 6.1.13 - resolution: "@types/tar@npm:6.1.13" - dependencies: - "@types/node": "npm:*" - minipass: "npm:^4.0.0" - checksum: 10/d325223cf90399fd03f366d0eabe2383e75e550b3e40a006d5f062d006b894a475cd7c0968d258a8eb8eae5df30b6e7f4607d493a474f89134bbff65362b77ed - languageName: node - linkType: hard - "@types/tern@npm:*": version: 0.23.9 resolution: "@types/tern@npm:0.23.9" @@ -15631,7 +15163,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.3, @types/ws@npm:^8.5.4": +"@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.3": version: 8.18.1 resolution: "@types/ws@npm:8.18.1" dependencies: @@ -16036,31 +15568,6 @@ __metadata: languageName: node linkType: hard -"@xterm/addon-attach@npm:^0.11.0": - version: 0.11.0 - resolution: "@xterm/addon-attach@npm:0.11.0" - peerDependencies: - "@xterm/xterm": ^5.0.0 - checksum: 10/f11e7ba68b8b1064ce545ecbf2beeaf3ebc94622fa3ca77e0ca0b8e8834964f634ee3897c031376abc7242ddfd305a40b10aa7b3e8056bfa59055967251523b5 - languageName: node - linkType: hard - -"@xterm/addon-fit@npm:^0.10.0": - version: 0.10.0 - resolution: "@xterm/addon-fit@npm:0.10.0" - peerDependencies: - "@xterm/xterm": ^5.0.0 - checksum: 10/8edfad561c0d0316c5883cbe2ce56109f105a2b2bf53b71d5f8c788e656a3205c1093a659dddcf4025a459e4b7ff8e07b6c6a19815c8711deeded560de5f1893 - languageName: node - linkType: hard - -"@xterm/xterm@npm:^5.5.0": - version: 5.5.0 - resolution: "@xterm/xterm@npm:5.5.0" - checksum: 10/d4cdc402de81a83a3e0ef93f38072cb8f54abe4d65865f2e29b92cbc2593f86d052f6b993895c9e5dec97f47548f504e90bcea0aad6845917c09b03f2f3a4629 - languageName: node - linkType: hard - "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" @@ -16222,20 +15729,6 @@ __metadata: languageName: node linkType: hard -"ajv-formats-draft2019@npm:^1.6.1": - version: 1.6.1 - resolution: "ajv-formats-draft2019@npm:1.6.1" - dependencies: - punycode: "npm:^2.1.1" - schemes: "npm:^1.4.0" - smtp-address-parser: "npm:^1.0.3" - uri-js: "npm:^4.4.1" - peerDependencies: - ajv: "*" - checksum: 10/ecc9920a06eee23331873f4554e576d3593d2a1c9f42843571c6362858380f394419ec7a7a83af50fabf3913c43408f998d7261d601134d65ad6961165449a9f - languageName: node - linkType: hard - "ajv-formats@npm:^2.1.1, ajv-formats@npm:~2.1.0, ajv-formats@npm:~2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" @@ -16264,15 +15757,6 @@ __metadata: languageName: node linkType: hard -"ajv-i18n@npm:^4.2.0": - version: 4.2.0 - resolution: "ajv-i18n@npm:4.2.0" - peerDependencies: - ajv: ^8.0.0-beta.0 - checksum: 10/c9e17fd87a6143583b415f0bb6e9c5d5cb4c013434bcee6ab1b5f6403a5090da78592e2cf2b21b1c224182402adbe2168ff08933c21d0bd6334085bd79bb01c9 - languageName: node - linkType: hard - "ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" @@ -16461,7 +15945,6 @@ __metadata: version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: - "@backstage-community/plugin-rbac": "npm:1.33.2" "@backstage/app-defaults": "npm:^1.5.16" "@backstage/catalog-model": "npm:^1.7.3" "@backstage/cli": "npm:^0.35.2" @@ -16913,15 +16396,6 @@ __metadata: languageName: node linkType: hard -"autosuggest-highlight@npm:^3.3.4": - version: 3.3.4 - resolution: "autosuggest-highlight@npm:3.3.4" - dependencies: - remove-accents: "npm:^0.4.2" - checksum: 10/4808b84f63baac7709e93fb2f73ed5b6638aa25af18ebc6236b4b11db9460c36a67d316270720d58d06a5f8fdf8c19b9308132263e305ddeb705a434562c5533 - languageName: node - linkType: hard - "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -18038,15 +17512,6 @@ __metadata: languageName: node linkType: hard -"cidr-regex@npm:^3.1.1": - version: 3.1.1 - resolution: "cidr-regex@npm:3.1.1" - dependencies: - ip-regex: "npm:^4.1.0" - checksum: 10/ef9306d086928ee82b3f841b3bdab6e072230f3623a57cf19e06174946f2cbfeb70ca52bc106b127db27a628b9e84fb39284f5851003898ffdb957fe330478ee - languageName: node - linkType: hard - "cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": version: 1.0.7 resolution: "cipher-base@npm:1.0.7" @@ -18058,7 +17523,7 @@ __metadata: languageName: node linkType: hard -"classnames@npm:^2.2.6, classnames@npm:^2.3.1, classnames@npm:^2.3.2, classnames@npm:^2.5.1": +"classnames@npm:^2.2.6, classnames@npm:^2.3.1, classnames@npm:^2.5.1": version: 2.5.1 resolution: "classnames@npm:2.5.1" checksum: 10/58eb394e8817021b153bb6e7d782cfb667e4ab390cb2e9dac2fc7c6b979d1cc2b2a733093955fc5c94aa79ef5c8c89f11ab77780894509be6afbb91dddd79d15 @@ -18441,13 +17906,6 @@ __metadata: languageName: node linkType: hard -"complex.js@npm:^2.1.1": - version: 2.4.3 - resolution: "complex.js@npm:2.4.3" - checksum: 10/904a2b4a09a4cfd94d8636ceb95e15cc077dcdedd07c54e233308210fb38897338dde4e7113811e89c1cfe4c6e3ebcf11735ad5c901e27c6d7e3c132a3078e3c - languageName: node - linkType: hard - "component-emitter@npm:^1.3.0": version: 1.3.1 resolution: "component-emitter@npm:1.3.1" @@ -18942,15 +18400,6 @@ __metadata: languageName: node linkType: hard -"cronstrue@npm:^2.32.0": - version: 2.59.0 - resolution: "cronstrue@npm:2.59.0" - bin: - cronstrue: bin/cli.js - checksum: 10/daa7a1ea7e4a40cdb533df754489142358ecdc23462ec57309fec61e539b84ae886d3ac4666a6133403f1b13a98834ca107a09f5f78d31e6cfac8625dd3422c3 - languageName: node - linkType: hard - "cross-env@npm:^7.0.0": version: 7.0.3 resolution: "cross-env@npm:7.0.3" @@ -19346,16 +18795,6 @@ __metadata: languageName: node linkType: hard -"dagre@npm:^0.8.5": - version: 0.8.5 - resolution: "dagre@npm:0.8.5" - dependencies: - graphlib: "npm:^2.1.8" - lodash: "npm:^4.17.15" - checksum: 10/f39899e29e9090581d67177ef6e2dd3ca5d7f764fbf3de81758d879bba66fee6fd8802d41d0c5d3d9a0563b334e99e1454a8d6ab4ce17e8e4f50836a3a403fdd - languageName: node - linkType: hard - "damerau-levenshtein@npm:^1.0.8": version: 1.0.8 resolution: "damerau-levenshtein@npm:1.0.8" @@ -19436,7 +18875,7 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.16.1, date-fns@npm:^2.30.0": +"date-fns@npm:^2.16.1": version: 2.30.0 resolution: "date-fns@npm:2.30.0" dependencies: @@ -19606,13 +19045,6 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^2.1.1": - version: 2.2.1 - resolution: "deepmerge@npm:2.2.1" - checksum: 10/a3da411cd3d471a8ae86ff7fd5e19abb648377b3f8c42a9e4c822406c2960a391cb829e4cca53819b73715e68f56b06f53c643ca7bba21cab569fecc9a723de1 - languageName: node - linkType: hard - "deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1, deepmerge@npm:~4.3.0": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" @@ -20639,13 +20071,6 @@ __metadata: languageName: node linkType: hard -"escape-latex@npm:^1.2.0": - version: 1.2.0 - resolution: "escape-latex@npm:1.2.0" - checksum: 10/73a787319f0965ecb8244bb38bf3a3cba872f0b9a5d3da8821140e9f39fe977045dc953a62b1a2bed4d12bfccbe75a7d8ec786412bf00739eaa2f627d0a8e0d6 - languageName: node - linkType: hard - "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -21487,13 +20912,6 @@ __metadata: languageName: node linkType: hard -"file-saver@npm:^2.0.5": - version: 2.0.5 - resolution: "file-saver@npm:2.0.5" - checksum: 10/fbba443d9b682fec0be6676c048a7ac688b9bd33b105c02e8e1a1cb3e354ed441198dc1f15dcbc137fa044bea73f8a7549f2617e3a952fa7d96390356d54a7c3 - languageName: node - linkType: hard - "file-type@npm:21.3.0": version: 21.3.0 resolution: "file-type@npm:21.3.0" @@ -21751,7 +21169,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:^4.0.4, form-data@npm:^4.0.5": +"form-data@npm:^4.0.0, form-data@npm:^4.0.5": version: 4.0.5 resolution: "form-data@npm:4.0.5" dependencies: @@ -21824,24 +21242,6 @@ __metadata: languageName: node linkType: hard -"formik@npm:^2.4.5": - version: 2.4.9 - resolution: "formik@npm:2.4.9" - dependencies: - "@types/hoist-non-react-statics": "npm:^3.3.1" - deepmerge: "npm:^2.1.1" - hoist-non-react-statics: "npm:^3.3.0" - lodash: "npm:^4.17.21" - lodash-es: "npm:^4.17.21" - react-fast-compare: "npm:^2.0.1" - tiny-warning: "npm:^1.0.2" - tslib: "npm:^2.0.0" - peerDependencies: - react: ">=16.8.0" - checksum: 10/c7354bedce92ac13a8e8a59fa9bd05fe1743e0d10becf12a574ab98906a234eeb483ae9cb2a84956bd6b77aed3006e37c85203981e15547889f1a5862c5b06d0 - languageName: node - linkType: hard - "formstream@npm:^1.1.1": version: 1.5.2 resolution: "formstream@npm:1.5.2" @@ -21861,13 +21261,6 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:4.3.4": - version: 4.3.4 - resolution: "fraction.js@npm:4.3.4" - checksum: 10/3a1e6b268038ffdea625fab6a8d155d7ab644d35d0c99bc59084bfd29fbc714f3a38381b0627751ddb5f188bcde0b3f48c27e80eeb2ecd440825a7d2cd2bf9f1 - languageName: node - linkType: hard - "framer-motion@npm:^6.5.1": version: 6.5.1 resolution: "framer-motion@npm:6.5.1" @@ -22542,15 +21935,6 @@ __metadata: languageName: node linkType: hard -"graphlib@npm:^2.1.8": - version: 2.1.8 - resolution: "graphlib@npm:2.1.8" - dependencies: - lodash: "npm:^4.17.15" - checksum: 10/37cbd851d3c1fb99f3174750ccaa22305d23d11746e5df81a38ba3bf25c0ba29cd9658ba69a0159ea81d56c28e8e875033eeaaa7167d838419fae08d9cd2c62c - languageName: node - linkType: hard - "graphql-config@npm:^5.0.2": version: 5.1.5 resolution: "graphql-config@npm:5.1.5" @@ -23689,13 +23073,6 @@ __metadata: languageName: node linkType: hard -"ip-regex@npm:^4.1.0": - version: 4.3.0 - resolution: "ip-regex@npm:4.3.0" - checksum: 10/7ff904b891221b1847f3fdf3dbb3e6a8660dc39bc283f79eb7ed88f5338e1a3d1104b779bc83759159be266249c59c2160e779ee39446d79d4ed0890dfd06f08 - languageName: node - linkType: hard - "ipaddr.js@npm:1.9.1": version: 1.9.1 resolution: "ipaddr.js@npm:1.9.1" @@ -23827,15 +23204,6 @@ __metadata: languageName: node linkType: hard -"is-cidr@npm:^4.0.0": - version: 4.0.2 - resolution: "is-cidr@npm:4.0.2" - dependencies: - cidr-regex: "npm:^3.1.1" - checksum: 10/ee6e670e655a835710a7fa15268b428adbf80267114a494ce1c2ca2b09e1ca0b629fe1375aae621d4c093b32930d5ff7c4ee6da97eae14e3836bc7b3a07b171f - languageName: node - linkType: hard - "is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" @@ -24477,13 +23845,6 @@ __metadata: languageName: node linkType: hard -"javascript-natural-sort@npm:^0.7.1": - version: 0.7.1 - resolution: "javascript-natural-sort@npm:0.7.1" - checksum: 10/7bf6eab67871865d347f09a95aa770f9206c1ab0226bcda6fdd9edec340bf41111a7f82abac30556aa16a21cfa3b2b1ca4a362c8b73dd5ce15220e5d31f49d79 - languageName: node - linkType: hard - "jest-css-modules@npm:^2.1.0": version: 2.1.0 resolution: "jest-css-modules@npm:2.1.0" @@ -24611,13 +23972,6 @@ __metadata: languageName: node linkType: hard -"jose@npm:^6.1.3": - version: 6.1.3 - resolution: "jose@npm:6.1.3" - checksum: 10/9626c51e8c3792b505e954f3094698c182208617b62dfb27269230f31e57560b083985ed8128b8a9753aa92daf18d3a2341cc826d149503f14569abe87d42389 - languageName: node - linkType: hard - "js-base64@npm:^3.6.0": version: 3.7.8 resolution: "js-base64@npm:3.7.8" @@ -24945,7 +24299,7 @@ __metadata: languageName: node linkType: hard -"jsonpath-plus@npm:^10.0.0, jsonpath-plus@npm:^10.0.7, jsonpath-plus@npm:^10.3.0, jsonpath-plus@npm:^6.0.1 || ^10.1.0": +"jsonpath-plus@npm:^10.0.7, jsonpath-plus@npm:^10.3.0, jsonpath-plus@npm:^6.0.1 || ^10.1.0": version: 10.4.0 resolution: "jsonpath-plus@npm:10.4.0" dependencies: @@ -25021,7 +24375,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-camel-case@npm:^10.10.0, jss-plugin-camel-case@npm:^10.5.1": +"jss-plugin-camel-case@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-camel-case@npm:10.10.0" dependencies: @@ -25032,7 +24386,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-default-unit@npm:^10.10.0, jss-plugin-default-unit@npm:^10.5.1": +"jss-plugin-default-unit@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-default-unit@npm:10.10.0" dependencies: @@ -25042,7 +24396,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-global@npm:^10.10.0, jss-plugin-global@npm:^10.5.1": +"jss-plugin-global@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-global@npm:10.10.0" dependencies: @@ -25052,7 +24406,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-nested@npm:^10.10.0, jss-plugin-nested@npm:^10.5.1": +"jss-plugin-nested@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-nested@npm:10.10.0" dependencies: @@ -25063,7 +24417,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-props-sort@npm:^10.10.0, jss-plugin-props-sort@npm:^10.5.1": +"jss-plugin-props-sort@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-props-sort@npm:10.10.0" dependencies: @@ -25073,7 +24427,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-rule-value-function@npm:^10.10.0, jss-plugin-rule-value-function@npm:^10.5.1": +"jss-plugin-rule-value-function@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-rule-value-function@npm:10.10.0" dependencies: @@ -25084,7 +24438,7 @@ __metadata: languageName: node linkType: hard -"jss-plugin-vendor-prefixer@npm:^10.10.0, jss-plugin-vendor-prefixer@npm:^10.5.1": +"jss-plugin-vendor-prefixer@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-vendor-prefixer@npm:10.10.0" dependencies: @@ -25095,7 +24449,7 @@ __metadata: languageName: node linkType: hard -"jss@npm:10.10.0, jss@npm:^10.10.0, jss@npm:^10.5.1, jss@npm:~10.10.0": +"jss@npm:10.10.0, jss@npm:^10.5.1, jss@npm:~10.10.0": version: 10.10.0 resolution: "jss@npm:10.10.0" dependencies: @@ -25330,18 +24684,6 @@ __metadata: languageName: node linkType: hard -"kubernetes-models@npm:^4.3.1": - version: 4.5.1 - resolution: "kubernetes-models@npm:4.5.1" - dependencies: - "@kubernetes-models/apimachinery": "npm:^2.2.0" - "@kubernetes-models/base": "npm:^5.0.1" - "@kubernetes-models/validate": "npm:^4.0.0" - "@swc/helpers": "npm:^0.5.8" - checksum: 10/65a7b2064dcfbc719a80da7fd24b44775f68423170eb7abef3d4946604acdb5c8a3b2c8fded1d7212e2725b10ccf13abc94adf3091dc8005e25cd5321577ae92 - languageName: node - linkType: hard - "kuler@npm:^2.0.0": version: 2.0.0 resolution: "kuler@npm:2.0.0" @@ -25465,16 +24807,6 @@ __metadata: languageName: node linkType: hard -"linkify-react@npm:4.1.3": - version: 4.1.3 - resolution: "linkify-react@npm:4.1.3" - peerDependencies: - linkifyjs: ^4.0.0 - react: ">= 15.0.0" - checksum: 10/e21dec51798783288c08979a74d0b8b6ee87315205d9a6ca36d8dc062c967577184791ab6456bb70f3635c2d156930040de9c278a3ca6dc940a45ea3b5d184c4 - languageName: node - linkType: hard - "linkify-react@npm:4.3.2": version: 4.3.2 resolution: "linkify-react@npm:4.3.2" @@ -25485,13 +24817,6 @@ __metadata: languageName: node linkType: hard -"linkifyjs@npm:4.1.3": - version: 4.1.3 - resolution: "linkifyjs@npm:4.1.3" - checksum: 10/7c17dac6a66fdea30e56b17d49d882a333833ec093993723738842b224c8cbd87bcddc6f51f2deac9529c868f162358d7acb0c44753b92832027ae761eceac1a - languageName: node - linkType: hard - "linkifyjs@npm:4.3.2": version: 4.3.2 resolution: "linkifyjs@npm:4.3.2" @@ -26092,25 +25417,6 @@ __metadata: languageName: node linkType: hard -"mathjs@npm:^11.11.2": - version: 11.12.0 - resolution: "mathjs@npm:11.12.0" - dependencies: - "@babel/runtime": "npm:^7.23.2" - complex.js: "npm:^2.1.1" - decimal.js: "npm:^10.4.3" - escape-latex: "npm:^1.2.0" - fraction.js: "npm:4.3.4" - javascript-natural-sort: "npm:^0.7.1" - seedrandom: "npm:^3.0.5" - tiny-emitter: "npm:^2.1.0" - typed-function: "npm:^4.1.1" - bin: - mathjs: bin/cli.js - checksum: 10/d0a3ab35fb0a51be321709cdd49bd69de832c4f62ab578fc5761f94502411a016ddb3809020cf3d87e0c563d379c24f68a1b77aa14031e59965203956518d6ad - languageName: node - linkType: hard - "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -27041,13 +26347,6 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.2.8 - resolution: "minipass@npm:4.2.8" - checksum: 10/e148eb6dcb85c980234cad889139ef8ddf9d5bdac534f4f0268446c8792dd4c74f4502479be48de3c1cce2f6450f6da4d0d4a86405a8a12be04c1c36b339569a - languageName: node - linkType: hard - "minipass@npm:^5.0.0": version: 5.0.0 resolution: "minipass@npm:5.0.0" @@ -27847,13 +27146,6 @@ __metadata: languageName: node linkType: hard -"oauth4webapi@npm:^3.8.4": - version: 3.8.5 - resolution: "oauth4webapi@npm:3.8.5" - checksum: 10/5c39d4dbd95b795438e79444a1d710631b3f8311e8b8913150b385b5815857b32f617a49d4869a4476b2318f614adfbc9d19510360df198719973f6f69a2e75b - languageName: node - linkType: hard - "oauth@npm:0.10.x": version: 0.10.2 resolution: "oauth@npm:0.10.2" @@ -28163,7 +27455,7 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0, openid-client@npm:^5.5.0, openid-client@npm:^5.6.5": +"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0, openid-client@npm:^5.5.0": version: 5.7.1 resolution: "openid-client@npm:5.7.1" dependencies: @@ -28175,16 +27467,6 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^6.1.3": - version: 6.8.2 - resolution: "openid-client@npm:6.8.2" - dependencies: - jose: "npm:^6.1.3" - oauth4webapi: "npm:^3.8.4" - checksum: 10/5d30ff2419c4847d0037c43cf136dd7189b16399aaee98faaad5bcea105c4e1e4e43d2dac1141ea77049b446b50d1b26f2bb882f717b3e38a5f996645abfc1d0 - languageName: node - linkType: hard - "oppa@npm:^0.4.0": version: 0.4.0 resolution: "oppa@npm:0.4.0" @@ -30254,13 +29536,6 @@ __metadata: languageName: node linkType: hard -"re2-wasm@npm:^1.0.2": - version: 1.0.2 - resolution: "re2-wasm@npm:1.0.2" - checksum: 10/aabe66abb7ba8441bb9518b8d28c3b74b89105aa7f688ef912f7465aa1f52c2af7b1c492c768ad4bc0bbc6398127c3ac30353aeb093df3b21fbcb68bcb285c98 - languageName: node - linkType: hard - "react-aria-components@npm:^1.13.0": version: 1.15.1 resolution: "react-aria-components@npm:1.15.1" @@ -30480,13 +29755,6 @@ __metadata: languageName: node linkType: hard -"react-fast-compare@npm:^2.0.1": - version: 2.0.4 - resolution: "react-fast-compare@npm:2.0.4" - checksum: 10/e4e3218c0f5c29b88e9f184a12adb77b0a93a803dbd45cb98bbb754c8310dc74e6266c53dd70b90ba4d0939e0e1b8a182cb05d081bcab22507a0390fbcd768ac - languageName: node - linkType: hard - "react-fast-compare@npm:^3.1.1": version: 3.2.2 resolution: "react-fast-compare@npm:3.2.2" @@ -30882,7 +30150,7 @@ __metadata: languageName: node linkType: hard -"react-use@npm:^17.2.4, react-use@npm:^17.3.2, react-use@npm:^17.4.0, react-use@npm:^17.5.0": +"react-use@npm:^17.2.4, react-use@npm:^17.3.2": version: 17.6.0 resolution: "react-use@npm:17.6.0" dependencies: @@ -31252,13 +30520,6 @@ __metadata: languageName: node linkType: hard -"remove-accents@npm:^0.4.2": - version: 0.4.4 - resolution: "remove-accents@npm:0.4.4" - checksum: 10/13d1b4086d3926928117d3253ede78f1bb1084f8897c7a60043b392a47663d5b3abd008bc854dc225d6f762dd5a5845acf15add1b02826f1e11de37862bad024 - languageName: node - linkType: hard - "remove-trailing-separator@npm:^1.0.1": version: 1.1.0 resolution: "remove-trailing-separator@npm:1.1.0" @@ -31945,15 +31206,6 @@ __metadata: languageName: node linkType: hard -"schemes@npm:^1.4.0": - version: 1.4.0 - resolution: "schemes@npm:1.4.0" - dependencies: - extend: "npm:^3.0.0" - checksum: 10/4133cf55ee41c44756c6489721dc72326c1148c8bf0e26bb27cd4e19c9a3fdc1b47318a4bf0257f0261afe8bfaef4fbd6c860ffdeaa8ec7704fc39549d48314f - languageName: node - linkType: hard - "screenfull@npm:^5.1.0": version: 5.2.0 resolution: "screenfull@npm:5.2.0" @@ -31961,13 +31213,6 @@ __metadata: languageName: node linkType: hard -"seedrandom@npm:^3.0.5": - version: 3.0.5 - resolution: "seedrandom@npm:3.0.5" - checksum: 10/acad5e516c04289f61c2fb9848f449b95f58362b75406b79ec51e101ec885293fc57e3675d2f39f49716336559d7190f7273415d185fead8cd27b171ebf7d8fb - languageName: node - linkType: hard - "select-case@npm:^1.0.0": version: 1.0.0 resolution: "select-case@npm:1.0.0" @@ -32373,15 +31618,6 @@ __metadata: languageName: node linkType: hard -"smtp-address-parser@npm:^1.0.3": - version: 1.1.0 - resolution: "smtp-address-parser@npm:1.1.0" - dependencies: - nearley: "npm:^2.20.1" - checksum: 10/ff01b570da1a8faf96e817d38622b45db903033cc8b2c7874c679d7cb17b0cfef00d81260cdb5d886170c89f8d067d193cc36b6ff6c89ef2444da6a345d0b1e2 - languageName: node - linkType: hard - "sockjs@npm:^0.3.24": version: 0.3.24 resolution: "sockjs@npm:0.3.24" @@ -32404,7 +31640,7 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.4, socks-proxy-agent@npm:^8.0.5": +"socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.5": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" dependencies: @@ -33394,7 +32630,7 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^3.0.9, tar-fs@npm:^3.1.1": +"tar-fs@npm:^3.1.1": version: 3.1.1 resolution: "tar-fs@npm:3.1.1" dependencies: @@ -33449,7 +32685,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.0.0, tar@npm:^7.5.4": +"tar@npm:^7.5.4": version: 7.5.9 resolution: "tar@npm:7.5.9" dependencies: @@ -33652,13 +32888,6 @@ __metadata: languageName: node linkType: hard -"tiny-emitter@npm:^2.1.0": - version: 2.1.0 - resolution: "tiny-emitter@npm:2.1.0" - checksum: 10/75633f4de4f47f43af56aff6162f25b87be7efc6f669fda256658f3c3f4a216f23dc0d13200c6fafaaf1b0c7142f0201352fb06aec0b77f68aea96be898f4516 - languageName: node - linkType: hard - "tiny-invariant@npm:^1.0.6": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" @@ -33701,16 +32930,7 @@ __metadata: languageName: node linkType: hard -"tmp-promise@npm:^3.0.2": - version: 3.0.3 - resolution: "tmp-promise@npm:3.0.3" - dependencies: - tmp: "npm:^0.2.0" - checksum: 10/0ca65b4f233b1d2b01e17a7a62961d32923e4b27383a370bf4d8d52f1062d79c3250e6b6b706ec390e73c9c58c13dc130b3855eedc89c86c7d90beb28b8382e5 - languageName: node - linkType: hard - -"tmp@npm:^0.2.0, tmp@npm:^0.2.5": +"tmp@npm:^0.2.5": version: 0.2.5 resolution: "tmp@npm:0.2.5" checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec @@ -34209,13 +33429,6 @@ __metadata: languageName: node linkType: hard -"typed-function@npm:^4.1.1": - version: 4.2.2 - resolution: "typed-function@npm:4.2.2" - checksum: 10/6d0eb312d2fa2c4b9af7a6cba5f3e9123e1e7c0b2521a7cd30a1140f862ffb94ad44353b2798a8c160fb40a9b341f7870d3c05589a7b54472514ff6f1b12e6ac - languageName: node - linkType: hard - "typed-rest-client@npm:2.1.0": version: 2.1.0 resolution: "typed-rest-client@npm:2.1.0" @@ -34776,7 +33989,7 @@ __metadata: languageName: node linkType: hard -"url-parse@npm:^1.4.3, url-parse@npm:^1.5.10": +"url-parse@npm:^1.5.10": version: 1.5.10 resolution: "url-parse@npm:1.5.10" dependencies: @@ -35508,7 +34721,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3, ws@npm:^8.19.0, ws@npm:^8.8.0": +"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.3, ws@npm:^8.19.0, ws@npm:^8.8.0": version: 8.19.0 resolution: "ws@npm:8.19.0" peerDependencies: @@ -35816,7 +35029,7 @@ __metadata: languageName: node linkType: hard -"yup@npm:^1.0.0, yup@npm:^1.3.2, yup@npm:^1.7.1": +"yup@npm:^1.0.0, yup@npm:^1.7.1": version: 1.7.1 resolution: "yup@npm:1.7.1" dependencies: From 0e73523e6a158fff2861de9caa485c9d1ee881a9 Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Thu, 30 Apr 2026 13:15:10 +0300 Subject: [PATCH 2/2] Show Error to User, add Default Empty Body... --- .../dcm-common/src/clients/CatalogClient.ts | 2 +- .../CatalogItemInstancesTabContent.tsx | 40 +++++++++---------- .../pages/resources/ResourcesTabContent.tsx | 26 +++++++++++- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts index 5dd263f6c26..6f7f651f6db 100644 --- a/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts +++ b/workspaces/dcm/plugins/dcm-common/src/clients/CatalogClient.ts @@ -119,7 +119,7 @@ export class CatalogClient extends DcmBaseClient implements CatalogApi { ): Promise { return this.fetch( `catalog-item-instances/${catalogItemInstanceId}:rehydrate`, - { method: 'POST' }, + { method: 'POST', body: '{}' }, ); } diff --git a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx index 1df828a57c2..ff1c72c9e28 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx @@ -19,25 +19,6 @@ import { TableColumn } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { Box, Chip, IconButton, Tooltip, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; - -const useStyles = makeStyles(() => ({ - catalogItemChip: { - maxWidth: 180, - }, - apiVersionChip: { - maxWidth: 140, - }, - actionsCell: { - display: 'flex', - alignItems: 'center', - flexWrap: 'nowrap', - gap: 4, - }, - /** Lets Tooltip wrap disabled IconButton (ref + layout) without raw ``. */ - tooltipTrigger: { - display: 'inline-flex', - }, -})); import AutorenewIcon from '@material-ui/icons/Autorenew'; import DeleteIcon from '@material-ui/icons/Delete'; import type { @@ -63,6 +44,25 @@ import { } from './instanceFormTypes'; import type { InstanceForm } from './instanceFormTypes'; +const useStyles = makeStyles(() => ({ + catalogItemChip: { + maxWidth: 180, + }, + apiVersionChip: { + maxWidth: 140, + }, + actionsCell: { + display: 'flex', + alignItems: 'center', + flexWrap: 'nowrap', + gap: 4, + }, + /** Lets Tooltip wrap disabled IconButton (ref + layout) without raw ``. */ + tooltipTrigger: { + display: 'inline-flex', + }, +})); + export function CatalogItemInstancesTabContent() { const classes = useStyles(); const catalogApi = useApi(catalogApiRef); @@ -198,7 +198,7 @@ export function CatalogItemInstancesTabContent() { sorting: false, width: '120px', render: inst => { - const busy = rehydratingId === (inst.uid ?? ''); + const busy = rehydratingId === inst.uid; return ( diff --git a/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx index 4e171e143b0..264846322d1 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/resources/ResourcesTabContent.tsx @@ -17,9 +17,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { TableColumn, Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { Box, Chip, Typography } from '@material-ui/core'; +import { Box, Button, Chip, Typography } from '@material-ui/core'; +import MuiAlert from '@material-ui/lab/Alert'; import type { ServiceTypeInstance } from '@red-hat-developer-hub/backstage-plugin-dcm-common'; import { resourcesApiRef } from '../../apis'; +import { extractApiError } from '../../utils/extractApiError'; import { DcmSearchTableCard } from '../../components/dcmTabListHelpers'; import { useDcmStyles } from '../../components/dcmStyles'; import emptyIllustration from '../../assets/environments-empty-state.png'; @@ -33,16 +35,18 @@ export function ResourcesTabContent() { const [instances, setInstances] = useState([]); const [loading, setLoading] = useState(true); + const [loadError, setLoadError] = useState(null); const [search, setSearch] = useState(''); const [page, setPage] = useState(0); const [pageSize, setPageSize] = usePersistedPageSize('resources'); const load = useCallback(() => { setLoading(true); + setLoadError(null); resourcesApi .listServiceTypeInstances() .then(res => setInstances(res.instances ?? [])) - .catch(() => setInstances([])) + .catch(err => setLoadError(extractApiError(err))) .finally(() => setLoading(false)); }, [resourcesApi]); @@ -137,6 +141,24 @@ export function ResourcesTabContent() { if (loading) return ; + if (loadError) { + return ( + + + Retry + + } + > + {loadError} + + + ); + } + return ( {instances.length === 0 ? (