From 6986bbd877f57e7c28963eb8789a446590754b5a Mon Sep 17 00:00:00 2001 From: Hubert Date: Sun, 28 Sep 2025 23:50:41 +0200 Subject: [PATCH 01/15] Managed resources deletion --- public/locales/en.json | 8 +- .../ControlPlane/ManagedResources.tsx | 134 +++++++++++++---- .../Dialogs/DeleteConfirmationDialog.tsx | 33 ++--- ....css => DeleteConfirmationForm.module.css} | 0 .../Dialogs/DeleteConfirmationForm.tsx | 36 +++++ .../ManagedResourceDeleteDialog.module.css | 15 ++ .../Dialogs/ManagedResourceDeleteDialog.tsx | 139 ++++++++++++++++++ src/components/Helper/getPluralKind.ts | 6 + src/lib/api/types/crate/deleteResource.ts | 38 +++++ src/lib/api/useApiResource.ts | 33 ++++- src/lib/shared/types.ts | 9 ++ 11 files changed, 397 insertions(+), 54 deletions(-) rename src/components/Dialogs/{DeleteConfirmationDialog.module.css => DeleteConfirmationForm.module.css} (100%) create mode 100644 src/components/Dialogs/DeleteConfirmationForm.tsx create mode 100644 src/components/Dialogs/ManagedResourceDeleteDialog.module.css create mode 100644 src/components/Dialogs/ManagedResourceDeleteDialog.tsx create mode 100644 src/components/Helper/getPluralKind.ts create mode 100644 src/lib/api/types/crate/deleteResource.ts diff --git a/public/locales/en.json b/public/locales/en.json index bf51c33f..df6c2d7b 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -37,7 +37,13 @@ "tableHeaderName": "Name", "tableHeaderCreated": "Created", "tableHeaderSynced": "Synced", - "tableHeaderReady": "Ready" + "tableHeaderReady": "Ready", + "tableHeaderDelete": "Delete", + "deleteAction": "Delete resource", + "deleteDialogTitle": "Delete resource", + "advancedOptions": "Advanced options", + "forceDeletion": "Force deletion", + "forceWarningLine": "Force deletion removes finalizers. Related resources may not be deleted and cleanup may be skipped." }, "ProvidersConfig": { "headerProviderConfigs": "Provider Configs", diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx index 65cf7281..288887e6 100644 --- a/src/components/ControlPlane/ManagedResources.tsx +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -3,9 +3,13 @@ import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, + Button, Title, + Menu, + MenuItem, + MenuDomRef, } from '@ui5/webcomponents-react'; -import { useApiResource } from '../../lib/api/useApiResource'; +import { useApiResource, useCRDItemsMapping } from '../../lib/api/useApiResource'; import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources'; import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo'; import IllustratedError from '../Shared/IllustratedError'; @@ -14,10 +18,37 @@ import '@ui5/webcomponents-icons/dist/sys-cancel-2'; import { resourcesInterval } from '../../lib/shared/constants'; import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; -import { useMemo } from 'react'; +import { FC, useMemo, useRef, useState } from 'react'; import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx'; import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; +import { ManagedResourceItem } from '../../lib/shared/types.ts'; +import { ManagedResourceDeleteDialog } from '../Dialogs/ManagedResourceDeleteDialog.tsx'; + +const getItemKey = (item: ManagedResourceItem): string => `${item.kind}-${item.metadata.name}`; + +const RowActionsMenu: FC<{ + item: ManagedResourceItem; + onOpen: (item: ManagedResourceItem) => void; + isDeleting: boolean; +}> = ({ item, onOpen, isDeleting }) => { + const { t } = useTranslation(); + const popoverRef = useRef(null); + + return ( + <> + + + + + + ); +}; diff --git a/src/components/Helper/getPluralKind.ts b/src/components/Helper/getPluralKind.ts new file mode 100644 index 00000000..35fdc7fb --- /dev/null +++ b/src/components/Helper/getPluralKind.ts @@ -0,0 +1,6 @@ +import { ManagedResourceItem } from '../../lib/shared/types'; + +export const getPluralKind = (item: ManagedResourceItem, kindMapping: Record): string => { + const singularKind = (item?.kind ?? '').toLowerCase(); + return kindMapping[singularKind] ?? ''; +}; diff --git a/src/lib/api/types/crate/deleteResource.ts b/src/lib/api/types/crate/deleteResource.ts new file mode 100644 index 00000000..37141426 --- /dev/null +++ b/src/lib/api/types/crate/deleteResource.ts @@ -0,0 +1,38 @@ +import { Resource } from '../resource'; + +export interface DeleteManagedResourceType { + name: string; + namespace: string; +} + +export const PatchResourceForForceDeletionBody = { + metadata: { + finalizers: {}, + }, +}; + +export const PatchResourceForForceDeletion = ( + apiVersion: string, + kind: string, + resourceName: string, +): Resource => { + return { + path: `/apis/${apiVersion}/${kind}/${resourceName}/`, + method: 'PATCH', + jq: undefined, + body: undefined, + }; +}; + +export const DeleteMCPManagedResource = ( + apiVersion: string, + kind: string, + resourceName: string, +): Resource => { + return { + path: `/apis/${apiVersion}/${kind}/${resourceName}/`, + method: 'DELETE', + jq: undefined, + body: undefined, + }; +}; diff --git a/src/lib/api/useApiResource.ts b/src/lib/api/useApiResource.ts index 63d846d4..a2997804 100644 --- a/src/lib/api/useApiResource.ts +++ b/src/lib/api/useApiResource.ts @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState, useRef } from 'react'; +import { useContext, useEffect, useState, useRef, useMemo } from 'react'; import useSWR, { SWRConfiguration, useSWRConfig } from 'swr'; import { fetchApiServerJson } from './fetch'; import { ApiConfigContext } from '../../components/Shared/k8s'; @@ -39,6 +39,37 @@ export const useApiResource = ( }; }; +export const useCRDItemsMapping = (config?: SWRConfiguration) => { + const apiConfig = useContext(ApiConfigContext); + const { data, error, isValidating, isLoading } = useSWR( + CRDRequest.path === null ? null : [CRDRequest.path, apiConfig], + ([path, apiConfig]) => + fetchApiServerJson(path, apiConfig, CRDRequest.jq, CRDRequest.method, CRDRequest.body), + config, + ); + + const kindMapping = useMemo(() => { + if (!data?.items) { + return {}; + } + + return data.items.reduce((kinds: Record, item) => { + const { singular, plural } = item.spec.names as { singular?: string; plural?: string; kind: string }; + if (singular && plural) { + kinds[singular] = plural; + } + return kinds; + }, {}); + }, [data]); + + return { + data: kindMapping, + error, + isValidating, + isLoading, + }; +}; + export const useProvidersConfigResource = (config?: SWRConfiguration) => { const apiConfig = useContext(ApiConfigContext); const { data, error, isValidating } = useSWR( diff --git a/src/lib/shared/types.ts b/src/lib/shared/types.ts index 798a30ec..9e853d9f 100644 --- a/src/lib/shared/types.ts +++ b/src/lib/shared/types.ts @@ -31,6 +31,13 @@ export interface ProviderConfigItem { count: string; users: string; }; + spec: { + names: { + kind: string; + plural: string; + singular: string; + }; + }; apiVersion?: string; } @@ -51,7 +58,9 @@ export type ManagedResourceItem = { metadata: { name: string; creationTimestamp: string; + resourceVersion: string; labels: []; + namespace?: string; }; apiVersion?: string; spec?: { From 0eabb48b892f7812d8bb44224fef601bae032960 Mon Sep 17 00:00:00 2001 From: Hubert Date: Mon, 29 Sep 2025 08:19:43 +0200 Subject: [PATCH 02/15] disable only action menu --- src/components/Dialogs/ManagedResourceDeleteDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Dialogs/ManagedResourceDeleteDialog.tsx b/src/components/Dialogs/ManagedResourceDeleteDialog.tsx index 72c4df00..f661bc4f 100644 --- a/src/components/Dialogs/ManagedResourceDeleteDialog.tsx +++ b/src/components/Dialogs/ManagedResourceDeleteDialog.tsx @@ -79,12 +79,12 @@ export const ManagedResourceDeleteDialog: FC = ({ kindMapping, open, onCl const handleDelete = async () => { if (!item) return; - + onDeleteStart(item); try { await deleteTrigger({ data: { force } }); - + if (force) { await patchTrigger({ data: { force } }); } From a332b11262c32782e4dbf253d0757682bf3c94db Mon Sep 17 00:00:00 2001 From: Hubert Date: Mon, 29 Sep 2025 08:33:11 +0200 Subject: [PATCH 03/15] Action menu moved to separate file --- .../ControlPlane/ManagedResources.tsx | 32 ++----------------- .../ManagedResourcesActionMenu.tsx | 29 +++++++++++++++++ .../Dialogs/ManagedResourceDeleteDialog.tsx | 2 +- 3 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 src/components/ControlPlane/ManagedResourcesActionMenu.tsx diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx index 288887e6..f558f11f 100644 --- a/src/components/ControlPlane/ManagedResources.tsx +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -3,53 +3,25 @@ import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, - Button, Title, - Menu, - MenuItem, - MenuDomRef, } from '@ui5/webcomponents-react'; import { useApiResource, useCRDItemsMapping } from '../../lib/api/useApiResource'; import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources'; import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo'; import IllustratedError from '../Shared/IllustratedError'; -import '@ui5/webcomponents-icons/dist/sys-enter-2'; -import '@ui5/webcomponents-icons/dist/sys-cancel-2'; import { resourcesInterval } from '../../lib/shared/constants'; import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; -import { FC, useMemo, useRef, useState } from 'react'; +import { useMemo, useState } from 'react'; import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx'; import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; import { ManagedResourceItem } from '../../lib/shared/types.ts'; import { ManagedResourceDeleteDialog } from '../Dialogs/ManagedResourceDeleteDialog.tsx'; +import { RowActionsMenu } from './ManagedResourcesActionMenu.tsx'; const getItemKey = (item: ManagedResourceItem): string => `${item.kind}-${item.metadata.name}`; -const RowActionsMenu: FC<{ - item: ManagedResourceItem; - onOpen: (item: ManagedResourceItem) => void; - isDeleting: boolean; -}> = ({ item, onOpen, isDeleting }) => { - const { t } = useTranslation(); - const popoverRef = useRef(null); - - return ( - <> - - From df0a14504ea11b734ad38f9e717a783ad2b8e8bf Mon Sep 17 00:00:00 2001 From: Hubert Date: Mon, 29 Sep 2025 10:49:13 +0200 Subject: [PATCH 09/15] fixing lint issue --- src/components/Dialogs/ManagedResourceDeleteDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Dialogs/ManagedResourceDeleteDialog.tsx b/src/components/Dialogs/ManagedResourceDeleteDialog.tsx index e2969e1e..93c86bff 100644 --- a/src/components/Dialogs/ManagedResourceDeleteDialog.tsx +++ b/src/components/Dialogs/ManagedResourceDeleteDialog.tsx @@ -59,11 +59,11 @@ export const ManagedResourceDeleteDialog: FC = ({ kindMapping, open, onCl const isConfirmed = confirmationText === resourceName; - const { trigger: deleteTrigger} = useApiResourceMutation( + const { trigger: deleteTrigger } = useApiResourceMutation( DeleteMCPManagedResource(apiVersion, pluralKind, resourceName), ); - const { trigger: patchTrigger} = useApiResourceMutation( + const { trigger: patchTrigger } = useApiResourceMutation( PatchResourceForForceDeletion(apiVersion, pluralKind, resourceName), ); From 68609b013225c6567b8a50c31b1479179930572b Mon Sep 17 00:00:00 2001 From: Hubert Date: Tue, 30 Sep 2025 09:24:28 +0200 Subject: [PATCH 10/15] PR comments --- .../ControlPlane/ManagedResources.module.css | 4 ++++ .../ControlPlane/ManagedResources.tsx | 3 ++- .../Dialogs/ManagedResourceDeleteDialog.tsx | 7 +++--- src/lib/api/types/crate/deleteResource.ts | 23 +++++++++++++++---- 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/components/ControlPlane/ManagedResources.module.css diff --git a/src/components/ControlPlane/ManagedResources.module.css b/src/components/ControlPlane/ManagedResources.module.css new file mode 100644 index 00000000..4473ff92 --- /dev/null +++ b/src/components/ControlPlane/ManagedResources.module.css @@ -0,0 +1,4 @@ +.deletingRow { + opacity: 0.5; + pointer-events: none; +} diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx index f558f11f..5567dd26 100644 --- a/src/components/ControlPlane/ManagedResources.tsx +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -19,6 +19,7 @@ import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; import { ManagedResourceItem } from '../../lib/shared/types.ts'; import { ManagedResourceDeleteDialog } from '../Dialogs/ManagedResourceDeleteDialog.tsx'; import { RowActionsMenu } from './ManagedResourcesActionMenu.tsx'; +import styles from './ManagedResources.module.css'; const getItemKey = (item: ManagedResourceItem): string => `${item.kind}-${item.metadata.name}`; @@ -173,7 +174,7 @@ export function ManagedResources() { item: item, conditionSyncedMessage: conditionSynced?.message ?? conditionSynced?.reason ?? '', conditionReadyMessage: conditionReady?.message ?? conditionReady?.reason ?? '', - style: isDeleting ? { opacity: 0.5, pointerEvents: 'none' } : undefined, + className: isDeleting ? styles.deletingRow : undefined, }; }), ) ?? []; diff --git a/src/components/Dialogs/ManagedResourceDeleteDialog.tsx b/src/components/Dialogs/ManagedResourceDeleteDialog.tsx index 93c86bff..23a3a830 100644 --- a/src/components/Dialogs/ManagedResourceDeleteDialog.tsx +++ b/src/components/Dialogs/ManagedResourceDeleteDialog.tsx @@ -44,8 +44,9 @@ export const ManagedResourceDeleteDialog: FC = ({ kindMapping, open, onCl } }, [open]); - const { apiVersion, resourceName, pluralKind } = useMemo( + const { apiVersion, resourceName, pluralKind, namespace } = useMemo( () => ({ + namespace: item?.metadata?.namespace ?? '', apiVersion: item?.apiVersion ?? '', resourceName: item?.metadata?.name ?? '', pluralKind: item ? getPluralKind(item, kindMapping) : '', @@ -60,11 +61,11 @@ export const ManagedResourceDeleteDialog: FC = ({ kindMapping, open, onCl const isConfirmed = confirmationText === resourceName; const { trigger: deleteTrigger } = useApiResourceMutation( - DeleteMCPManagedResource(apiVersion, pluralKind, resourceName), + DeleteMCPManagedResource(apiVersion, pluralKind, resourceName, namespace), ); const { trigger: patchTrigger } = useApiResourceMutation( - PatchResourceForForceDeletion(apiVersion, pluralKind, resourceName), + PatchResourceForForceDeletion(apiVersion, pluralKind, resourceName, namespace), ); const handleForceDeletionChange = () => { diff --git a/src/lib/api/types/crate/deleteResource.ts b/src/lib/api/types/crate/deleteResource.ts index 6f03a0d2..8f9c053b 100644 --- a/src/lib/api/types/crate/deleteResource.ts +++ b/src/lib/api/types/crate/deleteResource.ts @@ -13,11 +13,19 @@ export const PatchResourceForForceDeletionBody = { export const PatchResourceForForceDeletion = ( apiVersion: string, - kind: string, + pluralKind: string, resourceName: string, + namespace?: string, ): Resource => { + // If namespace is provided, use namespaced path; otherwise, use cluster-scoped path + const basePath = `/apis/${apiVersion}`; + + const path = namespace + ? `${basePath}/namespaces/${namespace}/${pluralKind}/${resourceName}` + : `${basePath}/${pluralKind}/${resourceName}`; + return { - path: `/apis/${apiVersion}/${kind}/${resourceName}/`, + path: path, method: 'PATCH', jq: undefined, body: JSON.stringify(PatchResourceForForceDeletionBody), @@ -26,11 +34,18 @@ export const PatchResourceForForceDeletion = ( export const DeleteMCPManagedResource = ( apiVersion: string, - kind: string, + pluralKind: string, resourceName: string, + namespace?: string, ): Resource => { + // If namespace is provided, use namespaced path; otherwise, use cluster-scoped path + const basePath = `/apis/${apiVersion}`; + + const path = namespace + ? `${basePath}/namespaces/${namespace}/${pluralKind}/${resourceName}` + : `${basePath}/${pluralKind}/${resourceName}`; return { - path: `/apis/${apiVersion}/${kind}/${resourceName}/`, + path: path, method: 'DELETE', jq: undefined, body: undefined, From d7e58eb774d1cc4c14e9c207bbcdcc1e58500b0d Mon Sep 17 00:00:00 2001 From: Hubert Date: Fri, 3 Oct 2025 10:02:43 +0200 Subject: [PATCH 11/15] PR changes --- public/locales/en.json | 4 +- .../ControlPlane/ManagedResources.module.css | 4 - .../ControlPlane/ManagedResources.tsx | 88 +++++++++++-------- .../ManagedResourcesActionMenu.tsx | 29 ++++-- .../Dialogs/ManagedResourceDeleteDialog.tsx | 59 ++++--------- src/hooks/useResourcePluralNames.ts | 26 ++++++ 6 files changed, 120 insertions(+), 90 deletions(-) delete mode 100644 src/components/ControlPlane/ManagedResources.module.css create mode 100644 src/hooks/useResourcePluralNames.ts diff --git a/public/locales/en.json b/public/locales/en.json index df6c2d7b..21d5de38 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -43,7 +43,9 @@ "deleteDialogTitle": "Delete resource", "advancedOptions": "Advanced options", "forceDeletion": "Force deletion", - "forceWarningLine": "Force deletion removes finalizers. Related resources may not be deleted and cleanup may be skipped." + "forceWarningLine": "Force deletion removes finalizers. Related resources may not be deleted and cleanup may be skipped.", + "deleteStarted": "Deleting {{resourceName}} initialized", + "actionColumnHeader": " " }, "ProvidersConfig": { "headerProviderConfigs": "Provider Configs", diff --git a/src/components/ControlPlane/ManagedResources.module.css b/src/components/ControlPlane/ManagedResources.module.css deleted file mode 100644 index 4473ff92..00000000 --- a/src/components/ControlPlane/ManagedResources.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.deletingRow { - opacity: 0.5; - pointer-events: none; -} diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx index 5567dd26..da94a1c2 100644 --- a/src/components/ControlPlane/ManagedResources.tsx +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -5,7 +5,7 @@ import { AnalyticalTableScaleWidthMode, Title, } from '@ui5/webcomponents-react'; -import { useApiResource, useCRDItemsMapping } from '../../lib/api/useApiResource'; +import { useApiResource, useApiResourceMutation } from '../../lib/api/useApiResource'; import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources'; import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo'; import IllustratedError from '../Shared/IllustratedError'; @@ -19,9 +19,13 @@ import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; import { ManagedResourceItem } from '../../lib/shared/types.ts'; import { ManagedResourceDeleteDialog } from '../Dialogs/ManagedResourceDeleteDialog.tsx'; import { RowActionsMenu } from './ManagedResourcesActionMenu.tsx'; -import styles from './ManagedResources.module.css'; - -const getItemKey = (item: ManagedResourceItem): string => `${item.kind}-${item.metadata.name}`; +import { useToast } from '../../context/ToastContext.tsx'; +import { + DeleteManagedResourceType, + DeleteMCPManagedResource, + PatchResourceForForceDeletion, +} from '../../lib/api/types/crate/deleteResource'; +import { useResourcePluralNames } from '../../hooks/useResourcePluralNames'; interface CellData { cell: { @@ -47,9 +51,8 @@ type ResourceRow = { export function ManagedResources() { const { t } = useTranslation(); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [selectedItem, setSelectedItem] = useState(null); - const [deletingItems, setDeletingItems] = useState>(new Set()); + const toast = useToast(); + const [pendingDeleteItem, setPendingDeleteItem] = useState(null); const { data: managedResources, @@ -59,19 +62,20 @@ export function ManagedResources() { refreshInterval: resourcesInterval, }); - const openDeleteDialog = (item: ManagedResourceItem) => { - setSelectedItem(item); - setDeleteDialogOpen(true); - }; + const { getPluralKind, isLoading: isLoadingPluralNames, error: pluralNamesError } = useResourcePluralNames(); - const handleDeleteStart = (item: ManagedResourceItem) => { - const itemKey = getItemKey(item); - setDeletingItems((prev) => new Set(prev.add(itemKey))); - }; + const resourceName = pendingDeleteItem?.metadata?.name ?? ''; + const apiVersion = pendingDeleteItem?.apiVersion ?? ''; + const pluralKind = pendingDeleteItem ? getPluralKind(pendingDeleteItem.kind) : ''; + const namespace = pendingDeleteItem?.metadata?.namespace ?? ''; - const { data: kindMapping } = useCRDItemsMapping({ - refreshInterval: resourcesInterval, - }); + const { trigger: deleteTrigger } = useApiResourceMutation( + DeleteMCPManagedResource(apiVersion, pluralKind, resourceName, namespace), + ); + + const { trigger: patchTrigger } = useApiResourceMutation( + PatchResourceForForceDeletion(apiVersion, pluralKind, resourceName, namespace), + ); const columns: AnalyticalTableColumnDefinition[] = useMemo( () => [ @@ -134,22 +138,20 @@ export function ManagedResources() { }, }, { - Header: ' ', + Header: t('ManagedResources.actionColumnHeader'), hAlign: 'Center', width: 60, disableFilters: true, Cell: (cellData: CellData) => { const item = cellData.cell.row.original?.item as ManagedResourceItem; - const itemKey = item ? getItemKey(item) : ''; - const isDeleting = deletingItems.has(itemKey); return cellData.cell.row.original?.item ? ( - + ) : undefined; }, }, ], - [t, deletingItems], + [t], ); const rows: ResourceRow[] = @@ -157,9 +159,6 @@ export function ManagedResources() { ?.filter((managedResource) => managedResource.items) .flatMap((managedResource) => managedResource.items?.map((item) => { - const itemKey = getItemKey(item); - const isDeleting = deletingItems.has(itemKey); - const conditionSynced = item.status?.conditions?.find((condition) => condition.type === 'Synced'); const conditionReady = item.status?.conditions?.find((condition) => condition.type === 'Ready'); @@ -174,18 +173,38 @@ export function ManagedResources() { item: item, conditionSyncedMessage: conditionSynced?.message ?? conditionSynced?.reason ?? '', conditionReadyMessage: conditionReady?.message ?? conditionReady?.reason ?? '', - className: isDeleting ? styles.deletingRow : undefined, }; }), ) ?? []; + const openDeleteDialog = (item: ManagedResourceItem) => { + setPendingDeleteItem(item); + }; + + const handleDeletionConfirmed = async (item: ManagedResourceItem, force: boolean) => { + toast.show(t('ManagedResources.deleteStarted', { resourceName: item.metadata.name })); + + try { + await deleteTrigger(); + + if (force) { + await patchTrigger(); + } + } catch (_) { + // Ignore errors - will be handled by the mutation hook + } + }; + + const combinedError = error || pluralNamesError; + const combinedLoading = isLoading || isLoadingPluralNames; + return ( <> {t('ManagedResources.header')} - {error && } + {combinedError && } - {!error && ( + {!combinedError && ( <> setDeleteDialogOpen(false)} - onDeleteStart={handleDeleteStart} + open={!!pendingDeleteItem} + item={pendingDeleteItem} + onClose={() => setPendingDeleteItem(null)} + onDeletionConfirmed={handleDeletionConfirmed} /> )} diff --git a/src/components/ControlPlane/ManagedResourcesActionMenu.tsx b/src/components/ControlPlane/ManagedResourcesActionMenu.tsx index a1ff907d..91d8e7a1 100644 --- a/src/components/ControlPlane/ManagedResourcesActionMenu.tsx +++ b/src/components/ControlPlane/ManagedResourcesActionMenu.tsx @@ -1,28 +1,43 @@ -import { FC, useRef } from 'react'; +import { FC, useRef, useState } from 'react'; import { Button, Menu, MenuItem, MenuDomRef } from '@ui5/webcomponents-react'; import { useTranslation } from 'react-i18next'; import { ManagedResourceItem } from '../../lib/shared/types'; +import type { ButtonClickEventDetail } from '@ui5/webcomponents/dist/Button.js'; +import type { Ui5CustomEvent, ButtonDomRef } from '@ui5/webcomponents-react'; interface RowActionsMenuProps { item: ManagedResourceItem; onOpen: (item: ManagedResourceItem) => void; - isDeleting: boolean; } -export const RowActionsMenu: FC = ({ item, onOpen, isDeleting }) => { +export const RowActionsMenu: FC = ({ item, onOpen }) => { const { t } = useTranslation(); const popoverRef = useRef(null); + const [open, setOpen] = useState(false); + + const handleOpenerClick = (e: Ui5CustomEvent) => { + if (popoverRef.current && e.currentTarget) { + popoverRef.current.opener = e.currentTarget as unknown as HTMLElement; + setOpen((prev) => !prev); + } + }; return ( <> -