From 45079a35f7e56869c451c8e5598b472c6b362608 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Tue, 27 Jan 2026 10:49:34 -0500 Subject: [PATCH 01/12] CONSOLE-5012: Migrate PVC modals to overlay pattern Migrates PVC-related modals (expand, clone, delete, restore) from the legacy createModalLauncher pattern to the modern OverlayComponent pattern with lazy loading. Changes include: - Convert modal exports to OverlayComponent providers - Implement lazy loading for modal components - Update action hooks to use useOverlay() and launchModal() - Set react-modal app element globally in App component - Replace history.push with useNavigate from react-router-dom-v5-compat - Remove setTimeout wrappers (not needed with useNavigate) - Simplify ModalWrapper appElement selector - Maintain backward compatibility with existing modal exports Co-Authored-By: Claude Sonnet 4.5 --- .../src/actions/hooks/usePVCActions.ts | 20 ++++------ .../actions/hooks/useVolumeSnapshotActions.ts | 9 +++-- .../modals/clone/clone-pvc-modal.tsx | 16 ++++++-- .../modals/restore-pvc/restore-pvc-modal.tsx | 16 ++++++-- .../src/app/modal-support/OverlayProvider.tsx | 6 ++- frontend/public/components/app.tsx | 9 +++++ frontend/public/components/factory/modal.tsx | 4 +- .../components/modals/delete-pvc-modal.tsx | 12 ++++-- .../components/modals/expand-pvc-modal.tsx | 22 ++++++++--- frontend/public/components/modals/index.ts | 39 ++++++++++++++----- 10 files changed, 107 insertions(+), 46 deletions(-) diff --git a/frontend/packages/console-app/src/actions/hooks/usePVCActions.ts b/frontend/packages/console-app/src/actions/hooks/usePVCActions.ts index 47b6d02431e..1ab7e92d5d2 100644 --- a/frontend/packages/console-app/src/actions/hooks/usePVCActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/usePVCActions.ts @@ -4,8 +4,11 @@ import { ModifyVACModal } from '@console/app/src/components/modals/modify-vac-mo import { Action } from '@console/dynamic-plugin-sdk'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize'; -import { clonePVCModal, expandPVCModal } from '@console/internal/components/modals'; -import deletePVCModal from '@console/internal/components/modals/delete-pvc-modal'; +import { + LazyClonePVCModalProvider, + LazyDeletePVCModalProvider, + LazyExpandPVCModalProvider, +} from '@console/internal/components/modals'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { VolumeSnapshotModel, PersistentVolumeClaimModel } from '@console/internal/models'; import { PersistentVolumeClaimKind } from '@console/internal/module/k8s'; @@ -46,7 +49,7 @@ export const usePVCActions = ( id: 'expand-pvc', label: t('console-app~Expand PVC'), cta: () => - expandPVCModal({ + launchModal(LazyExpandPVCModalProvider, { kind: PersistentVolumeClaimModel, resource: obj, }), @@ -67,11 +70,7 @@ export const usePVCActions = ( label: t('console-app~Clone PVC'), disabled: obj?.status?.phase !== 'Bound', tooltip: obj?.status?.phase !== 'Bound' ? t('console-app~PVC is not Bound') : '', - cta: () => - clonePVCModal({ - kind: PersistentVolumeClaimModel, - resource: obj, - }), + cta: () => launchModal(LazyClonePVCModalProvider, { resource: obj }), accessReview: asAccessReview(PersistentVolumeClaimModel, obj, 'create'), }), [PVCActionCreator.ModifyVAC]: () => ({ @@ -88,10 +87,7 @@ export const usePVCActions = ( [PVCActionCreator.DeletePVC]: () => ({ id: 'delete-pvc', label: t('public~Delete PersistentVolumeClaim'), - cta: () => - deletePVCModal({ - pvc: obj, - }), + cta: () => launchModal(LazyDeletePVCModalProvider, { pvc: obj }), accessReview: asAccessReview(PersistentVolumeClaimModel, obj, 'delete'), }), }), diff --git a/frontend/packages/console-app/src/actions/hooks/useVolumeSnapshotActions.ts b/frontend/packages/console-app/src/actions/hooks/useVolumeSnapshotActions.ts index 261475fcd05..8e089167af6 100644 --- a/frontend/packages/console-app/src/actions/hooks/useVolumeSnapshotActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useVolumeSnapshotActions.ts @@ -1,8 +1,9 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Action } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize'; -import { restorePVCModal } from '@console/internal/components/modals'; +import { LazyRestorePVCModalProvider } from '@console/internal/components/modals'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { VolumeSnapshotModel } from '@console/internal/models'; import { VolumeSnapshotKind } from '@console/internal/module/k8s'; @@ -32,6 +33,7 @@ export const useVolumeSnapshotActions = ( filterActions?: VolumeSnapshotActionCreator[], ): Action[] => { const { t } = useTranslation(); + const launchModal = useOverlay(); const memoizedFilterActions = useDeepCompareMemoize(filterActions); @@ -43,14 +45,13 @@ export const useVolumeSnapshotActions = ( disabled: !resource?.status?.readyToUse, tooltip: !resource?.status?.readyToUse ? t('console-app~Volume Snapshot is not Ready') : '', cta: () => - restorePVCModal({ - kind: VolumeSnapshotModel, + launchModal(LazyRestorePVCModalProvider, { resource, }), accessReview: asAccessReview(VolumeSnapshotModel, resource, 'create'), }), }), - [t, resource], + [t, resource, launchModal], ); const actions = useMemo(() => { diff --git a/frontend/packages/console-app/src/components/modals/clone/clone-pvc-modal.tsx b/frontend/packages/console-app/src/components/modals/clone/clone-pvc-modal.tsx index 3b2df85af25..beb421690b2 100644 --- a/frontend/packages/console-app/src/components/modals/clone/clone-pvc-modal.tsx +++ b/frontend/packages/console-app/src/components/modals/clone/clone-pvc-modal.tsx @@ -8,12 +8,14 @@ import { TextInput, } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { ModalBody, ModalComponentProps, ModalSubmitFooter, ModalTitle, - createModalLauncher, + ModalWrapper, } from '@console/internal/components/factory'; import { DataPoint } from '@console/internal/components/graphs'; import { PrometheusEndpoint } from '@console/internal/components/graphs/helpers'; @@ -24,7 +26,6 @@ import { useK8sGet } from '@console/internal/components/utils/k8s-get-hook'; import { RequestSizeInput } from '@console/internal/components/utils/request-size-input'; import { ResourceIcon } from '@console/internal/components/utils/resource-icon'; import { resourceObjPath } from '@console/internal/components/utils/resource-link'; -import { history } from '@console/internal/components/utils/router'; import { LoadingInline } from '@console/internal/components/utils/status-box'; import { StorageClassDropdown } from '@console/internal/components/utils/storage-class-dropdown'; import { @@ -53,6 +54,7 @@ import './_clone-pvc-modal.scss'; const ClonePVCModal = (props: ClonePVCModalProps) => { const { t } = useTranslation(); + const navigate = useNavigate(); const { close, cancel, resource } = props; const [handlePromise, inProgress, errorMessage] = usePromiseHandler(); const { name: pvcName, namespace } = resource?.metadata; @@ -129,7 +131,7 @@ const ClonePVCModal = (props: ClonePVCModalProps) => { handlePromise(k8sCreate(PersistentVolumeClaimModel, pvcCloneObj)) .then((cloneResource) => { close(); - history.push(resourceObjPath(cloneResource, referenceFor(cloneResource))); + navigate(resourceObjPath(cloneResource, referenceFor(cloneResource))); }) .catch(() => {}); }; @@ -275,4 +277,10 @@ export type ClonePVCModalProps = { resource?: PersistentVolumeClaimKind; } & ModalComponentProps; -export default createModalLauncher(ClonePVCModal); +export const ClonePVCModalProvider: OverlayComponent = (props) => { + return ( + + + + ); +}; diff --git a/frontend/packages/console-app/src/components/modals/restore-pvc/restore-pvc-modal.tsx b/frontend/packages/console-app/src/components/modals/restore-pvc/restore-pvc-modal.tsx index f3c2492e01c..d3951c094a8 100644 --- a/frontend/packages/console-app/src/components/modals/restore-pvc/restore-pvc-modal.tsx +++ b/frontend/packages/console-app/src/components/modals/restore-pvc/restore-pvc-modal.tsx @@ -10,13 +10,15 @@ import { TextInput, } from '@patternfly/react-core'; import { Trans, useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom-v5-compat'; import { VolumeModeSelector } from '@console/app/src/components/volume-modes/volume-mode'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { ModalBody, ModalComponentProps, ModalSubmitFooter, ModalTitle, - createModalLauncher, + ModalWrapper, } from '@console/internal/components/factory'; import { dropdownUnits, @@ -28,7 +30,6 @@ import { useK8sGet } from '@console/internal/components/utils/k8s-get-hook'; import { RequestSizeInput } from '@console/internal/components/utils/request-size-input'; import { ResourceIcon } from '@console/internal/components/utils/resource-icon'; import { resourcePathFromModel } from '@console/internal/components/utils/resource-link'; -import { history } from '@console/internal/components/utils/router'; import { StorageClassDropdown } from '@console/internal/components/utils/storage-class-dropdown'; import { convertToBaseValue, @@ -60,6 +61,7 @@ import './restore-pvc-modal.scss'; const RestorePVCModal = ({ close, cancel, resource }: RestorePVCModalProps) => { const [handlePromise, inProgress, errorMessage] = usePromiseHandler(); const { t } = useTranslation(); + const navigate = useNavigate(); const [restorePVCName, setPVCName] = useState(`${getName(resource) || 'pvc'}-restore`); const volumeSnapshotAnnotations = getAnnotations(resource); const snapshotBaseSize = convertToBaseValue(resource?.status?.restoreSize ?? '0'); @@ -124,7 +126,7 @@ const RestorePVCModal = ({ close, cancel, resource }: RestorePVCModalProps) => { handlePromise(k8sCreate(PersistentVolumeClaimModel, restorePVCTemplate, { ns: namespace })) .then((newPVC) => { close(); - history.push( + navigate( resourcePathFromModel(PersistentVolumeClaimModel, newPVC.metadata.name, namespace), ); }) @@ -278,4 +280,10 @@ type RestorePVCModalProps = { resource: VolumeSnapshotKind; } & ModalComponentProps; -export default createModalLauncher(RestorePVCModal); +export const RestorePVCModalProvider: OverlayComponent = (props) => { + return ( + + + + ); +}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/app/modal-support/OverlayProvider.tsx b/frontend/packages/console-dynamic-plugin-sdk/src/app/modal-support/OverlayProvider.tsx index c31b61a756f..961663a6b0a 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/app/modal-support/OverlayProvider.tsx +++ b/frontend/packages/console-dynamic-plugin-sdk/src/app/modal-support/OverlayProvider.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { createContext, useState, useCallback } from 'react'; +import { createContext, useState, useCallback, Suspense } from 'react'; import * as _ from 'lodash'; import { UnknownProps } from '../common-types'; @@ -52,7 +52,9 @@ export const OverlayProvider: FC = ({ children }) => { return ( {_.map(componentsMap, (c, id) => ( - closeOverlay(id)} /> + + closeOverlay(id)} /> + ))} {children} diff --git a/frontend/public/components/app.tsx b/frontend/public/components/app.tsx index 4fae8fd33e0..5200b5d6a91 100644 --- a/frontend/public/components/app.tsx +++ b/frontend/public/components/app.tsx @@ -5,6 +5,7 @@ import type { FC, Provider as ProviderComponent, ReactNode } from 'react'; import { render } from 'react-dom'; import { Helmet, HelmetProvider } from 'react-helmet-async'; import { linkify } from 'react-linkify'; +import * as Modal from 'react-modal'; import { Provider, useSelector, useDispatch } from 'react-redux'; import { Router } from 'react-router-dom'; import { useParams, useLocation, CompatRouter, Routes, Route } from 'react-router-dom-v5-compat'; @@ -136,6 +137,14 @@ const App: FC<{ useCSPViolationDetector(); useNotificationPoller(); + // Initialize react-modal app element for accessibility + useLayoutEffect(() => { + const appElement = document.getElementById('app-content'); + if (appElement) { + Modal.setAppElement(appElement); + } + }, []); + useEffect(() => { window.addEventListener('resize', onResize); return () => { diff --git a/frontend/public/components/factory/modal.tsx b/frontend/public/components/factory/modal.tsx index 06b72acea1b..5eae57820f9 100644 --- a/frontend/public/components/factory/modal.tsx +++ b/frontend/public/components/factory/modal.tsx @@ -24,7 +24,7 @@ export const createModal: CreateModal = (getModalElement) => { ReactDOM.unmountComponentAtNode(containerElement); resolve(); }; - Modal.setAppElement(document.getElementById('app-content')); + // Modal app element is now set globally in App component containerElement && ReactDOM.render(getModalElement(closeModal), containerElement); }); return { result }; @@ -34,8 +34,10 @@ export const createModal: CreateModal = (getModalElement) => { export const ModalWrapper: FC = ({ blocking, className, children, onClose }) => { const { t } = useTranslation(); const parentSelector = useCallback(() => document.querySelector('#modal-container'), []); + const appElement = document.getElementById('app-content'); return ( { handlePromise(Promise.all([promise, ...extensionPromises])).then(() => { close(); - // Redirect to resourcce list page if the resouce is deleted. navigate(resourceListPathFromModel(PersistentVolumeClaimModel, pvc.metadata.namespace)); }); }; @@ -83,4 +83,10 @@ export type DeletePVCModalProps = { pvc: PersistentVolumeClaimKind; } & ModalComponentProps; -export default createModalLauncher(DeletePVCModal); +export const DeletePVCModalProvider: OverlayComponent = (props) => { + return ( + + + + ); +}; diff --git a/frontend/public/components/modals/expand-pvc-modal.tsx b/frontend/public/components/modals/expand-pvc-modal.tsx index 0735da9afab..855162b3001 100644 --- a/frontend/public/components/modals/expand-pvc-modal.tsx +++ b/frontend/public/components/modals/expand-pvc-modal.tsx @@ -1,8 +1,14 @@ import { useState, useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom-v5-compat'; - -import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter } from '../factory/modal'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; +import { + ModalTitle, + ModalBody, + ModalSubmitFooter, + ModalWrapper, + ModalComponentProps, +} from '../factory/modal'; import { RequestSizeInput } from '../utils/request-size-input'; import { resourceObjPath } from '../utils/resource-link'; import { validate, convertToBaseValue, humanizeBinaryBytesWithoutB } from '../utils/units'; @@ -85,11 +91,15 @@ const ExpandPVCModal = ({ resource, kind, close, cancel }: ExpandPVCModalProps) ); }; -export const expandPVCModal = createModalLauncher(ExpandPVCModal); - export type ExpandPVCModalProps = { kind: K8sKind; resource: K8sResourceKind; - cancel?: () => void; - close: () => void; +} & ModalComponentProps; + +export const ExpandPVCModalProvider: OverlayComponent = (props) => { + return ( + + + + ); }; diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 8b887e54310..69ff0976492 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -1,5 +1,7 @@ // This module utilizes dynamic `import()` to enable lazy-loading for each modal instead of including them in the main bundle. +import { lazy } from 'react'; + // Helper to detect if a modal is open. This is used to disable autofocus in elements under a modal. // TODO: Improve focus and keybinding handling, see https://issues.redhat.com/browse/ODC-3554 export const isModalOpen = () => document.body.classList.contains('ReactModal__Body--open'); @@ -47,8 +49,19 @@ export const configureUpdateStrategyModal = (props) => export const annotationsModalLauncher = (props) => import('./tags' /* webpackChunkName: "tags" */).then((m) => m.annotationsModalLauncher(props)); -export const deleteModal = (props) => - import('./delete-modal' /* webpackChunkName: "delete-modal" */).then((m) => m.deleteModal(props)); +// Lazy-loaded OverlayComponent for Delete Modal +export const LazyDeleteModalProvider = lazy(() => + import('./delete-modal' /* webpackChunkName: "delete-modal" */).then((m) => ({ + default: m.DeleteModalProvider, + })), +); + +// Lazy-loaded OverlayComponent for Delete PVC Modal +export const LazyDeletePVCModalProvider = lazy(() => + import('./delete-pvc-modal' /* webpackChunkName: "delete-pvc-modal" */).then((m) => ({ + default: m.DeletePVCModalProvider, + })), +); export const clusterChannelModal = (props) => import('./cluster-channel-modal' /* webpackChunkName: "cluster-channel-modal" */).then((m) => @@ -73,15 +86,19 @@ export const tolerationsModal = (props) => m.tolerationsModal(props), ); -export const expandPVCModal = (props) => - import('./expand-pvc-modal' /* webpackChunkName: "expand-pvc-modal" */).then((m) => - m.expandPVCModal(props), - ); +// Lazy-loaded OverlayComponent for Expand PVC Modal +export const LazyExpandPVCModalProvider = lazy(() => + import('./expand-pvc-modal' /* webpackChunkName: "expand-pvc-modal" */).then((m) => ({ + default: m.ExpandPVCModalProvider, + })), +); -export const clonePVCModal = (props) => +// Lazy-loaded OverlayComponent for Clone PVC Modal +export const LazyClonePVCModalProvider = lazy(() => import( '@console/app/src/components/modals/clone/clone-pvc-modal' /* webpackChunkName: "clone-pvc-modal" */ - ).then((m) => m.default(props)); + ).then((m) => ({ default: m.ClonePVCModalProvider })), +); export const configureClusterUpstreamModal = (props) => import( @@ -103,10 +120,12 @@ export const removeUserModal = (props) => m.removeUserModal(props), ); -export const restorePVCModal = (props) => +// Lazy-loaded OverlayComponent for Restore PVC Modal +export const LazyRestorePVCModalProvider = lazy(() => import( '@console/app/src/components/modals/restore-pvc/restore-pvc-modal' /* webpackChunkName: "restore-pvc-modal" */ - ).then((m) => m.default(props)); + ).then((m) => ({ default: m.RestorePVCModalProvider })), +); export const managedResourceSaveModal = (props) => import( From 62bc535b1f300e9cfb3e1cb6868a699eb9f1dc8c Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Tue, 27 Jan 2026 10:49:54 -0500 Subject: [PATCH 02/12] CONSOLE-5012: Migrate delete-modal and actions to overlay pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit completes the migration from createModalLauncher to the OverlayComponent pattern for delete modals and related actions. Changes: - Refactored delete-modal.tsx to use OverlayComponent pattern - Added LazyDeleteModalProvider with React.lazy() for code splitting - Converted action creators to hooks to comply with React rules: * DeleteResourceAction → useDeleteResourceAction (context-menu.ts) * deleteKnativeServiceResource → useDeleteKnativeServiceResource (creators.ts) - Updated all consumers to use new hook-based actions: * useCommonActions - uses LazyDeleteModalProvider with launchModal * deployment-provider - uses useDeleteResourceAction hook * deploymentconfig-provider - uses useDeleteResourceAction hook * knative-plugin providers - uses useDeleteKnativeServiceResource hook - Updated public/components/modals/index.ts with lazy export - Eliminated all deleteModal() function calls Note: DeleteApplicationAction remains as a non-hook function creator since it doesn't use modals and doesn't need to comply with hooks rules. This change maintains backward compatibility through default exports and preserves lazy loading functionality. Co-Authored-By: Claude Sonnet 4.5 --- .../src/actions/hooks/useCommonActions.ts | 8 ++- .../actions/providers/deployment-provider.ts | 6 +- .../providers/deploymentconfig-provider.ts | 6 +- .../dev-console/src/actions/context-menu.ts | 36 ++++++++---- .../knative-plugin/src/actions/creators.ts | 57 +++++++++++-------- .../knative-plugin/src/actions/providers.ts | 20 ++++++- .../public/components/modals/delete-modal.tsx | 21 +++++-- 7 files changed, 104 insertions(+), 50 deletions(-) diff --git a/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts b/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts index 20e9302b249..9459b757ac1 100644 --- a/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useCommonActions.ts @@ -1,10 +1,11 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Action } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize'; import { annotationsModalLauncher, - deleteModal, + LazyDeleteModalProvider, labelsModalLauncher, podSelectorModal, taintsModal, @@ -43,6 +44,7 @@ export const useCommonActions = ( editPath?: string, ): [ActionObject, boolean] => { const { t } = useTranslation(); + const launchModal = useOverlay(); const launchCountModal = useConfigureCountModal({ resourceKind: kind, resource, @@ -75,7 +77,7 @@ export const useCommonActions = ( id: 'delete-resource', label: t('console-app~Delete {{kind}}', { kind: kind?.kind }), cta: () => - deleteModal({ + launchModal(LazyDeleteModalProvider, { kind, resource, message, @@ -170,7 +172,7 @@ export const useCommonActions = ( }), }), // eslint-disable-next-line react-hooks/exhaustive-deps - [kind, resource, t, message, actualEditPath], + [kind, resource, t, message, actualEditPath, launchModal], ); const result = useMemo((): [ActionObject, boolean] => { diff --git a/frontend/packages/console-app/src/actions/providers/deployment-provider.ts b/frontend/packages/console-app/src/actions/providers/deployment-provider.ts index 9802d8ad741..c4a0c4d9077 100644 --- a/frontend/packages/console-app/src/actions/providers/deployment-provider.ts +++ b/frontend/packages/console-app/src/actions/providers/deployment-provider.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { DeleteResourceAction } from '@console/dev-console/src/actions/context-menu'; +import { useDeleteResourceAction } from '@console/dev-console/src/actions/context-menu'; import { Action } from '@console/dynamic-plugin-sdk/src'; import { DeploymentKind, referenceFor } from '@console/internal/module/k8s'; import { useK8sModel } from '@console/shared/src/hooks/useK8sModel'; @@ -14,6 +14,7 @@ export const useDeploymentActionsProvider = (resource: DeploymentKind) => { const [kindObj, inFlight] = useK8sModel(referenceFor(resource)); const [hpaActions, relatedHPAs] = useHPAActions(kindObj, resource); const [pdbActions] = usePDBActions(kindObj, resource); + const deleteResourceAction = useDeleteResourceAction(kindObj, resource); const [deploymentActionsObject, deploymentActionsReady] = useDeploymentActions( kindObj, resource, @@ -54,7 +55,7 @@ export const useDeploymentActionsProvider = (resource: DeploymentKind) => { deploymentActionsObject.EditDeployment, ...(resource.metadata?.annotations?.['openshift.io/generated-by'] === 'OpenShiftWebConsole' - ? [DeleteResourceAction(kindObj, resource)] + ? [deleteResourceAction] : [commonActions.Delete]), ]; }, [ @@ -66,6 +67,7 @@ export const useDeploymentActionsProvider = (resource: DeploymentKind) => { commonActions, isReady, deploymentActionsObject, + deleteResourceAction, ]); return [deploymentActions, !inFlight, undefined]; diff --git a/frontend/packages/console-app/src/actions/providers/deploymentconfig-provider.ts b/frontend/packages/console-app/src/actions/providers/deploymentconfig-provider.ts index 465018427c8..31900dd7355 100644 --- a/frontend/packages/console-app/src/actions/providers/deploymentconfig-provider.ts +++ b/frontend/packages/console-app/src/actions/providers/deploymentconfig-provider.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { DeleteResourceAction } from '@console/dev-console/src/actions/context-menu'; +import { useDeleteResourceAction } from '@console/dev-console/src/actions/context-menu'; import { DeploymentConfigKind, referenceFor } from '@console/internal/module/k8s'; import { useK8sModel } from '@console/shared/src/hooks/useK8sModel'; import { getHealthChecksAction } from '../creators/health-checks-factory'; @@ -15,6 +15,7 @@ export const useDeploymentConfigActionsProvider = (resource: DeploymentConfigKin const [hpaActions, relatedHPAs] = useHPAActions(kindObj, resource); const [pdbActions] = usePDBActions(kindObj, resource); const retryRolloutAction = useRetryRolloutAction(resource); + const deleteResourceAction = useDeleteResourceAction(kindObj, resource); const [deploymentActions, deploymentActionsReady] = useDeploymentActions(kindObj, resource, [ DeploymentActionCreator.StartDCRollout, DeploymentActionCreator.PauseRollout, @@ -50,7 +51,7 @@ export const useDeploymentConfigActionsProvider = (resource: DeploymentConfigKin deploymentActions.EditDeployment, ...(resource.metadata?.annotations?.['openshift.io/generated-by'] === 'OpenShiftWebConsole' - ? [DeleteResourceAction(kindObj, resource)] + ? [deleteResourceAction] : [commonActions.Delete]), ] : [], @@ -64,6 +65,7 @@ export const useDeploymentConfigActionsProvider = (resource: DeploymentConfigKin commonActions, deploymentActions, isReady, + deleteResourceAction, ], ); diff --git a/frontend/packages/dev-console/src/actions/context-menu.ts b/frontend/packages/dev-console/src/actions/context-menu.ts index 4d580b77529..1c8c8becf03 100644 --- a/frontend/packages/dev-console/src/actions/context-menu.ts +++ b/frontend/packages/dev-console/src/actions/context-menu.ts @@ -1,7 +1,10 @@ +import { useMemo } from 'react'; import i18next from 'i18next'; +import { useTranslation } from 'react-i18next'; import { Action, K8sModel } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { TopologyApplicationObject } from '@console/dynamic-plugin-sdk/src/extensions/topology-types'; -import { deleteModal } from '@console/internal/components/modals'; +import { LazyDeleteModalProvider } from '@console/internal/components/modals'; import { asAccessReview } from '@console/internal/components/utils'; import { K8sResourceKind } from '@console/internal/module/k8s'; import { deleteResourceModal } from '@console/shared'; @@ -36,14 +39,25 @@ export const DeleteApplicationAction = ( }; }; -export const DeleteResourceAction = (kind: K8sModel, obj: K8sResourceKind): Action => ({ - id: `delete-resource`, - label: i18next.t('devconsole~Delete {{kind}}', { kind: kind.kind }), - cta: () => - deleteModal({ - kind, - resource: obj, - deleteAllResources: () => cleanUpWorkload(obj), +export const useDeleteResourceAction = ( + kind: K8sModel | undefined, + obj: K8sResourceKind, +): Action => { + const { t } = useTranslation(); + const launchModal = useOverlay(); + + return useMemo( + () => ({ + id: `delete-resource`, + label: t('devconsole~Delete {{kind}}', { kind: kind?.kind }), + cta: () => + launchModal(LazyDeleteModalProvider, { + kind, + resource: obj, + deleteAllResources: () => cleanUpWorkload(obj), + }), + accessReview: asAccessReview(kind as K8sModel, obj, 'delete'), }), - accessReview: asAccessReview(kind, obj, 'delete'), -}); + [t, kind, obj, launchModal], + ); +}; diff --git a/frontend/packages/knative-plugin/src/actions/creators.ts b/frontend/packages/knative-plugin/src/actions/creators.ts index a5d07e7316b..340e4ebf750 100644 --- a/frontend/packages/knative-plugin/src/actions/creators.ts +++ b/frontend/packages/knative-plugin/src/actions/creators.ts @@ -1,7 +1,8 @@ import { useMemo } from 'react'; import i18next from 'i18next'; import { Action } from '@console/dynamic-plugin-sdk'; -import { deleteModal } from '@console/internal/components/modals'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; +import { LazyDeleteModalProvider } from '@console/internal/components/modals'; import { asAccessReview, resourceObjPath } from '@console/internal/components/utils'; import { truncateMiddle } from '@console/internal/components/utils/truncate-middle'; import { K8sKind, K8sResourceKind, referenceForModel } from '@console/internal/module/k8s'; @@ -116,32 +117,40 @@ export const editKnativeServiceResource = ( }; }; -export const deleteKnativeServiceResource = ( - kind: K8sKind, +export const useDeleteKnativeServiceResource = ( + kind: K8sKind | undefined, obj: K8sResourceKind, serviceTypeValue: ServiceTypeValue, serviceCreatedFromWebFlag: boolean, -): Action => ({ - id: `delete-resource`, - label: - serviceTypeValue === ServiceTypeValue.Function - ? i18next.t('knative-plugin~Delete Function') - : i18next.t('knative-plugin~Delete Service'), - cta: () => - deleteModal( - serviceCreatedFromWebFlag - ? { - kind, - resource: obj, - deleteAllResources: () => cleanUpWorkload(obj), - } - : { - kind, - resource: obj, - }, - ), - accessReview: asAccessReview(kind, obj, 'delete'), -}); +): Action => { + const launchModal = useOverlay(); + + return useMemo( + () => ({ + id: `delete-resource`, + label: + serviceTypeValue === ServiceTypeValue.Function + ? i18next.t('knative-plugin~Delete Function') + : i18next.t('knative-plugin~Delete Service'), + cta: () => + launchModal( + LazyDeleteModalProvider, + serviceCreatedFromWebFlag + ? { + kind, + resource: obj, + deleteAllResources: () => cleanUpWorkload(obj), + } + : { + kind, + resource: obj, + }, + ), + accessReview: asAccessReview(kind as K8sKind, obj, 'delete'), + }), + [kind, obj, serviceTypeValue, serviceCreatedFromWebFlag, launchModal], + ); +}; export const moveSinkSource = ( model: K8sKind, diff --git a/frontend/packages/knative-plugin/src/actions/providers.ts b/frontend/packages/knative-plugin/src/actions/providers.ts index a43f77c03cf..fac3bdd5af9 100644 --- a/frontend/packages/knative-plugin/src/actions/providers.ts +++ b/frontend/packages/knative-plugin/src/actions/providers.ts @@ -58,7 +58,7 @@ import { editKnativeService, moveSinkSource, editKnativeServiceResource, - deleteKnativeServiceResource, + useDeleteKnativeServiceResource, useDeleteRevisionAction, useSetTrafficDistributionAction, useMoveSinkPubsubAction, @@ -95,6 +95,18 @@ export const useKnativeServiceActionsProvider = (resource: K8sResourceKind) => { const serviceTypeValue = useContext(KnativeServiceTypeContext); const setTrafficDistributionAction = useSetTrafficDistributionAction(kindObj, resource); const testServerlessFunctionAction = useTestServerlessFunctionAction(kindObj, resource); + const deleteKnativeServiceFromWebAction = useDeleteKnativeServiceResource( + kindObj, + resource, + serviceTypeValue, + true, + ); + const deleteKnativeServiceAction = useDeleteKnativeServiceResource( + kindObj, + resource, + serviceTypeValue, + false, + ); const [deploymentActions, deploymentActionsReady] = useDeploymentActions(kindObj, resource, [ DeploymentActionCreator.EditResourceLimits, ] as const); @@ -119,8 +131,8 @@ export const useKnativeServiceActionsProvider = (resource: K8sResourceKind) => { editKnativeServiceResource(kindObj, resource, serviceTypeValue), ...(resource.metadata.annotations?.['openshift.io/generated-by'] === 'OpenShiftWebConsole' - ? [deleteKnativeServiceResource(kindObj, resource, serviceTypeValue, true)] - : [deleteKnativeServiceResource(kindObj, resource, serviceTypeValue, false)]), + ? [deleteKnativeServiceFromWebAction] + : [deleteKnativeServiceAction]), ...(resource?.metadata?.labels?.['function.knative.dev'] === 'true' ? [testServerlessFunctionAction] : []), @@ -134,6 +146,8 @@ export const useKnativeServiceActionsProvider = (resource: K8sResourceKind) => { commonActions, serviceTypeValue, testServerlessFunctionAction, + deleteKnativeServiceFromWebAction, + deleteKnativeServiceAction, ], ); diff --git a/frontend/public/components/modals/delete-modal.tsx b/frontend/public/components/modals/delete-modal.tsx index 0779f965e3c..ee3e00bdbe6 100644 --- a/frontend/public/components/modals/delete-modal.tsx +++ b/frontend/public/components/modals/delete-modal.tsx @@ -4,7 +4,14 @@ import { useState, useCallback, useEffect } from 'react'; import { Alert, Backdrop, Checkbox, Modal, ModalVariant } from '@patternfly/react-core'; import { Trans, useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom-v5-compat'; -import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter } from '../factory/modal'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; +import { + ModalTitle, + ModalBody, + ModalSubmitFooter, + ModalWrapper, + ModalComponentProps, +} from '../factory/modal'; import { resourceListPathFromModel, ResourceLink } from '../utils/resource-link'; import { k8sKill, @@ -173,15 +180,19 @@ export const DeleteOverlay: FC = (props) => { ) : null; }; -export const deleteModal = createModalLauncher(DeleteModal); +export const DeleteModalProvider: OverlayComponent = (props) => { + return ( + + + + ); +}; export type DeleteModalProps = { kind: K8sModel; resource: K8sResourceKind; - close?: () => void; redirectTo?: LocationDescriptor; message?: JSX.Element; - cancel?: () => void; btnText?: ReactNode; deleteAllResources?: () => Promise; -}; +} & ModalComponentProps; From 69babbabf072e33f5e75ae934c9ff0617bf9ef11 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Tue, 20 Jan 2026 15:49:53 -0500 Subject: [PATCH 03/12] CONSOLE-5012: Migrate configure-update-strategy-modal to overlay pattern This commit refactors configure-update-strategy-modal to use the OverlayComponent pattern, eliminating createModalLauncher. Changes: - Refactored configure-update-strategy-modal.tsx to use OverlayComponent - Added ConfigureUpdateStrategyModalProvider with ModalWrapper - Added LazyConfigureUpdateStrategyModalProvider with React.lazy() - Updated useDeploymentActions to use the new lazy provider - Removed deprecated configureUpdateStrategyModal from index.ts This change maintains backward compatibility through default exports and preserves lazy loading functionality. Co-Authored-By: Claude Sonnet 4.5 --- .../src/actions/hooks/useDeploymentActions.ts | 4 +-- .../configure-update-strategy-modal.tsx | 28 +++++++++++++++---- frontend/public/components/modals/index.ts | 8 ++++-- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts b/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts index df952300a0f..fd6ce0d8eb1 100644 --- a/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts @@ -4,7 +4,7 @@ import { Action } from '@console/dynamic-plugin-sdk'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize'; import { k8sPatchResource } from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource'; -import { configureUpdateStrategyModal } from '@console/internal/components/modals'; +import { LazyConfigureUpdateStrategyModalProvider } from '@console/internal/components/modals'; import { ErrorModal } from '@console/internal/components/modals/error-modal'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { resourceObjPath } from '@console/internal/components/utils/resource-link'; @@ -107,7 +107,7 @@ export const useDeploymentActions = ({ id: 'edit-update-strategy', label: t('console-app~Edit update strategy'), - cta: () => configureUpdateStrategyModal({ deployment: resource }), + cta: () => launchModal(LazyConfigureUpdateStrategyModalProvider, { deployment: resource }), accessReview: { group: kind?.apiGroup, resource: kind?.plural, diff --git a/frontend/public/components/modals/configure-update-strategy-modal.tsx b/frontend/public/components/modals/configure-update-strategy-modal.tsx index dc8c11cd8b7..1bbee4b2022 100644 --- a/frontend/public/components/modals/configure-update-strategy-modal.tsx +++ b/frontend/public/components/modals/configure-update-strategy-modal.tsx @@ -15,10 +15,16 @@ import { Tooltip, } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; - +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { k8sPatch, Patch, DeploymentUpdateStrategy, K8sResourceKind } from '../../module/k8s'; import { DeploymentModel } from '../../models'; -import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter } from '../factory/modal'; +import { + ModalTitle, + ModalBody, + ModalSubmitFooter, + ModalWrapper, + ModalComponentProps, +} from '../factory/modal'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; export const getNumberOrPercent = (value) => { @@ -216,7 +222,19 @@ export const ConfigureUpdateStrategyModal = ({ ); }; -export const configureUpdateStrategyModal = createModalLauncher(ConfigureUpdateStrategyModal); +export const ConfigureUpdateStrategyModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; export type ConfigureUpdateStrategyProps = { showDescription?: boolean; @@ -232,8 +250,6 @@ export type ConfigureUpdateStrategyProps = { export type ConfigureUpdateStrategyModalProps = { deployment: K8sResourceKind; - cancel?: () => void; - close?: () => void; -}; +} & ModalComponentProps; ConfigureUpdateStrategy.displayName = 'ConfigureUpdateStrategy'; diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 69ff0976492..b027595365f 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -41,10 +41,14 @@ export const rollbackModal = (props) => m.rollbackModal(props), ); -export const configureUpdateStrategyModal = (props) => +// Lazy-loaded OverlayComponent for Configure Update Strategy Modal +export const LazyConfigureUpdateStrategyModalProvider = lazy(() => import( './configure-update-strategy-modal' /* webpackChunkName: "configure-update-strategy-modal" */ - ).then((m) => m.configureUpdateStrategyModal(props)); + ).then((m) => ({ + default: m.ConfigureUpdateStrategyModalProvider, + })), +); export const annotationsModalLauncher = (props) => import('./tags' /* webpackChunkName: "tags" */).then((m) => m.annotationsModalLauncher(props)); From d5c97be54bb6fb3e8f713b814e127b1a467d2f48 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Tue, 20 Jan 2026 16:14:47 -0500 Subject: [PATCH 04/12] CONSOLE-5012: Migrate configure-ns-pull-secret-modal to overlay pattern This commit migrates the configure namespace pull secret modal from the legacy createModalLauncher pattern to the new OverlayComponent pattern. Changes: - Exported ConfigureNamespacePullSecretModalProvider as OverlayComponent - Updated index.ts to lazy-load the modal provider - Modified namespace.jsx to use useOverlay hook instead of direct modal call Co-Authored-By: Claude Sonnet 4.5 --- .../modals/configure-ns-pull-secret-modal.tsx | 17 +++++++++++++++-- frontend/public/components/modals/index.ts | 8 ++++++-- frontend/public/components/namespace.jsx | 10 ++++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/frontend/public/components/modals/configure-ns-pull-secret-modal.tsx b/frontend/public/components/modals/configure-ns-pull-secret-modal.tsx index 8532818499d..92d43804a7f 100644 --- a/frontend/public/components/modals/configure-ns-pull-secret-modal.tsx +++ b/frontend/public/components/modals/configure-ns-pull-secret-modal.tsx @@ -16,14 +16,15 @@ import { useTranslation } from 'react-i18next'; import type { FC, ChangeEvent, FormEvent } from 'react'; import { CONST } from '@console/shared'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { k8sPatchByName, k8sCreate, K8sResourceKind } from '../../module/k8s'; import { SecretModel, ServiceAccountModel } from '../../models'; import { useState, useCallback } from 'react'; import { - createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, + ModalWrapper, ModalComponentProps, } from '../factory/modal'; import { ResourceIcon } from '../utils/resource-icon'; @@ -313,4 +314,16 @@ const ConfigureNamespacePullSecret: FC = (pro ); }; -export const configureNamespacePullSecretModal = createModalLauncher(ConfigureNamespacePullSecret); +export const ConfigureNamespacePullSecretModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index b027595365f..44385625321 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -21,10 +21,14 @@ export const confirmModal = (props) => export const errorModal = (props) => import('./error-modal' /* webpackChunkName: "error-modal" */).then((m) => m.errorModal(props)); -export const configureNamespacePullSecretModal = (props) => +// Lazy-loaded OverlayComponent for Configure Namespace Pull Secret Modal +export const LazyConfigureNamespacePullSecretModalProvider = lazy(() => import( './configure-ns-pull-secret-modal' /* webpackChunkName: "configure-ns-pull-secret-modal" */ - ).then((m) => m.configureNamespacePullSecretModal(props)); + ).then((m) => ({ + default: m.ConfigureNamespacePullSecretModalProvider, + })), +); export const labelsModalLauncher = (props) => import('./labels-modal' /* webpackChunkName: "labels-modal" */).then((m) => diff --git a/frontend/public/components/namespace.jsx b/frontend/public/components/namespace.jsx index faa1ad3002b..3dfc26c39f5 100644 --- a/frontend/public/components/namespace.jsx +++ b/frontend/public/components/namespace.jsx @@ -41,6 +41,7 @@ import { DASH } from '@console/shared/src/constants/ui'; import { ByteDataTypes } from '@console/shared/src/graph-helper/data-utils'; import * as k8sActions from '@console/dynamic-plugin-sdk/src/app/k8s/actions/k8s'; import { useActivePerspective } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import PaneBody from '@console/shared/src/components/layout/PaneBody'; import { ConsoleLinkModel, @@ -71,7 +72,7 @@ import { import { navFactory } from './utils/horizontal-nav'; import { useAccessReview } from './utils/rbac'; import { Timestamp } from '@console/shared/src/components/datetime/Timestamp'; -import { configureNamespacePullSecretModal } from './modals'; +import { LazyConfigureNamespacePullSecretModalProvider } from './modals'; import { RoleBindingsPage } from './RBAC'; import { Bar } from './graphs/bar'; import { Area } from './graphs/area'; @@ -856,6 +857,7 @@ export const PullSecret = (props) => { const [error, setError] = useState(false); const { t } = useTranslation(); const { namespace, canViewSecrets } = props; + const launchModal = useOverlay(); useEffect(() => { k8sGet(ServiceAccountModel, 'default', namespace.metadata.name, {}) @@ -873,7 +875,11 @@ export const PullSecret = (props) => { }); }, [namespace.metadata.name]); - const modal = () => configureNamespacePullSecretModal({ namespace, pullSecret: undefined }); + const modal = () => + launchModal(LazyConfigureNamespacePullSecretModalProvider, { + namespace, + pullSecret: undefined, + }); const secrets = () => { if (error) { From d4853b8c60ef19ff4465dac4608698917c0889f8 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Tue, 20 Jan 2026 16:41:44 -0500 Subject: [PATCH 05/12] CONSOLE-5012: Migrate configure-cluster-upstream-modal to overlay pattern This commit migrates the configure cluster upstream modal from the legacy createModalLauncher pattern to the new OverlayComponent pattern and fixes react-modal accessibility warnings. Changes: - Exported ConfigureClusterUpstreamModalProvider as OverlayComponent - Updated index.ts to lazy-load the modal provider - Modified details-page.tsx to use useOverlay hook instead of direct modal call - Removed unnecessary TFunction prop from modal type definition - Fixed react-modal warnings by using useLayoutEffect for global app element setup - Added explicit appElement prop to ModalWrapper for robust timing handling Co-Authored-By: Claude Sonnet 4.5 --- .../configure-cluster-upstream-modal.tsx | 19 +++++++++++++++---- frontend/public/components/modals/index.ts | 8 ++++++-- .../public/components/utils/details-page.tsx | 6 ++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend/public/components/modals/configure-cluster-upstream-modal.tsx b/frontend/public/components/modals/configure-cluster-upstream-modal.tsx index 51e920b0787..73be2035ece 100644 --- a/frontend/public/components/modals/configure-cluster-upstream-modal.tsx +++ b/frontend/public/components/modals/configure-cluster-upstream-modal.tsx @@ -20,8 +20,9 @@ import { ModalComponentProps, ModalSubmitFooter, ModalTitle, - createModalLauncher, + ModalWrapper, } from '../factory/modal'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { ExternalLink } from '@console/shared/src/components/links/ExternalLink'; import { documentationURLs, @@ -30,7 +31,6 @@ import { isUpstream, } from '../utils/documentation'; import { useTranslation } from 'react-i18next'; -import { TFunction } from 'i18next'; import { CLUSTER_VERSION_DEFAULT_UPSTREAM_SERVER_URL_PLACEHOLDER } from '@console/shared/src/constants'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; @@ -166,9 +166,20 @@ export const ConfigureClusterUpstreamModal = (props: ConfigureClusterUpstreamMod ); }; -export const configureClusterUpstreamModal = createModalLauncher(ConfigureClusterUpstreamModal); +export const ConfigureClusterUpstreamModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; export type ConfigureClusterUpstreamModalProps = { cv: ClusterVersionKind; - t: TFunction; } & ModalComponentProps; diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 44385625321..0069ff7e687 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -108,10 +108,14 @@ export const LazyClonePVCModalProvider = lazy(() => ).then((m) => ({ default: m.ClonePVCModalProvider })), ); -export const configureClusterUpstreamModal = (props) => +// Lazy-loaded OverlayComponent for Configure Cluster Upstream Modal +export const LazyConfigureClusterUpstreamModalProvider = lazy(() => import( './configure-cluster-upstream-modal' /* webpackChunkName: "configure-cluster-upstream-modal" */ - ).then((m) => m.configureClusterUpstreamModal(props)); + ).then((m) => ({ + default: m.ConfigureClusterUpstreamModalProvider, + })), +); export const createAlertRoutingModal = (props) => import('./alert-routing-modal' /* webpackChunkName: "alert-routing-modal" */).then((m) => diff --git a/frontend/public/components/utils/details-page.tsx b/frontend/public/components/utils/details-page.tsx index 25377818427..e9db67b66e6 100644 --- a/frontend/public/components/utils/details-page.tsx +++ b/frontend/public/components/utils/details-page.tsx @@ -6,6 +6,7 @@ import { PencilAltIcon } from '@patternfly/react-icons/dist/esm/icons/pencil-alt import { useCanClusterUpgrade } from '@console/shared/src/hooks/useCanClusterUpgrade'; import { useAnnotationsModal } from '@console/shared/src/hooks/useAnnotationsModal'; import { useLabelsModal } from '@console/shared/src/hooks/useLabelsModal'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { DetailsItem } from './details-item'; import { LabelList } from './label-list'; import { OwnerReferences } from './owner-references'; @@ -21,7 +22,7 @@ import { referenceFor, Toleration, } from '../../module/k8s'; -import { configureClusterUpstreamModal } from '../modals'; +import { LazyConfigureClusterUpstreamModalProvider } from '../modals'; import { CommonActionCreator } from '@console/app/src/actions/hooks/types'; import { useCommonActions } from '@console/app/src/actions/hooks/useCommonActions'; @@ -206,6 +207,7 @@ export const RuntimeClass: FC = ({ obj, path }) => { export const UpstreamConfigDetailsItem: FC = ({ resource }) => { const { t } = useTranslation(); const canUpgrade = useCanClusterUpgrade(); + const launchModal = useOverlay(); return (
@@ -216,7 +218,7 @@ export const UpstreamConfigDetailsItem: FC = ({ onClick={(e) => { e.preventDefault(); e.stopPropagation(); - canUpgrade && configureClusterUpstreamModal({ cv: resource }); + canUpgrade && launchModal(LazyConfigureClusterUpstreamModalProvider, { cv: resource }); }} variant="link" isDisabled={!canUpgrade} From e20f92d1bb9f8856a14240a9d6310791a52fcf40 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 21 Jan 2026 11:05:10 -0500 Subject: [PATCH 06/12] CONSOLE-5012: Migrate managed-resource-save-modal to overlay pattern This commit migrates the managed resource save modal from the legacy createModalLauncher pattern to the new OverlayComponent pattern. Changes: - Exported ManagedResourceSaveModalProvider as OverlayComponent - Updated index.ts to lazy-load the modal provider - Modified edit-yaml.tsx to use useOverlay hook instead of direct modal call - Removed unnecessary 'kind' prop from modal invocation - Updated type definition to extend ModalComponentProps Co-Authored-By: Claude Sonnet 4.5 --- frontend/public/components/edit-yaml.tsx | 7 +++--- frontend/public/components/modals/index.ts | 8 +++++-- .../modals/managed-resource-save-modal.tsx | 22 +++++++++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frontend/public/components/edit-yaml.tsx b/frontend/public/components/edit-yaml.tsx index 4e1ec768d0e..5f66a9d7efd 100644 --- a/frontend/public/components/edit-yaml.tsx +++ b/frontend/public/components/edit-yaml.tsx @@ -31,7 +31,7 @@ import { } from '@console/dynamic-plugin-sdk'; import { useResolvedExtensions } from '@console/dynamic-plugin-sdk/src/api/useResolvedExtensions'; import { connectToFlags, WithFlagsProps } from '../reducers/connectToFlags'; -import { managedResourceSaveModal } from './modals'; +import { LazyManagedResourceSaveModalProvider } from './modals'; import ReplaceCodeModal from './modals/replace-code-modal'; import { checkAccess } from './utils/rbac'; import { Firehose } from './utils/firehose'; @@ -638,8 +638,7 @@ const EditYAMLInner: FC = (props) => { } if (owner) { - managedResourceSaveModal({ - kind: obj.kind, + launchModal(LazyManagedResourceSaveModalProvider, { resource: obj, onSubmit: () => updateYAML(obj), owner, @@ -648,7 +647,7 @@ const EditYAMLInner: FC = (props) => { } } updateYAML(obj); - }, [create, owner, t, updateYAML, validate, onSave, props.obj, editorMounted]); + }, [create, owner, t, updateYAML, validate, onSave, props.obj, editorMounted, launchModal]); const save = () => { setErrors([]); diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 0069ff7e687..c5da2bc9370 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -139,7 +139,11 @@ export const LazyRestorePVCModalProvider = lazy(() => ).then((m) => ({ default: m.RestorePVCModalProvider })), ); -export const managedResourceSaveModal = (props) => +// Lazy-loaded OverlayComponent for Managed Resource Save Modal +export const LazyManagedResourceSaveModalProvider = lazy(() => import( './managed-resource-save-modal' /* webpackChunkName: "managed-resource-save-modal" */ - ).then((m) => m.managedResourceSaveModal(props)); + ).then((m) => ({ + default: m.ManagedResourceSaveModalProvider, + })), +); diff --git a/frontend/public/components/modals/managed-resource-save-modal.tsx b/frontend/public/components/modals/managed-resource-save-modal.tsx index 29bfbb334e7..b7701108fae 100644 --- a/frontend/public/components/modals/managed-resource-save-modal.tsx +++ b/frontend/public/components/modals/managed-resource-save-modal.tsx @@ -1,9 +1,16 @@ import type { FC } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter } from '../factory/modal'; +import { + ModalTitle, + ModalBody, + ModalSubmitFooter, + ModalComponentProps, + ModalWrapper, +} from '../factory/modal'; import { referenceForOwnerRef, K8sResourceCommon, OwnerReference } from '../../module/k8s/'; import { YellowExclamationTriangleIcon } from '@console/shared/src/components/status/icons'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { ResourceLink } from '../utils/resource-link'; @@ -39,11 +46,18 @@ const ManagedResourceSaveModal: FC = (props) => { ); }; -export const managedResourceSaveModal = createModalLauncher(ManagedResourceSaveModal); +export const ManagedResourceSaveModalProvider: OverlayComponent = ( + props, +) => { + return ( + + + + ); +}; type ManagedResourceSaveModalProps = { onSubmit: () => void; - close: () => void; resource: K8sResourceCommon; owner: OwnerReference; -}; +} & ModalComponentProps; From a83171160fb99727ff8c98698a2ebe9368a4ec01 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 21 Jan 2026 11:09:45 -0500 Subject: [PATCH 07/12] CONSOLE-5012: Remove orphaned remove-user-modal The remove-user-modal was already replaced by useWarningModal in group.tsx and is no longer used anywhere in the codebase. Changes: - Deleted remove-user-modal.tsx - Removed LazyRemoveUserModalProvider export from index.ts Co-Authored-By: Claude Sonnet 4.5 --- frontend/public/components/modals/index.ts | 5 -- .../components/modals/remove-user-modal.tsx | 58 ------------------- 2 files changed, 63 deletions(-) delete mode 100644 frontend/public/components/modals/remove-user-modal.tsx diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index c5da2bc9370..89a5340ae2b 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -127,11 +127,6 @@ export const createColumnManagementModal = (props) => m.createColumnManagementModal(props), ); -export const removeUserModal = (props) => - import('./remove-user-modal' /* webpackChunkName: "remove-user-modal" */).then((m) => - m.removeUserModal(props), - ); - // Lazy-loaded OverlayComponent for Restore PVC Modal export const LazyRestorePVCModalProvider = lazy(() => import( diff --git a/frontend/public/components/modals/remove-user-modal.tsx b/frontend/public/components/modals/remove-user-modal.tsx deleted file mode 100644 index 9532f06d9d8..00000000000 --- a/frontend/public/components/modals/remove-user-modal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as _ from 'lodash'; -import type { FormEventHandler } from 'react'; -import { YellowExclamationTriangleIcon } from '@console/shared/src/components/status/icons'; - -import { GroupModel } from '../../models'; -import { GroupKind, k8sPatch } from '../../module/k8s'; -import { - ModalBody, - ModalComponentProps, - ModalSubmitFooter, - ModalTitle, - createModalLauncher, -} from '../factory/modal'; -import { useTranslation } from 'react-i18next'; -import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; - -export const RemoveUserModal = (props: RemoveUserModalProps) => { - const [handlePromise, inProgress, errorMessage] = usePromiseHandler(); - - const submit: FormEventHandler = (e): void => { - e.preventDefault(); - const value = _.filter(props.group.users, (user: string) => user !== props.user); - const patch = [{ op: 'replace', path: '/users', value }]; - handlePromise(k8sPatch(GroupModel, props.group, patch)) - .then(() => props.close()) - .catch(() => {}); - }; - - const { t } = useTranslation(); - return ( -
- - - {t('public~Remove User from Group?')} - - - {t('public~Remove User {{ user }} from Group {{ name }}?', { - user: props.user, - name: props.group.metadata.name, - })} - - - - ); -}; - -export const removeUserModal = createModalLauncher(RemoveUserModal); - -export type RemoveUserModalProps = { - group: GroupKind; - user: string; -} & ModalComponentProps; From 7d1ce8d17beaee752d52a29c31f4a7984f76b6fd Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 21 Jan 2026 11:37:08 -0500 Subject: [PATCH 08/12] CONSOLE-5012: Migrate ConfigureUnschedulableModal to overlay pattern This commit migrates the ConfigureUnschedulableModal from the legacy createModalLauncher pattern to the new OverlayComponent pattern. Changes: - Exported ConfigureUnschedulableModalProvider as OverlayComponent - Updated modals/index.ts to lazy-load the modal provider - Modified useNodeActions hook to use useOverlay hook - Removed createModalLauncher usage Co-Authored-By: Claude Sonnet 4.5 --- .../src/components/nodes/menu-actions.tsx | 8 +++++--- .../modals/ConfigureUnschedulableModal.tsx | 20 ++++++++++++++----- .../src/components/nodes/modals/index.ts | 10 +++++++--- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/frontend/packages/console-app/src/components/nodes/menu-actions.tsx b/frontend/packages/console-app/src/components/nodes/menu-actions.tsx index c8683b9cd01..dd09aa188a7 100644 --- a/frontend/packages/console-app/src/components/nodes/menu-actions.tsx +++ b/frontend/packages/console-app/src/components/nodes/menu-actions.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useCommonResourceActions } from '@console/app/src/actions//hooks/useCommonResourceActions'; import { Action } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { useK8sModel } from '@console/dynamic-plugin-sdk/src/lib-core'; import { k8sUpdateResource } from '@console/dynamic-plugin-sdk/src/utils/k8s'; import { asAccessReview } from '@console/internal/components/utils/rbac'; @@ -14,7 +15,7 @@ import { } from '@console/internal/module/k8s'; import { isNodeUnschedulable } from '@console/shared/src/selectors/node'; import { makeNodeSchedulable } from '../../k8s/requests/nodes'; -import { createConfigureUnschedulableModal } from './modals'; +import { LazyConfigureUnschedulableModalProvider } from './modals'; const updateCSR = (csr: CertificateSigningRequestKind, type: 'Approved' | 'Denied') => { const approvedCSR = { @@ -47,6 +48,7 @@ export const denyCSR = (csr: CertificateSigningRequestKind) => updateCSR(csr, 'D export const useNodeActions: ExtensionHook = (obj) => { const [kindObj, inFlight] = useK8sModel(referenceFor(obj)); const { t } = useTranslation(); + const launchModal = useOverlay(); const deleteMessage = useMemo( () => (

@@ -71,14 +73,14 @@ export const useNodeActions: ExtensionHook = (obj) => { actions.push({ id: 'mark-as-unschedulable', label: t('console-app~Mark as unschedulable'), - cta: () => createConfigureUnschedulableModal({ resource: obj }), + cta: () => launchModal(LazyConfigureUnschedulableModalProvider, { resource: obj }), accessReview: asAccessReview(kindObj, obj, 'patch'), }); } actions.push(...commonActions); return actions; - }, [kindObj, obj, t, commonActions]); + }, [kindObj, obj, t, commonActions, launchModal]); return [nodeActions, !inFlight, undefined]; }; diff --git a/frontend/packages/console-app/src/components/nodes/modals/ConfigureUnschedulableModal.tsx b/frontend/packages/console-app/src/components/nodes/modals/ConfigureUnschedulableModal.tsx index d9716ee353b..80f6ddbb94f 100644 --- a/frontend/packages/console-app/src/components/nodes/modals/ConfigureUnschedulableModal.tsx +++ b/frontend/packages/console-app/src/components/nodes/modals/ConfigureUnschedulableModal.tsx @@ -1,10 +1,12 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { ModalTitle, ModalBody, ModalSubmitFooter, - createModalLauncher, + ModalComponentProps, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { NodeKind } from '@console/internal/module/k8s'; import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; @@ -12,9 +14,7 @@ import { makeNodeUnschedulable } from '../../../k8s/requests/nodes'; type ConfigureUnschedulableModalProps = { resource: NodeKind; - cancel?: () => void; - close?: () => void; -}; +} & ModalComponentProps; const ConfigureUnschedulableModal: FC = ({ resource, @@ -48,4 +48,14 @@ const ConfigureUnschedulableModal: FC = ({ ); }; -export default createModalLauncher(ConfigureUnschedulableModal); +export const ConfigureUnschedulableModalProvider: OverlayComponent = ( + props, +) => ( + + + +); diff --git a/frontend/packages/console-app/src/components/nodes/modals/index.ts b/frontend/packages/console-app/src/components/nodes/modals/index.ts index 222850138c8..f32de198f9f 100644 --- a/frontend/packages/console-app/src/components/nodes/modals/index.ts +++ b/frontend/packages/console-app/src/components/nodes/modals/index.ts @@ -1,6 +1,10 @@ -import { NodeKind } from '@console/internal/module/k8s'; +import { lazy } from 'react'; -export const createConfigureUnschedulableModal = (props: { resource: NodeKind }) => +// Lazy-loaded OverlayComponent for Configure Unschedulable Modal +export const LazyConfigureUnschedulableModalProvider = lazy(() => import( './ConfigureUnschedulableModal' /* webpackChunkName: "configure-unschedulable-modal" */ - ).then((m) => m.default(props)); + ).then((m) => ({ + default: m.ConfigureUnschedulableModalProvider, + })), +); From edba5f82b0104fa2cf3676d7b24e4fa51961a602 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 21 Jan 2026 11:42:06 -0500 Subject: [PATCH 09/12] CONSOLE-5012: Migrate DeletePDBModal to overlay pattern Co-Authored-By: Claude Sonnet 4.5 --- .../console-app/src/actions/hooks/usePDBActions.ts | 13 +++++++------ .../src/components/pdb/modals/DeletePDBModal.tsx | 9 +++++++-- .../console-app/src/components/pdb/modals/index.ts | 12 +++++++----- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/frontend/packages/console-app/src/actions/hooks/usePDBActions.ts b/frontend/packages/console-app/src/actions/hooks/usePDBActions.ts index bd3045a587c..422ccb34567 100644 --- a/frontend/packages/console-app/src/actions/hooks/usePDBActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/usePDBActions.ts @@ -2,11 +2,12 @@ import { useMemo } from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { Action } from '@console/dynamic-plugin-sdk'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { useDeepCompareMemoize } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useDeepCompareMemoize'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import { asAccessReview } from '@console/internal/components/utils/rbac'; import { K8sPodControllerKind, K8sModel, referenceFor } from '@console/internal/module/k8s'; -import { deletePDBModal } from '../../components/pdb/modals'; +import { LazyDeletePDBModalProvider } from '../../components/pdb/modals'; import { PodDisruptionBudgetKind } from '../../components/pdb/types'; import { getPDBResource } from '../../components/pdb/utils/get-pdb-resources'; import { PodDisruptionBudgetModel } from '../../models'; @@ -42,6 +43,7 @@ export const usePDBActions = ( const namespace = resource?.metadata?.namespace; const memoizedFilterActions = useDeepCompareMemoize(filterActions); const { t } = useTranslation(); + const launchModal = useOverlay(); const watchedResource = useMemo( () => ({ isList: true, @@ -88,16 +90,15 @@ export const usePDBActions = ( id: 'delete-pdb', label: t('console-app~Remove PodDisruptionBudget'), insertBefore: 'edit-resource-limits', - cta: () => { - deletePDBModal({ + cta: () => + launchModal(LazyDeletePDBModalProvider, { workloadName: resource.metadata.name, pdb: matchedPDB, - }); - }, + }), accessReview: asAccessReview(kindObj, resource, 'delete'), }), }; - }, [loaded, kindObj, resource, matchedPDB, t]); + }, [loaded, kindObj, resource, matchedPDB, t, launchModal]); const actions = useMemo(() => { if (!loaded || !kindObj || !resource) return []; diff --git a/frontend/packages/console-app/src/components/pdb/modals/DeletePDBModal.tsx b/frontend/packages/console-app/src/components/pdb/modals/DeletePDBModal.tsx index 18a7121fb07..b8e27425b6f 100644 --- a/frontend/packages/console-app/src/components/pdb/modals/DeletePDBModal.tsx +++ b/frontend/packages/console-app/src/components/pdb/modals/DeletePDBModal.tsx @@ -4,12 +4,13 @@ import { Form } from '@patternfly/react-core'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import { t_global_icon_color_status_warning_default as warningColor } from '@patternfly/react-tokens'; import { useTranslation, Trans } from 'react-i18next'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { - createModalLauncher, ModalBody, ModalComponentProps, ModalSubmitFooter, ModalTitle, + ModalWrapper, } from '@console/internal/components/factory/modal'; import { LoadingInline } from '@console/internal/components/utils/status-box'; import { k8sKill } from '@console/internal/module/k8s'; @@ -72,7 +73,11 @@ const DeletePDBModal: FC = ({ close, pdb, workloadName }) = ); }; -export const deletePDBModal = createModalLauncher(DeletePDBModal); +export const DeletePDBModalProvider: OverlayComponent = (props) => ( + + + +); export type DeletePDBModalProps = ModalComponentProps & { pdb: PodDisruptionBudgetKind; diff --git a/frontend/packages/console-app/src/components/pdb/modals/index.ts b/frontend/packages/console-app/src/components/pdb/modals/index.ts index 6643d2aa108..e504927fcca 100644 --- a/frontend/packages/console-app/src/components/pdb/modals/index.ts +++ b/frontend/packages/console-app/src/components/pdb/modals/index.ts @@ -1,6 +1,8 @@ -import { DeletePDBModalProps } from './DeletePDBModal'; +import { lazy } from 'react'; -export const deletePDBModal = (props: DeletePDBModalProps) => - import('./DeletePDBModal' /* webpackChunkName: "shared-modals" */).then((m) => - m.deletePDBModal(props), - ); +// Lazy-loaded OverlayComponent for Delete PDB Modal +export const LazyDeletePDBModalProvider = lazy(() => + import('./DeletePDBModal' /* webpackChunkName: "delete-pdb-modal" */).then((m) => ({ + default: m.DeletePDBModalProvider, + })), +); From 1d6376ee53fc5ae9fbed815b73d754d37ebe8dc6 Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Wed, 21 Jan 2026 11:46:51 -0500 Subject: [PATCH 10/12] CONSOLE-5012: Migrate ResourceLimitsModal to overlay pattern Co-Authored-By: Claude Sonnet 4.5 --- .../src/actions/hooks/useDeploymentActions.ts | 4 ++-- .../ResourceLimitsModalLauncher.tsx | 11 ++++++++--- .../src/components/modals/resource-limits/index.ts | 14 ++++++++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts b/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts index fd6ce0d8eb1..5d3d274ada5 100644 --- a/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useDeploymentActions.ts @@ -16,7 +16,7 @@ import { k8sCreate, K8sModel, } from '@console/internal/module/k8s'; -import { resourceLimitsModal } from '../../components/modals/resource-limits'; +import { LazyResourceLimitsModalProvider } from '../../components/modals/resource-limits'; import { DeploymentActionCreator, ActionObject } from './types'; const restartRollout = (model: K8sModel | undefined, obj: K8sResourceKind | undefined) => { @@ -171,7 +171,7 @@ export const useDeploymentActions = - resourceLimitsModal({ + launchModal(LazyResourceLimitsModalProvider, { model: kind, resource, }), diff --git a/frontend/packages/console-app/src/components/modals/resource-limits/ResourceLimitsModalLauncher.tsx b/frontend/packages/console-app/src/components/modals/resource-limits/ResourceLimitsModalLauncher.tsx index 4349768d61c..a329532180d 100644 --- a/frontend/packages/console-app/src/components/modals/resource-limits/ResourceLimitsModalLauncher.tsx +++ b/frontend/packages/console-app/src/components/modals/resource-limits/ResourceLimitsModalLauncher.tsx @@ -4,7 +4,8 @@ import { TFunction } from 'i18next'; import { useTranslation } from 'react-i18next'; import * as yup from 'yup'; import { limitsValidationSchema } from '@console/dev-console/src/components/import/validation-schema'; -import { createModalLauncher, ModalComponentProps } from '@console/internal/components/factory'; +import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; +import { ModalComponentProps, ModalWrapper } from '@console/internal/components/factory'; import { K8sKind, k8sPatch, K8sResourceKind } from '@console/internal/module/k8s'; import { getLimitsDataFromResource, getResourceLimitsData } from '@console/shared/src'; import ResourceLimitsModal from './ResourceLimitsModal'; @@ -60,6 +61,10 @@ const ResourceLimitsModalLauncher: FC = (props ); }; -export const resourceLimitsModal = createModalLauncher( - (props: ResourceLimitsModalLauncherProps) => , +export const ResourceLimitsModalProvider: OverlayComponent = ( + props, +) => ( + + + ); diff --git a/frontend/packages/console-app/src/components/modals/resource-limits/index.ts b/frontend/packages/console-app/src/components/modals/resource-limits/index.ts index 6329d442d18..37e57a734ac 100644 --- a/frontend/packages/console-app/src/components/modals/resource-limits/index.ts +++ b/frontend/packages/console-app/src/components/modals/resource-limits/index.ts @@ -1,4 +1,10 @@ -export const resourceLimitsModal = (props) => - import( - './ResourceLimitsModalLauncher' /* webpackChunkName: "resource-limits-modal" */ - ).then((m) => m.resourceLimitsModal(props)); +import { lazy } from 'react'; + +// Lazy-loaded OverlayComponent for Resource Limits Modal +export const LazyResourceLimitsModalProvider = lazy(() => + import('./ResourceLimitsModalLauncher' /* webpackChunkName: "resource-limits-modal" */).then( + (m) => ({ + default: m.ResourceLimitsModalProvider, + }), + ), +); From 6f9a46a1321dffca0a5d776dd19ff4b446cf26cc Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Thu, 22 Jan 2026 07:50:52 -0500 Subject: [PATCH 11/12] CONSOLE-5012: Create shared global error modal launcher utility - Add error-modal-handler.ts to console-shared package with: - launchGlobalErrorModal() for launching modals from non-React contexts - useSetupGlobalErrorModalLauncher() hook for initialization - setGlobalErrorModalLauncher() for managing global state - Migrate all packages to use shared launcher: - topology: Update Topology.tsx, componentUtils.ts, edgeActions.ts - knative-plugin: Update knativeComponentUtils.ts, create-connector-utils.ts - shipwright-plugin: Update logs-utils.ts, BuildRunDetailsPage.tsx - dev-console: Update pipeline-template-utils.ts, ImportPage.tsx, DeployImagePage.tsx - Initialize launcher in root components: - topology: Topology.tsx (for topology drag/drop contexts) - shipwright-plugin: BuildRunDetailsPage.tsx (for standalone log viewing) - dev-console: ImportPage.tsx, DeployImagePage.tsx (for import flows) - Remove topology-specific errorModalHandler implementation - Remove deprecated errorModal function from public/components/modals This consolidates error modal launching into a single reusable utility, eliminating code duplication across packages and ensuring error modals work correctly in all contexts (topology, standalone pages, import flows). Co-Authored-By: Claude Sonnet 4.5 --- .../src/utils/error-modal-handler.ts | 51 ++++++++++++++ .../console-shared/src/utils/index.ts | 1 + .../src/components/import/DeployImagePage.tsx | 2 + .../src/components/import/ImportPage.tsx | 2 + .../__tests__/pipeline-template-utils.spec.ts | 1 + .../pipeline/pipeline-template-utils.ts | 4 +- .../components/knativeComponentUtils.ts | 11 ++- .../src/topology/create-connector-utils.ts | 5 +- .../buildrun-details/BuildRunDetailsPage.tsx | 2 + .../src/components/logs/logs-utils.ts | 6 +- .../topology/locales/en/topology.json | 1 + .../topology/src/actions/edgeActions.ts | 9 ++- .../src/components/graph-view/Topology.tsx | 6 ++ .../graph-view/components/componentUtils.ts | 12 ++-- .../topology/src/utils/moveNodeToGroup.tsx | 67 +++++++++++++++++-- .../public/components/modals/error-modal.tsx | 11 +-- frontend/public/components/modals/index.ts | 4 -- 17 files changed, 157 insertions(+), 38 deletions(-) create mode 100644 frontend/packages/console-shared/src/utils/error-modal-handler.ts diff --git a/frontend/packages/console-shared/src/utils/error-modal-handler.ts b/frontend/packages/console-shared/src/utils/error-modal-handler.ts new file mode 100644 index 00000000000..c26fd8a349f --- /dev/null +++ b/frontend/packages/console-shared/src/utils/error-modal-handler.ts @@ -0,0 +1,51 @@ +import { useEffect } from 'react'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; +import { ErrorModal, ErrorModalProps } from '@console/internal/components/modals/error-modal'; + +// Module-level error modal launcher that can be set by root components +let globalErrorModalLauncher: ((props: ErrorModalProps) => void) | null = null; + +export const setGlobalErrorModalLauncher = ( + launcher: ((props: ErrorModalProps) => void) | null, +) => { + globalErrorModalLauncher = launcher; +}; + +export const launchGlobalErrorModal = (props: ErrorModalProps) => { + if (globalErrorModalLauncher) { + globalErrorModalLauncher(props); + } else { + // eslint-disable-next-line no-console + console.error('Global error modal launcher not initialized:', props); + } +}; + +/** + * Hook that sets up a global error modal launcher for non-React contexts. + * This should be called once in the root component of a package/feature. + * + * @example + * ```tsx + * const MyRootComponent = () => { + * useSetupGlobalErrorModalLauncher(); + * // ... rest of component + * }; + * ``` + */ +export const useSetupGlobalErrorModalLauncher = () => { + const launcher = useOverlay(); + + useEffect(() => { + const errorModalLauncher = (props: ErrorModalProps) => { + launcher(ErrorModal, props); + }; + + setGlobalErrorModalLauncher(errorModalLauncher); + + return () => { + setGlobalErrorModalLauncher(null); + }; + // Intentionally only run once on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +}; diff --git a/frontend/packages/console-shared/src/utils/index.ts b/frontend/packages/console-shared/src/utils/index.ts index b05617e4664..fee7f597cd7 100644 --- a/frontend/packages/console-shared/src/utils/index.ts +++ b/frontend/packages/console-shared/src/utils/index.ts @@ -22,3 +22,4 @@ export * from './keyword-filter'; export * from './order-extensions'; export * from './console-plugin'; export * from './default-categories'; +export * from './error-modal-handler'; diff --git a/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx b/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx index f539f7dde55..fedfbe04166 100644 --- a/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx +++ b/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx @@ -2,6 +2,7 @@ import type { FunctionComponent } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useLocation } from 'react-router-dom-v5-compat'; import { Firehose } from '@console/internal/components/utils'; +import { useSetupGlobalErrorModalLauncher } from '@console/shared'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { PageHeading } from '@console/shared/src/components/heading/PageHeading'; import { QUERY_PROPERTIES } from '../../const'; @@ -10,6 +11,7 @@ import QueryFocusApplication from '../QueryFocusApplication'; import DeployImage from './DeployImage'; const DeployImagePage: FunctionComponent = () => { + useSetupGlobalErrorModalLauncher(); const { t } = useTranslation(); const { ns: namespace } = useParams(); const location = useLocation(); diff --git a/frontend/packages/dev-console/src/components/import/ImportPage.tsx b/frontend/packages/dev-console/src/components/import/ImportPage.tsx index e988c3d2e76..71f88105dfd 100644 --- a/frontend/packages/dev-console/src/components/import/ImportPage.tsx +++ b/frontend/packages/dev-console/src/components/import/ImportPage.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { useParams, useLocation } from 'react-router-dom-v5-compat'; import { Firehose, FirehoseResource } from '@console/internal/components/utils'; import { ImageStreamModel, ProjectModel } from '@console/internal/models'; +import { useSetupGlobalErrorModalLauncher } from '@console/shared'; import DevPreviewBadge from '@console/shared/src/components/badges/DevPreviewBadge'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { PageHeading } from '@console/shared/src/components/heading/PageHeading'; @@ -35,6 +36,7 @@ const ImportFlows = (t: TFunction): { [name: string]: ImportData } => ({ }); const ImportPage: FunctionComponent = () => { + useSetupGlobalErrorModalLauncher(); const { t } = useTranslation(); const { ns: namespace } = useParams(); const location = useLocation(); diff --git a/frontend/packages/dev-console/src/components/pipeline-section/pipeline/__tests__/pipeline-template-utils.spec.ts b/frontend/packages/dev-console/src/components/pipeline-section/pipeline/__tests__/pipeline-template-utils.spec.ts index 7450e69abec..7f708343cdc 100644 --- a/frontend/packages/dev-console/src/components/pipeline-section/pipeline/__tests__/pipeline-template-utils.spec.ts +++ b/frontend/packages/dev-console/src/components/pipeline-section/pipeline/__tests__/pipeline-template-utils.spec.ts @@ -19,6 +19,7 @@ import { jest.mock('@console/internal/module/k8s', () => ({ k8sCreate: jest.fn(), k8sUpdate: jest.fn(), + referenceForModel: jest.fn((model) => model?.kind || 'UnknownModel'), })); const getDefaultLabel = (name: string) => ({ diff --git a/frontend/packages/dev-console/src/components/pipeline-section/pipeline/pipeline-template-utils.ts b/frontend/packages/dev-console/src/components/pipeline-section/pipeline/pipeline-template-utils.ts index 20c7e3efdd9..5f88bd5eb46 100644 --- a/frontend/packages/dev-console/src/components/pipeline-section/pipeline/pipeline-template-utils.ts +++ b/frontend/packages/dev-console/src/components/pipeline-section/pipeline/pipeline-template-utils.ts @@ -3,7 +3,6 @@ import * as _ from 'lodash'; import { compare, gte, parse, SemVer } from 'semver'; import { k8sGet, k8sList, k8sListResourceItems } from '@console/dynamic-plugin-sdk/src/utils/k8s'; import { getActiveUserName } from '@console/internal/actions/ui'; -import { errorModal } from '@console/internal/components/modals/error-modal'; import { ClusterServiceVersionModel, RouteModel, @@ -24,6 +23,7 @@ import { ClusterServiceVersionKind, ClusterServiceVersionPhase, } from '@console/operator-lifecycle-manager/src/types'; +import { launchGlobalErrorModal } from '@console/shared'; import { NameValueFromPair, NameValuePair, @@ -928,7 +928,7 @@ export const exposeRoute = async (elName: string, ns: string, iteration = 0) => ); await k8sCreate(RouteModel, route, { ns }); } catch (e) { - errorModal({ + launchGlobalErrorModal({ title: 'Error Exposing Route', error: e.message || 'Unknown error exposing the Webhook route', }); diff --git a/frontend/packages/knative-plugin/src/topology/components/knativeComponentUtils.ts b/frontend/packages/knative-plugin/src/topology/components/knativeComponentUtils.ts index 1883d25a687..4aaf9d421d1 100644 --- a/frontend/packages/knative-plugin/src/topology/components/knativeComponentUtils.ts +++ b/frontend/packages/knative-plugin/src/topology/components/knativeComponentUtils.ts @@ -13,7 +13,7 @@ import { isGraph, } from '@patternfly/react-topology'; import i18next from 'i18next'; -import { errorModal } from '@console/internal/components/modals'; +import { launchGlobalErrorModal } from '@console/shared'; import { NodeComponentProps, NODE_DRAG_TYPE, @@ -188,10 +188,9 @@ export const eventSourceLinkDragSourceSpec = (): DragSourceSpec< canDropEventSourceSinkOnNode(monitor.getOperation().type, edge, dropResult) ) { createSinkConnection(edge.getSource(), dropResult).catch((error) => { - errorModal({ + launchGlobalErrorModal({ title: i18next.t('knative-plugin~Error moving event source sink'), error: error.message, - showIcon: true, }); }); } @@ -222,10 +221,9 @@ export const eventSourceKafkaLinkDragSourceSpec = (): DragSourceSpec< edge.setEndPoint(); if (monitor.didDrop() && dropResult) { createEventSourceKafkaConnection(edge.getSource(), dropResult).catch((error) => { - errorModal({ + launchGlobalErrorModal({ title: i18next.t('knative-plugin~Error moving event source kafka connector'), error: error?.message, - showIcon: true, }); }); } @@ -260,10 +258,9 @@ export const eventingPubSubLinkDragSourceSpec = (): DragSourceSpec< canDropPubSubSinkOnNode(monitor.getOperation().type, edge, dropResult) ) { createSinkPubSubConnection(edge, dropResult).catch((error) => { - errorModal({ + launchGlobalErrorModal({ title: i18next.t('knative-plugin~Error while sink'), error: error.message, - showIcon: true, }); }); } diff --git a/frontend/packages/knative-plugin/src/topology/create-connector-utils.ts b/frontend/packages/knative-plugin/src/topology/create-connector-utils.ts index a597110735c..a41962f3912 100644 --- a/frontend/packages/knative-plugin/src/topology/create-connector-utils.ts +++ b/frontend/packages/knative-plugin/src/topology/create-connector-utils.ts @@ -1,6 +1,6 @@ import { Node } from '@patternfly/react-topology/src/types'; import i18next from 'i18next'; -import { errorModal } from '@console/internal/components/modals'; +import { launchGlobalErrorModal } from '@console/shared'; import { getResource } from '@console/topology/src/utils'; import { addPubSubConnectionModal } from '../components/pub-sub/PubSubModalLauncher'; import { createEventSourceKafkaConnection } from './knative-topology-utils'; @@ -15,10 +15,9 @@ const createKafkaConnection = (source: Node, target: Node) => createEventSourceKafkaConnection(source, target) .then(() => null) .catch((error) => { - errorModal({ + launchGlobalErrorModal({ title: i18next.t('knative-plugin~Error moving event source kafka connector'), error: error.message, - showIcon: true, }); }); diff --git a/frontend/packages/shipwright-plugin/src/components/buildrun-details/BuildRunDetailsPage.tsx b/frontend/packages/shipwright-plugin/src/components/buildrun-details/BuildRunDetailsPage.tsx index 9bf322ae502..3d8704e6315 100644 --- a/frontend/packages/shipwright-plugin/src/components/buildrun-details/BuildRunDetailsPage.tsx +++ b/frontend/packages/shipwright-plugin/src/components/buildrun-details/BuildRunDetailsPage.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react'; import { DetailsPage, DetailsPageProps } from '@console/internal/components/factory'; import { Page, navFactory } from '@console/internal/components/utils'; import { referenceFor } from '@console/internal/module/k8s'; +import { useSetupGlobalErrorModalLauncher } from '@console/shared'; import { ActionMenu, ActionMenuVariant, @@ -14,6 +15,7 @@ import BuildRunEventsTab from './BuildRunEventsTab'; import BuildRunLogsTab from './BuildRunLogsTab'; const BuildRunDetailsPage: FC = (props) => { + useSetupGlobalErrorModalLauncher(); const customActionMenu = (_, buildRun) => { const kindReference = referenceFor(buildRun); const context = { [kindReference]: buildRun }; diff --git a/frontend/packages/shipwright-plugin/src/components/logs/logs-utils.ts b/frontend/packages/shipwright-plugin/src/components/logs/logs-utils.ts index 8dfcd5cbee4..061f878a6f0 100644 --- a/frontend/packages/shipwright-plugin/src/components/logs/logs-utils.ts +++ b/frontend/packages/shipwright-plugin/src/components/logs/logs-utils.ts @@ -2,7 +2,6 @@ import { saveAs } from 'file-saver'; import i18next from 'i18next'; import * as _ from 'lodash'; import { coFetchText } from '@console/internal/co-fetch'; -import { errorModal } from '@console/internal/components/modals'; import { LOG_SOURCE_RESTARTING, LOG_SOURCE_RUNNING, @@ -18,6 +17,7 @@ import { resourceURL, k8sGet, } from '@console/internal/module/k8s'; +import { launchGlobalErrorModal } from '@console/shared'; import { TaskRunKind } from '../../types'; import { ComputedStatus, SucceedConditionReason } from './log-snippet-types'; import { getTaskRunLog } from './tekton-results'; @@ -81,7 +81,9 @@ const getOrderedStepsFromPod = (name: string, ns: string): Promise { - errorModal({ error: err.message || i18next.t('shipwright-plugin~Error downloading logs.') }); + launchGlobalErrorModal({ + error: err.message || i18next.t('shipwright-plugin~Error downloading logs.'), + }); return []; }); }; diff --git a/frontend/packages/topology/locales/en/topology.json b/frontend/packages/topology/locales/en/topology.json index 1a825fac996..51a919cd32a 100644 --- a/frontend/packages/topology/locales/en/topology.json +++ b/frontend/packages/topology/locales/en/topology.json @@ -10,6 +10,7 @@ "Delete Connector?": "Delete Connector?", "Deleting the visual connector removes the `connects-to` annotation from the resources. Are you sure you want to delete the visual connector?": "Deleting the visual connector removes the `connects-to` annotation from the resources. Are you sure you want to delete the visual connector?", "Delete": "Delete", + "Error deleting connector": "Error deleting connector", "Delete connector": "Delete connector", "Edit application grouping": "Edit application grouping", "View all {{size}}": "View all {{size}}", diff --git a/frontend/packages/topology/src/actions/edgeActions.ts b/frontend/packages/topology/src/actions/edgeActions.ts index 969e6827da2..313d512b820 100644 --- a/frontend/packages/topology/src/actions/edgeActions.ts +++ b/frontend/packages/topology/src/actions/edgeActions.ts @@ -4,7 +4,6 @@ import { Edge, isNode, Node } from '@patternfly/react-topology'; import i18next from 'i18next'; import { useTranslation } from 'react-i18next'; import { Action, K8sModel } from '@console/dynamic-plugin-sdk'; -import { errorModal } from '@console/internal/components/modals'; import { asAccessReview } from '@console/internal/components/utils'; import { TYPE_EVENT_SOURCE, @@ -16,6 +15,7 @@ import { TYPE_KAFKA_CONNECTION_LINK, TYPE_MANAGED_KAFKA_CONNECTION, } from '@console/knative-plugin/src/topology/const'; +import { launchGlobalErrorModal } from '@console/shared'; import { useWarningModal } from '@console/shared/src/hooks/useWarningModal'; import { useMoveConnectionModalLauncher } from '../components/modals/MoveConnectionModal'; import { TYPE_CONNECTS_TO, TYPE_TRAFFIC_CONNECTOR } from '../const'; @@ -102,7 +102,12 @@ export const useDeleteConnectorAction = ( confirmButtonVariant: ButtonVariant.danger, onConfirm: () => { return removeTopologyResourceConnection(element, resource).catch((err) => { - err && errorModal({ error: err.message }); + if (err) { + launchGlobalErrorModal({ + title: t('topology~Error deleting connector'), + error: err.message, + }); + } }); }, ouiaId: 'TopologyDeleteConnectorConfirmation', diff --git a/frontend/packages/topology/src/components/graph-view/Topology.tsx b/frontend/packages/topology/src/components/graph-view/Topology.tsx index f9b4f585010..bc7b0faecbf 100644 --- a/frontend/packages/topology/src/components/graph-view/Topology.tsx +++ b/frontend/packages/topology/src/components/graph-view/Topology.tsx @@ -34,12 +34,14 @@ import { useQueryParams, withUserSettingsCompatibility, WithUserSettingsCompatibilityProps, + useSetupGlobalErrorModalLauncher, } from '@console/shared'; import { withFallback, ErrorBoundaryFallbackPage } from '@console/shared/src/components/error'; import { TOPOLOGY_LAYOUT_CONFIG_STORAGE_KEY, TOPOLOGY_LAYOUT_LOCAL_STORAGE_KEY } from '../../const'; import { odcElementFactory } from '../../elements'; import { getTopologyGraphModel, setTopologyGraphModel } from '../../redux/action'; import { SHOW_GROUPING_HINT_EVENT, ShowGroupingHintEventListener } from '../../topology-types'; +import { useSetupMoveNodeToGroupErrorHandler } from '../../utils'; import { componentFactory } from './components'; import { DEFAULT_LAYOUT, SUPPORTED_LAYOUTS, layoutFactory } from './layouts/layoutFactory'; import TopologyControlBar from './TopologyControlBar'; @@ -152,6 +154,10 @@ const Topology: FC< TopologyComponentFactory >(isTopologyComponentFactory); + // Setup global error handlers for topology operations + useSetupMoveNodeToGroupErrorHandler(); + useSetupGlobalErrorModalLauncher(); + const createVisualization = useCallback(() => { const storedLayout = topologyLayoutDataJson?.[namespace]; const newVisualization = new Visualization(); diff --git a/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts b/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts index 21a4efcd1e1..5408a9f9042 100644 --- a/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts +++ b/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts @@ -21,9 +21,8 @@ import { } from '@patternfly/react-topology'; import i18next from 'i18next'; import { action } from 'mobx'; -import { errorModal } from '@console/internal/components/modals'; import { K8sResourceKind } from '@console/internal/module/k8s'; -import { ActionContext } from '@console/shared'; +import { ActionContext, launchGlobalErrorModal } from '@console/shared'; import { createConnection, moveNodeToGroup } from '../../../utils'; import { isWorkloadRegroupable, graphContextMenu, groupContextMenu } from './nodeContextMenu'; import withTopologyContextMenu from './withTopologyContextMenu'; @@ -136,6 +135,7 @@ const nodeDragSourceSpec = ( if (!monitor.isCancelled() && monitor.getOperation()?.type === REGROUP_OPERATION) { if (monitor.didDrop() && dropResult && props && props.element.getParent() !== dropResult) { const controller = props.element.getController(); + await moveNodeToGroup( props.element as Node, isNode(dropResult) ? (dropResult as Node) : null, @@ -308,8 +308,9 @@ const edgeDragSourceSpec = ( ) { const title = failureTitle !== undefined ? failureTitle : i18next.t('topology~Error moving connection'); + callback(edge.getSource(), dropResult, edge.getTarget()).catch((error) => { - errorModal({ title, error: error.message, showIcon: true }); + launchGlobalErrorModal({ title, error: error.message }); }); } }, @@ -346,7 +347,10 @@ const createVisualConnector = (source: Node, target: Node | Graph): ReactElement } createConnection(source, target, null).catch((error) => { - errorModal({ title: i18next.t('topology~Error creating connection'), error: error.message }); + launchGlobalErrorModal({ + title: i18next.t('topology~Error creating connection'), + error: error.message, + }); }); return null; diff --git a/frontend/packages/topology/src/utils/moveNodeToGroup.tsx b/frontend/packages/topology/src/utils/moveNodeToGroup.tsx index 7550bac2ce1..bc6b5a3e6e3 100644 --- a/frontend/packages/topology/src/utils/moveNodeToGroup.tsx +++ b/frontend/packages/topology/src/utils/moveNodeToGroup.tsx @@ -1,9 +1,23 @@ +import { useCallback, useEffect } from 'react'; import { Node } from '@patternfly/react-topology'; import { Trans } from 'react-i18next'; -import { confirmModal, errorModal } from '@console/internal/components/modals'; +import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; +import { confirmModal } from '@console/internal/components/modals'; +import { ErrorModal, ErrorModalProps } from '@console/internal/components/modals/error-modal'; import { updateTopologyResourceApplication } from './topology-utils'; -export const moveNodeToGroup = (node: Node, targetGroup: Node): Promise => { +// Module-level error handler that can be set by the Topology component +let globalErrorHandler: ((error: string) => void) | null = null; + +export const setMoveNodeToGroupErrorHandler = (handler: ((error: string) => void) | null) => { + globalErrorHandler = handler; +}; + +export const moveNodeToGroup = ( + node: Node, + targetGroup: Node, + onError?: (error: string) => void, +): Promise => { const sourceGroup = node.getParent() !== node.getGraph() ? (node.getParent() as Node) : undefined; if (sourceGroup === targetGroup) { return Promise.reject(); @@ -51,7 +65,10 @@ export const moveNodeToGroup = (node: Node, targetGroup: Node): Promise => .then(resolve) .catch((err) => { const error = err.message; - errorModal({ error }); + const errorHandler = onError || globalErrorHandler; + if (errorHandler) { + errorHandler(error); + } reject(err); }); }, @@ -61,6 +78,48 @@ export const moveNodeToGroup = (node: Node, targetGroup: Node): Promise => return updateTopologyResourceApplication(node, targetGroup.getLabel()).catch((err) => { const error = err.message; - errorModal({ error }); + const errorHandler = onError || globalErrorHandler; + if (errorHandler) { + errorHandler(error); + } }); }; + +/** + * Hook that sets up error handling for moveNodeToGroup using useOverlay. + * This sets a global error handler that will be used by all moveNodeToGroup calls. + */ +export const useSetupMoveNodeToGroupErrorHandler = () => { + const launcher = useOverlay(); + + useEffect(() => { + const errorHandler = (error: string) => { + launcher(ErrorModal, { error }); + }; + + setMoveNodeToGroupErrorHandler(errorHandler); + + return () => { + setMoveNodeToGroupErrorHandler(null); + }; + // Intentionally only run once on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +}; + +/** + * @deprecated Use useSetupMoveNodeToGroupErrorHandler in the parent component instead + * Hook that provides a moveNodeToGroup function with error handling using useOverlay. + */ +export const useMoveNodeToGroup = () => { + const launcher = useOverlay(); + + return useCallback( + (node: Node, targetGroup: Node) => { + return moveNodeToGroup(node, targetGroup, (error) => { + launcher(ErrorModal, { error }); + }); + }, + [launcher], + ); +}; diff --git a/frontend/public/components/modals/error-modal.tsx b/frontend/public/components/modals/error-modal.tsx index b3556859df0..6ef082221c3 100644 --- a/frontend/public/components/modals/error-modal.tsx +++ b/frontend/public/components/modals/error-modal.tsx @@ -12,13 +12,7 @@ import { import { useTranslation } from 'react-i18next'; import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; -import { - createModalLauncher, - ModalTitle, - ModalBody, - ModalFooter, - ModalComponentProps, -} from '../factory/modal'; +import { ModalTitle, ModalBody, ModalFooter, ModalComponentProps } from '../factory/modal'; import { YellowExclamationTriangleIcon } from '@console/shared/src/components/status/icons'; export const ModalErrorContent = (props: ErrorModalProps) => { @@ -64,9 +58,6 @@ export const useErrorModalLauncher = (props) => { return useCallback(() => launcher(ErrorModal, props), [launcher, props]); }; -/** @deprecated Use useErrorModalLauncher hook instead */ -export const errorModal = createModalLauncher(ModalErrorContent); - export type ErrorModalProps = { error: string | React.ReactNode; title?: string; diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 89a5340ae2b..61c61fd96c5 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -17,10 +17,6 @@ export const confirmModal = (props) => m.confirmModal(props), ); -/** @deprecated Use useErrorModalLauncher hook instead */ -export const errorModal = (props) => - import('./error-modal' /* webpackChunkName: "error-modal" */).then((m) => m.errorModal(props)); - // Lazy-loaded OverlayComponent for Configure Namespace Pull Secret Modal export const LazyConfigureNamespacePullSecretModalProvider = lazy(() => import( From 274b7357b32bd2969cbce090466a71241d831a5e Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Fri, 23 Jan 2026 15:57:35 -0500 Subject: [PATCH 12/12] CONSOLE-5012: Migrate confirmModal to overlay pattern This commit migrates all instances of confirmModal to use the modern overlay pattern with useWarningModal, completing the modal migration effort started in previous PRs. Changes: - Removed deprecated confirmModal launcher and confirm-modal.tsx file - Migrated topology moveNodeToGroup to use useWarningModal with proper cancel handling via global handler pattern - Updated StopNodeMaintenanceModal to remove deprecated imperative API and keep only the useStopNodeMaintenanceModal hook - Fixed useWarningModal to properly call closeOverlay() and filter it from props spread to prevent React DOM warnings - Added try-catch in topology drag-drop to prevent optimistic update when user cancels the move operation - Updated all metal3-plugin maintenance components to use the hook-based approach This PR builds upon and depends on: - #15932 (ResourceLimitsModal migration) - #15939 (DeletePDBModal migration) - #15940 (ConfigureUnschedulableModal migration) - #15946 (Global error modal utility) Co-Authored-By: Claude Sonnet 4.5 --- .../src/hooks/useWarningModal.tsx | 7 +- .../StartingMaintenancePopoverContent.tsx | 5 +- .../UnderMaintenancePopoverContent.tsx | 5 +- .../src/components/maintenance/actions.tsx | 11 +- .../modals/StopNodeMaintenanceModal.tsx | 21 --- .../src/components/graph-view/Topology.tsx | 6 +- .../graph-view/components/componentUtils.ts | 29 ++-- .../topology/src/utils/moveNodeToGroup.tsx | 134 +++++++++++++----- .../components/modals/confirm-modal.tsx | 72 ---------- frontend/public/components/modals/index.ts | 6 - frontend/public/locales/en/public.json | 1 - 11 files changed, 138 insertions(+), 159 deletions(-) delete mode 100644 frontend/public/components/modals/confirm-modal.tsx diff --git a/frontend/packages/console-shared/src/hooks/useWarningModal.tsx b/frontend/packages/console-shared/src/hooks/useWarningModal.tsx index 8db0e05b6ca..524917c19aa 100644 --- a/frontend/packages/console-shared/src/hooks/useWarningModal.tsx +++ b/frontend/packages/console-shared/src/hooks/useWarningModal.tsx @@ -6,17 +6,22 @@ import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/us /** * ControlledWarningModal is a wrapper around WarningModal that manages its open state. */ -const ControlledWarningModal: FC = (props) => { +const ControlledWarningModal: FC void }> = ({ + closeOverlay, + ...props +}) => { const [isOpen, setIsOpen] = useState(true); const onClose: WarningModalProps['onClose'] = (e) => { setIsOpen(false); props.onClose?.(e); + closeOverlay?.(); }; const onConfirm: WarningModalProps['onConfirm'] = () => { setIsOpen(false); props.onConfirm?.(); + closeOverlay?.(); }; return ; diff --git a/frontend/packages/metal3-plugin/src/components/maintenance/StartingMaintenancePopoverContent.tsx b/frontend/packages/metal3-plugin/src/components/maintenance/StartingMaintenancePopoverContent.tsx index 6fb4dd62e43..d443476661a 100644 --- a/frontend/packages/metal3-plugin/src/components/maintenance/StartingMaintenancePopoverContent.tsx +++ b/frontend/packages/metal3-plugin/src/components/maintenance/StartingMaintenancePopoverContent.tsx @@ -20,7 +20,7 @@ import { getNodeMaintenanceLastError, getNodeMaintenancePendingPods, } from '../../selectors'; -import stopNodeMaintenanceModal from '../modals/StopNodeMaintenanceModal'; +import { useStopNodeMaintenanceModal } from '../modals/StopNodeMaintenanceModal'; import MaintenancePopoverPodList from './MaintenancePopoverPodList'; type StartingMaintenancePopoverContentProps = { @@ -31,6 +31,7 @@ const StartingMaintenancePopoverContent: FC { const { t } = useTranslation(); + const stopNodeMaintenanceModalLauncher = useStopNodeMaintenanceModal(nodeMaintenance); const reason = getNodeMaintenanceReason(nodeMaintenance); const creationTimestamp = getNodeMaintenanceCreationTimestamp(nodeMaintenance); const lastError = getNodeMaintenanceLastError(nodeMaintenance); @@ -78,7 +79,7 @@ const StartingMaintenancePopoverContent: FC
- diff --git a/frontend/packages/metal3-plugin/src/components/maintenance/UnderMaintenancePopoverContent.tsx b/frontend/packages/metal3-plugin/src/components/maintenance/UnderMaintenancePopoverContent.tsx index 18d0b19049f..7d3b4db8e0f 100644 --- a/frontend/packages/metal3-plugin/src/components/maintenance/UnderMaintenancePopoverContent.tsx +++ b/frontend/packages/metal3-plugin/src/components/maintenance/UnderMaintenancePopoverContent.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import { K8sResourceKind } from '@console/internal/module/k8s'; import { Timestamp } from '@console/shared/src/components/datetime/Timestamp'; import { getNodeMaintenanceReason, getNodeMaintenanceCreationTimestamp } from '../../selectors'; -import stopNodeMaintenanceModal from '../modals/StopNodeMaintenanceModal'; +import { useStopNodeMaintenanceModal } from '../modals/StopNodeMaintenanceModal'; type UnderMaintenancePopoverContentProps = { nodeMaintenance: K8sResourceKind; @@ -20,6 +20,7 @@ const UnderMaintenancePopoverContent: FC = nodeMaintenance, }) => { const { t } = useTranslation(); + const stopNodeMaintenanceModalLauncher = useStopNodeMaintenanceModal(nodeMaintenance); const reason = getNodeMaintenanceReason(nodeMaintenance); const creationTimestamp = getNodeMaintenanceCreationTimestamp(nodeMaintenance); @@ -43,7 +44,7 @@ const UnderMaintenancePopoverContent: FC =
- diff --git a/frontend/packages/metal3-plugin/src/components/maintenance/actions.tsx b/frontend/packages/metal3-plugin/src/components/maintenance/actions.tsx index acbb8892fd5..fc2dd1a0942 100644 --- a/frontend/packages/metal3-plugin/src/components/maintenance/actions.tsx +++ b/frontend/packages/metal3-plugin/src/components/maintenance/actions.tsx @@ -6,7 +6,7 @@ import { NodeKind } from '@console/internal/module/k8s'; import { useMaintenanceCapability } from '../../hooks/useMaintenanceCapability'; import { findNodeMaintenance } from '../../selectors'; import { useStartNodeMaintenanceModalLauncher } from '../modals/StartNodeMaintenanceModal'; -import stopNodeMaintenanceModal from '../modals/StopNodeMaintenanceModal'; +import { useStopNodeMaintenanceModal } from '../modals/StopNodeMaintenanceModal'; export const useNodeMaintenanceActions: ExtensionHook = (resource) => { const { t } = useTranslation(); @@ -24,9 +24,10 @@ export const useNodeMaintenanceActions: ExtensionHook = (res namespaced: false, }); - const actions = useMemo(() => { - const nodeMaintenance = findNodeMaintenance(maintenances, resource.metadata.name); + const nodeMaintenance = findNodeMaintenance(maintenances, resource.metadata.name); + const stopNodeMaintenanceModalLauncher = useStopNodeMaintenanceModal(nodeMaintenance); + const actions = useMemo(() => { let action: Action = { id: 'start-node-maintenance', label: t('metal3-plugin~Start Maintenance'), @@ -38,13 +39,13 @@ export const useNodeMaintenanceActions: ExtensionHook = (res action = { id: 'stop-node-maintenance', label: t('metal3-plugin~Stop Maintenance'), - cta: () => stopNodeMaintenanceModal(nodeMaintenance, t), + cta: () => stopNodeMaintenanceModalLauncher(), insertBefore: 'edit-labels', }; } return [action]; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [maintenances, resource.metadata.name, t]); + }, [maintenances, resource.metadata.name, t, stopNodeMaintenanceModalLauncher]); return [actions, loading, loadError]; }; diff --git a/frontend/packages/metal3-plugin/src/components/modals/StopNodeMaintenanceModal.tsx b/frontend/packages/metal3-plugin/src/components/modals/StopNodeMaintenanceModal.tsx index b43af5b270f..23983ecc688 100644 --- a/frontend/packages/metal3-plugin/src/components/modals/StopNodeMaintenanceModal.tsx +++ b/frontend/packages/metal3-plugin/src/components/modals/StopNodeMaintenanceModal.tsx @@ -1,6 +1,4 @@ -import { TFunction } from 'i18next'; import { Trans, useTranslation } from 'react-i18next'; -import { confirmModal } from '@console/internal/components/modals/confirm-modal'; import { k8sKill, K8sResourceKind } from '@console/internal/module/k8s'; import { useWarningModal } from '@console/shared/src/hooks/useWarningModal'; import { @@ -26,23 +24,6 @@ const getMaintenanceModel = (nodeMaintenance: K8sResourceKind) => { return NodeMaintenanceKubevirtAlphaModel; }; -const stopNodeMaintenanceModal = (nodeMaintenance: K8sResourceKind, t: TFunction) => { - const reason = getNodeMaintenanceReason(nodeMaintenance); - const reasonLabel = reason ? `(${reason})` : ''; - const nodeName = getNodeMaintenanceNodeName(nodeMaintenance); - return confirmModal({ - title: t('metal3-plugin~Stop maintenance'), - message: ( - - Are you sure you want to stop maintenance {reasonLabel} on node{' '} - {nodeName}? - - ), - btnText: t('metal3-plugin~Stop maintenance'), - executeFn: () => k8sKill(getMaintenanceModel(nodeMaintenance), nodeMaintenance), - }); -}; - export const useStopNodeMaintenanceModal = (nodeMaintenance: K8sResourceKind) => { const { t } = useTranslation(); const reason = getNodeMaintenanceReason(nodeMaintenance); @@ -61,5 +42,3 @@ export const useStopNodeMaintenanceModal = (nodeMaintenance: K8sResourceKind) => }); return stopNodeMaintenanceModalLauncher; }; - -export default stopNodeMaintenanceModal; diff --git a/frontend/packages/topology/src/components/graph-view/Topology.tsx b/frontend/packages/topology/src/components/graph-view/Topology.tsx index bc7b0faecbf..9939192049b 100644 --- a/frontend/packages/topology/src/components/graph-view/Topology.tsx +++ b/frontend/packages/topology/src/components/graph-view/Topology.tsx @@ -41,7 +41,7 @@ import { TOPOLOGY_LAYOUT_CONFIG_STORAGE_KEY, TOPOLOGY_LAYOUT_LOCAL_STORAGE_KEY } import { odcElementFactory } from '../../elements'; import { getTopologyGraphModel, setTopologyGraphModel } from '../../redux/action'; import { SHOW_GROUPING_HINT_EVENT, ShowGroupingHintEventListener } from '../../topology-types'; -import { useSetupMoveNodeToGroupErrorHandler } from '../../utils'; +import { useSetupMoveNodeToGroupHandlers } from '../../utils'; import { componentFactory } from './components'; import { DEFAULT_LAYOUT, SUPPORTED_LAYOUTS, layoutFactory } from './layouts/layoutFactory'; import TopologyControlBar from './TopologyControlBar'; @@ -154,8 +154,8 @@ const Topology: FC< TopologyComponentFactory >(isTopologyComponentFactory); - // Setup global error handlers for topology operations - useSetupMoveNodeToGroupErrorHandler(); + // Setup global error and confirmation handlers for topology operations + useSetupMoveNodeToGroupHandlers(); useSetupGlobalErrorModalLauncher(); const createVisualization = useCallback(() => { diff --git a/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts b/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts index 5408a9f9042..d142c98eb80 100644 --- a/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts +++ b/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts @@ -136,18 +136,23 @@ const nodeDragSourceSpec = ( if (monitor.didDrop() && dropResult && props && props.element.getParent() !== dropResult) { const controller = props.element.getController(); - await moveNodeToGroup( - props.element as Node, - isNode(dropResult) ? (dropResult as Node) : null, - ); - - // perform the optimistic update in an action so as not to render too soon - action(() => { - // FIXME: check shouldn't be necessary if we handled the async and backend data refresh correctly - if (controller.getNodeById(props.element.getId())) { - dropResult.appendChild(props.element); - } - })(); + try { + await moveNodeToGroup( + props.element as Node, + isNode(dropResult) ? (dropResult as Node) : null, + ); + + // perform the optimistic update in an action so as not to render too soon + action(() => { + // FIXME: check shouldn't be necessary if we handled the async and backend data refresh correctly + if (controller.getNodeById(props.element.getId())) { + dropResult.appendChild(props.element); + } + })(); + } catch (error) { + // User cancelled or operation failed - don't perform the optimistic update + // The node will remain in its original position + } } else { // cancel operation return Promise.reject(); diff --git a/frontend/packages/topology/src/utils/moveNodeToGroup.tsx b/frontend/packages/topology/src/utils/moveNodeToGroup.tsx index bc6b5a3e6e3..1c29259ff78 100644 --- a/frontend/packages/topology/src/utils/moveNodeToGroup.tsx +++ b/frontend/packages/topology/src/utils/moveNodeToGroup.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect } from 'react'; import { Node } from '@patternfly/react-topology'; -import { Trans } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; -import { confirmModal } from '@console/internal/components/modals'; import { ErrorModal, ErrorModalProps } from '@console/internal/components/modals/error-modal'; +import { useWarningModal } from '@console/shared/src/hooks/useWarningModal'; import { updateTopologyResourceApplication } from './topology-utils'; // Module-level error handler that can be set by the Topology component @@ -13,6 +13,31 @@ export const setMoveNodeToGroupErrorHandler = (handler: ((error: string) => void globalErrorHandler = handler; }; +// Module-level confirm handler that can be set by the Topology component +let globalConfirmHandler: + | (( + title: string, + message: React.ReactNode, + confirmButtonText: string, + onConfirm: () => Promise, + onCancel: () => void, + ) => void) + | null = null; + +export const setMoveNodeToGroupConfirmHandler = ( + handler: + | (( + title: string, + message: React.ReactNode, + confirmButtonText: string, + onConfirm: () => Promise, + onCancel: () => void, + ) => void) + | null, +) => { + globalConfirmHandler = handler; +}; + export const moveNodeToGroup = ( node: Node, targetGroup: Node, @@ -24,14 +49,14 @@ export const moveNodeToGroup = ( } if (sourceGroup) { - // t('topology~Move component node') - // t('topology~Remove component node from application') - const titleKey = targetGroup - ? 'topology~Move component node' - : 'topology~Remove component node from application'; const nodeLabel = node.getLabel(); const sourceLabel = sourceGroup.getLabel(); const targetLabel = targetGroup?.getLabel(); + + // t('topology~Move component node') + // t('topology~Remove component node from application') + const title = targetGroup ? 'Move component node' : 'Remove component node from application'; + const message = targetGroup ? ( Are you sure you want to move {{ nodeLabel }} from {{ sourceLabel }} to{' '} @@ -42,37 +67,35 @@ export const moveNodeToGroup = ( Are you sure you want to remove {{ nodeLabel }} from {{ sourceLabel }}? ); + // t('topology~Move') // t('topology~Remove') - const btnTextKey = targetGroup ? 'topology~Move' : 'topology~Remove'; + const confirmButtonText = targetGroup ? 'Move' : 'Remove'; return new Promise((resolve, reject) => { - confirmModal({ - titleKey, - message, - btnTextKey, - close: () => { - reject(); - }, - cancel: () => { - reject(); - }, - executeFn: () => { - return updateTopologyResourceApplication( - node, - targetGroup ? targetGroup.getLabel() : null, - ) - .then(resolve) - .catch((err) => { - const error = err.message; - const errorHandler = onError || globalErrorHandler; - if (errorHandler) { - errorHandler(error); - } - reject(err); - }); - }, - }); + if (!globalConfirmHandler) { + reject(new Error('Confirm handler not initialized')); + return; + } + + const handleConfirm = () => { + return updateTopologyResourceApplication(node, targetGroup ? targetGroup.getLabel() : null) + .then(resolve) + .catch((err) => { + const error = err.message; + const errorHandler = onError || globalErrorHandler; + if (errorHandler) { + errorHandler(error); + } + reject(err); + }); + }; + + const handleCancel = () => { + reject(new Error('User cancelled')); + }; + + globalConfirmHandler(title, message, confirmButtonText, handleConfirm, handleCancel); }); } @@ -86,6 +109,49 @@ export const moveNodeToGroup = ( }; /** + * Hook that sets up both error and confirm handling for moveNodeToGroup using useOverlay. + * This sets global handlers that will be used by all moveNodeToGroup calls. + */ +export const useSetupMoveNodeToGroupHandlers = () => { + const { t } = useTranslation(); + const launcher = useOverlay(); + const launchWarningModal = useWarningModal(); + + useEffect(() => { + const errorHandler = (error: string) => { + launcher(ErrorModal, { error }); + }; + + const confirmHandler = ( + title: string, + message: React.ReactNode, + confirmButtonText: string, + onConfirm: () => Promise, + onCancel: () => void, + ) => { + launchWarningModal({ + title: t(`topology~${title}`), + children: message, + confirmButtonLabel: t(`topology~${confirmButtonText}`), + onConfirm, + onClose: onCancel, + }); + }; + + setMoveNodeToGroupErrorHandler(errorHandler); + setMoveNodeToGroupConfirmHandler(confirmHandler); + + return () => { + setMoveNodeToGroupErrorHandler(null); + setMoveNodeToGroupConfirmHandler(null); + }; + // Intentionally only run once on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +}; + +/** + * @deprecated Use useSetupMoveNodeToGroupHandlers in the parent component instead * Hook that sets up error handling for moveNodeToGroup using useOverlay. * This sets a global error handler that will be used by all moveNodeToGroup calls. */ diff --git a/frontend/public/components/modals/confirm-modal.tsx b/frontend/public/components/modals/confirm-modal.tsx deleted file mode 100644 index 5601758848f..00000000000 --- a/frontend/public/components/modals/confirm-modal.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Translation } from 'react-i18next'; -import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler'; -import type { ReactNode, FormEvent, FC } from 'react'; -import { - createModalLauncher, - ModalTitle, - ModalBody, - ModalSubmitFooter, - ModalComponentProps, -} from '../factory/modal'; - -interface ConfirmModalProps extends ModalComponentProps { - btnText?: string | ReactNode; - btnTextKey?: string; - cancelText?: string | ReactNode; - cancelTextKey?: string; - executeFn: (e?: FormEvent, opts?: { supressNotifications: boolean }) => Promise; - message?: string | ReactNode; - messageKey?: string; - title?: string | ReactNode; - titleKey?: string; - submitDanger?: boolean; -} - -const ConfirmModal: FC = (props) => { - const [handlePromise, inProgress, errorMessage] = usePromiseHandler(); - - const submit = (event: FormEvent) => { - event.preventDefault(); - - handlePromise( - props.executeFn(null, { - supressNotifications: true, - }), - ).then(props.close); - }; - - const { - title, - titleKey, - message, - messageKey, - btnText, - btnTextKey, - cancelText, - cancelTextKey, - submitDanger, - cancel, - } = props; - - return ( - - {(t) => ( -

- {titleKey ? t(titleKey) : title} - {messageKey ? t(messageKey) : message} - - - )} - - ); -}; - -/** @deprecated use `useWarningModal` instead */ -export const confirmModal = createModalLauncher(ConfirmModal); diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts index 61c61fd96c5..5ae0b76641f 100644 --- a/frontend/public/components/modals/index.ts +++ b/frontend/public/components/modals/index.ts @@ -11,12 +11,6 @@ export const configureJobParallelismModal = (props) => m.configureJobParallelismModal(props), ); -/** @deprecated use `useWarningModal` instead */ -export const confirmModal = (props) => - import('./confirm-modal' /* webpackChunkName: "confirm-modal" */).then((m) => - m.confirmModal(props), - ); - // Lazy-loaded OverlayComponent for Configure Namespace Pull Secret Modal export const LazyConfigureNamespacePullSecretModalProvider = lazy(() => import( diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index 5447308e1c9..f4e1936353d 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -902,7 +902,6 @@ "Recreate": "Recreate", "Shut down all existing pods before creating new ones": "Shut down all existing pods before creating new ones", "Edit update strategy": "Edit update strategy", - "Confirm": "Confirm", "Delete {{kind}}?": "Delete {{kind}}?", "Are you sure you want to delete <2>{{resourceName}} in namespace <5>{{namespace}}?": "Are you sure you want to delete <2>{{resourceName}} in namespace <5>{{namespace}}?", "Are you sure you want to delete <2>{{resourceName}}?": "Are you sure you want to delete <2>{{resourceName}}?",