From edbbd36c282dd0d5f248b22994534b391f3a164c Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Tue, 10 Feb 2026 11:26:06 -0500
Subject: [PATCH 1/9] Update modal Cancel buttons to use variant="link"
Update Cancel button variants from "secondary" to "link" in modern
PatternFly modals to follow PatternFly standards.
Co-Authored-By: Claude Sonnet 4.5
---
.../src/components/modals/add-group-users-modal.tsx | 6 +++---
.../src/components/modals/CreateNamespaceModal.tsx | 2 +-
.../src/components/modals/CreateProjectModal.tsx | 2 +-
.../src/components/test-function/TestFunctionModal.tsx | 4 ++--
.../src/components/modals/PowerOffHostModal.tsx | 2 +-
.../src/components/modals/RestartHostModal.tsx | 2 +-
.../src/components/modals/StartNodeMaintenanceModal.tsx | 2 +-
.../src/components/registry-poll-interval-details.tsx | 2 +-
.../public/components/modals/delete-namespace-modal.tsx | 2 +-
.../components/secrets/create-secret/SecretFormWrapper.tsx | 2 +-
10 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx b/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx
index da15972b46c..8e045f1825a 100644
--- a/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx
+++ b/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx
@@ -91,9 +91,6 @@ const AddGroupUsersModal: OverlayComponent = ({ group,
)}
-
- {t('public~Cancel')}
-
= ({ group,
>
{t('public~Save')}
+
+ {t('public~Cancel')}
+
);
diff --git a/frontend/packages/console-shared/src/components/modals/CreateNamespaceModal.tsx b/frontend/packages/console-shared/src/components/modals/CreateNamespaceModal.tsx
index a444efba571..d87252ece1e 100644
--- a/frontend/packages/console-shared/src/components/modals/CreateNamespaceModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/CreateNamespaceModal.tsx
@@ -169,7 +169,7 @@ export const CreateNamespaceModal: ModalComponent = ({
= ({
,
= (props) => {
{t('knative-plugin~Test')}
-
+
{t('knative-plugin~Cancel')}
@@ -64,7 +64,7 @@ const TestFunctionModal: FC = (props) => {
{
clearResponseValues(props);
diff --git a/frontend/packages/metal3-plugin/src/components/modals/PowerOffHostModal.tsx b/frontend/packages/metal3-plugin/src/components/modals/PowerOffHostModal.tsx
index 23b9515a646..95519d76dd4 100644
--- a/frontend/packages/metal3-plugin/src/components/modals/PowerOffHostModal.tsx
+++ b/frontend/packages/metal3-plugin/src/components/modals/PowerOffHostModal.tsx
@@ -240,7 +240,7 @@ const PowerOffHostModal: OverlayComponent = (props) => {
>
{t('metal3-plugin~Power Off')}
-
+
{t('metal3-plugin~Cancel')}
diff --git a/frontend/packages/metal3-plugin/src/components/modals/RestartHostModal.tsx b/frontend/packages/metal3-plugin/src/components/modals/RestartHostModal.tsx
index 5c0aa2f8048..802271bfdd1 100644
--- a/frontend/packages/metal3-plugin/src/components/modals/RestartHostModal.tsx
+++ b/frontend/packages/metal3-plugin/src/components/modals/RestartHostModal.tsx
@@ -56,7 +56,7 @@ const RestartHostModal: OverlayComponent = (props) => {
{t('metal3-plugin~Restart')}
-
+
{t('metal3-plugin~Cancel')}
diff --git a/frontend/packages/metal3-plugin/src/components/modals/StartNodeMaintenanceModal.tsx b/frontend/packages/metal3-plugin/src/components/modals/StartNodeMaintenanceModal.tsx
index 288889d01ff..52b47a2858d 100644
--- a/frontend/packages/metal3-plugin/src/components/modals/StartNodeMaintenanceModal.tsx
+++ b/frontend/packages/metal3-plugin/src/components/modals/StartNodeMaintenanceModal.tsx
@@ -105,7 +105,7 @@ export const StartNodeMaintenanceModal: OverlayComponent
{t('metal3-plugin~Start Maintenance')}
-
+
{t('metal3-plugin~Cancel')}
diff --git a/frontend/packages/operator-lifecycle-manager/src/components/registry-poll-interval-details.tsx b/frontend/packages/operator-lifecycle-manager/src/components/registry-poll-interval-details.tsx
index 9fdbac45f6a..66075df5c91 100644
--- a/frontend/packages/operator-lifecycle-manager/src/components/registry-poll-interval-details.tsx
+++ b/frontend/packages/operator-lifecycle-manager/src/components/registry-poll-interval-details.tsx
@@ -143,7 +143,7 @@ export const RegistryPollIntervalDetailItem: FC
{
setIsModalOpen(false);
}}
diff --git a/frontend/public/components/modals/delete-namespace-modal.tsx b/frontend/public/components/modals/delete-namespace-modal.tsx
index 667b11365fc..1038373ce3e 100644
--- a/frontend/public/components/modals/delete-namespace-modal.tsx
+++ b/frontend/public/components/modals/delete-namespace-modal.tsx
@@ -120,7 +120,7 @@ export const DeleteNamespaceModal: OverlayComponent =
>
{t('public~Delete')}
-
+
{t('public~Cancel')}
diff --git a/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx b/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx
index 1a7d50ca467..b0e056d82bc 100644
--- a/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx
+++ b/frontend/public/components/secrets/create-secret/SecretFormWrapper.tsx
@@ -205,7 +205,7 @@ export const SecretFormWrapper: FC = (props) => {
>
{props.saveButtonText || t('public~Create')}
-
+
{t('public~Cancel')}
From fff846f0c1ee7a3ee8afb6cf989be8397504b09c Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Tue, 10 Feb 2026 11:28:29 -0500
Subject: [PATCH 2/9] Migrate modals to modern PatternFly Modal and remove
deprecated Modal wrapper
- Migrate useCopyCodeModal, FavoriteButton, and TourStepComponent from
deprecated Modal wrapper to modern PatternFly Modal components
- Remove deprecated Modal wrapper (packages/console-shared/src/components/modal/)
- Migrate CatalogDetailsModal and operator-hub-items from deprecated
PatternFly Modal to modern Modal components
- Preserve ocs-modal CSS class for catalog modal positioning
- Fix FavoriteButton form submission bug by adding preventDefault
- Fix Guided Tour accessibility warning by closing Help dropdown and
blurring focus before starting tour
Co-Authored-By: Claude Sonnet 4.5
---
.../components/favorite/FavoriteButton.tsx | 72 +++++------
.../src/components/tour/TourStepComponent.tsx | 3 +-
.../details/CatalogDetailsModal.scss} | 0
.../catalog/details/CatalogDetailsModal.tsx | 81 +++++++------
.../console-shared/src/components/index.ts | 1 -
.../src/components/modal/Modal.tsx | 20 ---
.../src/components/modal/index.ts | 1 -
.../src/hooks/useCopyCodeModal.tsx | 9 +-
.../operator-hub/operator-hub-items.tsx | 114 +++++++++---------
.../components/masthead/masthead-toolbar.tsx | 4 +
10 files changed, 152 insertions(+), 153 deletions(-)
rename frontend/packages/console-shared/src/components/{modal/Modal.scss => catalog/details/CatalogDetailsModal.scss} (100%)
delete mode 100644 frontend/packages/console-shared/src/components/modal/Modal.tsx
delete mode 100644 frontend/packages/console-shared/src/components/modal/index.ts
diff --git a/frontend/packages/console-app/src/components/favorite/FavoriteButton.tsx b/frontend/packages/console-app/src/components/favorite/FavoriteButton.tsx
index 534bc81ee31..4a932bbfc00 100644
--- a/frontend/packages/console-app/src/components/favorite/FavoriteButton.tsx
+++ b/frontend/packages/console-app/src/components/favorite/FavoriteButton.tsx
@@ -9,10 +9,13 @@ import {
HelperTextItem,
TextInput,
Tooltip,
+ Modal,
+ ModalVariant,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
} from '@patternfly/react-core';
-import { ModalVariant } from '@patternfly/react-core/deprecated';
import { useTranslation } from 'react-i18next';
-import Modal from '@console/shared/src/components/modal/Modal';
import { useTelemetry } from '@console/shared/src/hooks/useTelemetry';
import { useUserSettingsCompatibility } from '@console/shared/src/hooks/useUserSettingsCompatibility';
import { FAVORITES_CONFIG_MAP_KEY, FAVORITES_LOCAL_STORAGE_KEY } from '../../consts';
@@ -78,7 +81,8 @@ export const FavoriteButton = ({ defaultName }: FavoriteButtonProps) => {
setIsModalOpen(false);
};
- const handleConfirmStar = () => {
+ const handleConfirmStar = (e?: React.FormEvent) => {
+ e?.preventDefault();
const trimmedName = name.trim();
if (!trimmedName) {
setError(t('Name is required.'));
@@ -151,46 +155,44 @@ export const FavoriteButton = ({ defaultName }: FavoriteButtonProps) => {
{isModalOpen && (
-
+
+
+
+
+
{t('Save')}
- ,
+
{t('Cancel')}
- ,
- ]}
- variant={ModalVariant.small}
- >
-
+
+
)}
diff --git a/frontend/packages/console-app/src/components/tour/TourStepComponent.tsx b/frontend/packages/console-app/src/components/tour/TourStepComponent.tsx
index 4d629e3e479..03acc787c28 100644
--- a/frontend/packages/console-app/src/components/tour/TourStepComponent.tsx
+++ b/frontend/packages/console-app/src/components/tour/TourStepComponent.tsx
@@ -3,6 +3,7 @@ import { useContext } from 'react';
import {
Grid,
GridItem,
+ Modal,
ModalBody,
ModalFooter,
ModalHeader,
@@ -10,7 +11,6 @@ import {
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { ThemeContext } from '@console/internal/components/ThemeProvider';
-import Modal from '@console/shared/src/components/modal/Modal';
import type { PopoverPlacement } from '@console/shared/src/components/popover/const';
import Popover from '@console/shared/src/components/popover/Popover';
import Spotlight from '@console/shared/src/components/spotlight/Spotlight';
@@ -108,7 +108,6 @@ const TourStepComponent: FC = ({
id="guided-tour-modal"
data-test="guided-tour-modal"
aria-label={t('console-app~guided tour {{step, number}}', { step })}
- isFullScreen
>
diff --git a/frontend/packages/console-shared/src/components/modal/Modal.scss b/frontend/packages/console-shared/src/components/catalog/details/CatalogDetailsModal.scss
similarity index 100%
rename from frontend/packages/console-shared/src/components/modal/Modal.scss
rename to frontend/packages/console-shared/src/components/catalog/details/CatalogDetailsModal.scss
diff --git a/frontend/packages/console-shared/src/components/catalog/details/CatalogDetailsModal.tsx b/frontend/packages/console-shared/src/components/catalog/details/CatalogDetailsModal.tsx
index 5377e70f759..07983dc679f 100644
--- a/frontend/packages/console-shared/src/components/catalog/details/CatalogDetailsModal.tsx
+++ b/frontend/packages/console-shared/src/components/catalog/details/CatalogDetailsModal.tsx
@@ -1,14 +1,23 @@
import type { FC } from 'react';
import { CatalogItemHeader } from '@patternfly/react-catalog-view-extension';
-import { Split, SplitItem, Divider, Stack, StackItem } from '@patternfly/react-core';
+import {
+ Split,
+ SplitItem,
+ Divider,
+ Stack,
+ StackItem,
+ Modal,
+ ModalBody,
+ ModalHeader,
+} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom-v5-compat';
import type { CatalogItem } from '@console/dynamic-plugin-sdk/src/extensions';
-import { Modal } from '../../modal';
import CatalogBadges from '../CatalogBadges';
import { useCtaLink } from '../hooks/useCtaLink';
import { getIconProps } from '../utils/catalog-utils';
import CatalogDetailsPanel from './CatalogDetailsPanel';
+import './CatalogDetailsModal.scss';
type CatalogDetailsModalProps = {
item: CatalogItem;
@@ -42,43 +51,45 @@ const CatalogDetailsModal: FC = ({ item, onClose }) =>
return (
-
-
-
-
- {to && (
-
-
- {label}
-
-
- )}
-
-
-
- {badges?.length > 0 ? : undefined}
-
-
-
-
-
-
-
-
-
-
+ {modalHeader}
+
+
+
+
+
+ {to && (
+
+
+ {label}
+
+
+ )}
+
+
+
+ {badges?.length > 0 ? : undefined}
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/frontend/packages/console-shared/src/components/index.ts b/frontend/packages/console-shared/src/components/index.ts
index 8a4b3e379b6..c1fdd23fe00 100644
--- a/frontend/packages/console-shared/src/components/index.ts
+++ b/frontend/packages/console-shared/src/components/index.ts
@@ -15,7 +15,6 @@ export * from './virtualized-grid';
export * from './alerts';
export * from './popover';
export * from './utils';
-export * from './modal';
export * from './modals';
export * from './hpa';
export * from './multi-tab-list';
diff --git a/frontend/packages/console-shared/src/components/modal/Modal.tsx b/frontend/packages/console-shared/src/components/modal/Modal.tsx
deleted file mode 100644
index 76d2388c6f3..00000000000
--- a/frontend/packages/console-shared/src/components/modal/Modal.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { LegacyRef, FC } from 'react';
-import type { ModalProps as PfModalProps } from '@patternfly/react-core/deprecated';
-import { Modal as PfModal } from '@patternfly/react-core/deprecated';
-import { css } from '@patternfly/react-styles';
-import './Modal.scss';
-
-type ModalProps = {
- isFullScreen?: boolean;
- ref?: LegacyRef;
-} & PfModalProps;
-
-const Modal: FC = ({ isFullScreen = false, className, ...props }) => (
- (isFullScreen ? document.body : document.querySelector('#modal-container'))}
- />
-);
-
-export default Modal;
diff --git a/frontend/packages/console-shared/src/components/modal/index.ts b/frontend/packages/console-shared/src/components/modal/index.ts
deleted file mode 100644
index c6b35681c7e..00000000000
--- a/frontend/packages/console-shared/src/components/modal/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as Modal } from './Modal';
diff --git a/frontend/packages/console-shared/src/hooks/useCopyCodeModal.tsx b/frontend/packages/console-shared/src/hooks/useCopyCodeModal.tsx
index 0c2b342fa36..e1dd798afa1 100644
--- a/frontend/packages/console-shared/src/hooks/useCopyCodeModal.tsx
+++ b/frontend/packages/console-shared/src/hooks/useCopyCodeModal.tsx
@@ -1,12 +1,15 @@
import { useCallback } from 'react';
+import { Modal, ModalHeader, ModalBody, ModalVariant } from '@patternfly/react-core';
import { useModal } from '@console/dynamic-plugin-sdk/src/lib-core';
import { CopyToClipboard } from '@console/internal/components/utils/copy-to-clipboard';
-import { Modal } from '@console/shared/src/components/modal';
import type { ModalComponent } from 'packages/console-dynamic-plugin-sdk/src/app/modal-support/ModalProvider';
const CopyCodeModal: CopyCodeModalComponent = ({ title, snippet, closeModal }) => (
-
-
+
+
+
+
+
);
diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx
index 443ab4338b1..32406693a97 100644
--- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx
+++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx
@@ -8,6 +8,9 @@ import {
EmptyStateFooter,
EmptyStateVariant,
Truncate,
+ Modal,
+ ModalBody,
+ ModalHeader,
} from '@patternfly/react-core';
import { css } from '@patternfly/react-styles';
import * as _ from 'lodash';
@@ -19,7 +22,6 @@ import { TileViewPage } from '@console/internal/components/utils/tile-view-page'
import i18n from '@console/internal/i18n';
import {
GreenCheckCircleIcon,
- Modal,
COMMUNITY_PROVIDERS_WARNING_LOCAL_STORAGE_KEY as storeKey,
COMMUNITY_PROVIDERS_WARNING_USERSETTINGS_KEY as userSettingsKey,
useUserSettingsCompatibility,
@@ -42,8 +44,9 @@ import {
sourceSort,
validSubscriptionSort,
} from './operator-hub-utils';
-import type { OperatorHubItem, TokenizedAuthProvider } from './index';
import { InfrastructureFeature } from './index';
+import type { OperatorHubItem, TokenizedAuthProvider } from './index';
+import '@console/shared/src/components/catalog/details/CatalogDetailsModal.scss';
// Scoring and priority code no longer used and will be removed with Operator Hub catalog files cleanup effort
const SCORE = {
@@ -1038,65 +1041,64 @@ export const OperatorHubTileView: FC = (props) => {
/>
{detailsItem && (
-
-
-
- {!detailsItem.installed ? (
-
- {t('olm~Install')}
-
- ) : (
- history.push(uninstallLink())}
- variant="secondary"
- >
- {t('olm~Uninstall')}
-
- )}
-
- >
- }
>
-
+
+
+
+
+ {!detailsItem.installed ? (
+
+ {t('olm~Install')}
+
+ ) : (
+ history.push(uninstallLink())}
+ variant="secondary"
+ >
+ {t('olm~Uninstall')}
+
+ )}
+
+
+
+
+
)}
>
diff --git a/frontend/public/components/masthead/masthead-toolbar.tsx b/frontend/public/components/masthead/masthead-toolbar.tsx
index ef56e0ae9be..83f7b979c46 100644
--- a/frontend/public/components/masthead/masthead-toolbar.tsx
+++ b/frontend/public/components/masthead/masthead-toolbar.tsx
@@ -207,6 +207,10 @@ const MastheadToolbarContents: FC = ({
const handleGuidedTourClick = (e) => {
e.preventDefault();
+ // Move focus away from the dropdown menu item to prevent aria-hidden warning
+ if (document.activeElement instanceof HTMLElement) {
+ document.activeElement.blur();
+ }
fireTelemetryEvent('launch-guided-tour-form-help', {
id: 'guided-tour-help',
perspective: activePerspective,
From a3d3623cf79e9f6271174866790e272e01774841 Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Tue, 10 Feb 2026 11:26:23 -0500
Subject: [PATCH 3/9] Migrate DeleteModal and related modals to modern
PatternFly Modal
- Migrate DeleteModal from deprecated factory/modal components to
modern PatternFly v6 Modal components
- Create reusable ModalFooterWithAlerts component for alert display
- Update configure-count-modal and configure-machine-autoscaler-modal
to use modern Modal components and ModalFooterWithAlerts
Co-Authored-By: Claude Sonnet 4.5
---
.../modals/ModalFooterWithAlerts.tsx | 30 +++
.../modals/configure-count-modal.tsx | 37 ++--
.../configure-machine-autoscaler-modal.tsx | 37 ++--
.../public/components/modals/delete-modal.tsx | 200 ++++++++++--------
.../public/components/utils/button-bar.tsx | 2 +-
5 files changed, 172 insertions(+), 134 deletions(-)
create mode 100644 frontend/packages/console-shared/src/components/modals/ModalFooterWithAlerts.tsx
diff --git a/frontend/packages/console-shared/src/components/modals/ModalFooterWithAlerts.tsx b/frontend/packages/console-shared/src/components/modals/ModalFooterWithAlerts.tsx
new file mode 100644
index 00000000000..2d415a5f4d2
--- /dev/null
+++ b/frontend/packages/console-shared/src/components/modals/ModalFooterWithAlerts.tsx
@@ -0,0 +1,30 @@
+import { AlertGroup, Flex, ModalFooter } from '@patternfly/react-core';
+import { ErrorMessage, InfoMessage } from '@console/internal/components/utils/button-bar';
+
+export const ModalFooterWithAlerts: React.FC = ({
+ children,
+ errorMessage,
+ message,
+}) => (
+
+ {(errorMessage || message) && (
+
+ {errorMessage && }
+ {message && }
+
+ )}
+ {children}
+
+);
+
+type ModalFooterWithAlertsProps = {
+ children: React.ReactNode;
+ message?: string;
+ errorMessage?: string;
+};
diff --git a/frontend/public/components/modals/configure-count-modal.tsx b/frontend/public/components/modals/configure-count-modal.tsx
index cc4ad21c343..5b65afe3cc9 100644
--- a/frontend/public/components/modals/configure-count-modal.tsx
+++ b/frontend/public/components/modals/configure-count-modal.tsx
@@ -1,23 +1,14 @@
import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useState, useCallback } from 'react';
-import {
- Modal,
- ModalHeader,
- ModalBody,
- ModalFooter,
- Button,
- HelperText,
- HelperTextItem,
- FormGroup,
- Form,
-} from '@patternfly/react-core';
+import { Modal, ModalHeader, ModalBody, Button, FormGroup, Form } from '@patternfly/react-core';
import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import { k8sPatchResource } from '@console/dynamic-plugin-sdk/src/utils/k8s';
import { K8sResourceKind, K8sModel } from '../../module/k8s';
import { NumberSpinner, NumberSpinnerProps } from '../utils/number-spinner';
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
+import { ModalFooterWithAlerts } from '@console/shared/src/components/modals/ModalFooterWithAlerts';
export const ConfigureCountModal: OverlayComponent = (props) => {
const {
@@ -90,7 +81,7 @@ export const ConfigureCountModal: OverlayComponent = (
description={messageKey ? t(messageKey, messageVariablesSafe) : message}
/>
-
-
-
- {t('public~Cancel')}
-
-
+
+
{buttonTextKey ? t(buttonTextKey, buttonTextVariables) : buttonText}
-
+
+ {t('public~Cancel')}
+
+
);
};
diff --git a/frontend/public/components/modals/configure-machine-autoscaler-modal.tsx b/frontend/public/components/modals/configure-machine-autoscaler-modal.tsx
index 447c9a79520..363319c5467 100644
--- a/frontend/public/components/modals/configure-machine-autoscaler-modal.tsx
+++ b/frontend/public/components/modals/configure-machine-autoscaler-modal.tsx
@@ -2,17 +2,7 @@ import { useState, useCallback } from 'react';
import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom-v5-compat';
-import {
- Modal,
- ModalHeader,
- ModalBody,
- ModalFooter,
- Button,
- HelperText,
- HelperTextItem,
- FormGroup,
- Form,
-} from '@patternfly/react-core';
+import { Modal, ModalHeader, ModalBody, Button, FormGroup, Form } from '@patternfly/react-core';
import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import { MachineAutoscalerModel } from '../../models';
import { NumberSpinner } from '../utils/number-spinner';
@@ -20,6 +10,7 @@ import { resourcePathFromModel } from '../utils/resource-link';
import { K8sResourceKind } from '../../module/k8s';
import { k8sCreateResource } from '@console/dynamic-plugin-sdk/src/utils/k8s';
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
+import { ModalFooterWithAlerts } from '@console/shared/src/components/modals/ModalFooterWithAlerts';
export const ConfigureMachineAutoscalerModal: OverlayComponent = ({
machineSet,
@@ -116,7 +107,7 @@ export const ConfigureMachineAutoscalerModal: OverlayComponent
-
-
-
- {t('public~Cancel')}
-
-
+
+
{t('public~Create')}
-
+
+ {t('public~Cancel')}
+
+
);
};
diff --git a/frontend/public/components/modals/delete-modal.tsx b/frontend/public/components/modals/delete-modal.tsx
index d7023fb00c3..1d0320478cb 100644
--- a/frontend/public/components/modals/delete-modal.tsx
+++ b/frontend/public/components/modals/delete-modal.tsx
@@ -1,17 +1,19 @@
import * as _ from 'lodash';
import type { ReactNode } from 'react';
import { useState, useCallback, useEffect } from 'react';
-import { Alert, Checkbox } from '@patternfly/react-core';
+import {
+ Alert,
+ Button,
+ Checkbox,
+ Modal,
+ ModalBody,
+ ModalHeader,
+ ModalVariant,
+} from '@patternfly/react-core';
import { Trans, 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 {
- ModalTitle,
- ModalBody,
- ModalSubmitFooter,
- ModalWrapper,
- ModalComponentProps,
-} from '../factory/modal';
+import { ModalComponentProps } from '../factory/modal';
import { resourceListPathFromModel, ResourceLink } from '../utils/resource-link';
import {
k8sKill,
@@ -27,6 +29,7 @@ import { findOwner } from '../../module/k8s/managed-by';
import { LocationDescriptor } from 'history';
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
+import { ModalFooterWithAlerts } from '@console/shared/src/components/modals/ModalFooterWithAlerts';
//Modal for resource deletion and allows cascading deletes if propagationPolicy is provided for the enum
export const DeleteModal = (props: DeleteModalProps) => {
@@ -87,93 +90,116 @@ export const DeleteModal = (props: DeleteModalProps) => {
});
const { kind, resource, message } = props;
+
return (
-
-
-
+
+
+ {props.btnText || t('public~Delete')}
+
+
+ {t('public~Cancel')}
+
+
+ >
);
};
export const DeleteModalOverlay: OverlayComponent = (props) => {
- return (
-
-
-
- );
+ const [isOpen, setIsOpen] = useState(true);
+ const handleClose = () => {
+ setIsOpen(false);
+ props.closeOverlay();
+ };
+
+ return isOpen ? (
+
+
+
+ ) : null;
};
export type DeleteModalProps = {
diff --git a/frontend/public/components/utils/button-bar.tsx b/frontend/public/components/utils/button-bar.tsx
index 23ffe0c55b3..1cc41c9d64a 100644
--- a/frontend/public/components/utils/button-bar.tsx
+++ b/frontend/public/components/utils/button-bar.tsx
@@ -29,7 +29,7 @@ export const ErrorMessage = ({ message }) => {
);
};
-const InfoMessage = ({ message }) => (
+export const InfoMessage = ({ message }) => (
Date: Tue, 20 Jan 2026 13:45:01 -0500
Subject: [PATCH 4/9] Migrate DeleteHPAModal and ConsolePluginModal from
createModalLauncher to useOverlay Assisted by: Claude Code
---
.../src/actions/creators/hpa-factory.ts | 124 +++++++++---------
.../ConsoleOperatorConfig.tsx | 6 +-
.../src/components/hpa/DeleteHPAModal.tsx | 27 +++-
.../src/components/hpa/index.ts | 11 +-
.../components/modals/ConsolePluginModal.tsx | 52 +++++---
.../src/components/modals/index.ts | 2 +-
.../src/components/clusterserviceversion.tsx | 6 +-
7 files changed, 137 insertions(+), 91 deletions(-)
diff --git a/frontend/packages/console-app/src/actions/creators/hpa-factory.ts b/frontend/packages/console-app/src/actions/creators/hpa-factory.ts
index 7ad609ca5a6..bc064bb5c02 100644
--- a/frontend/packages/console-app/src/actions/creators/hpa-factory.ts
+++ b/frontend/packages/console-app/src/actions/creators/hpa-factory.ts
@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import i18next from 'i18next';
import type { Action } from '@console/dynamic-plugin-sdk';
+import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook';
import { HorizontalPodAutoscalerModel } from '@console/internal/models';
import type {
@@ -12,84 +13,23 @@ import type {
import { referenceForModel } from '@console/internal/module/k8s';
import type { ClusterServiceVersionKind } from '@console/operator-lifecycle-manager';
import { ClusterServiceVersionModel } from '@console/operator-lifecycle-manager';
-import deleteHPAModal from '@console/shared/src/components/hpa/DeleteHPAModal';
+import { LazyDeleteHPAModalOverlay } from '@console/shared/src/components/hpa';
import { isHelmResource } from '@console/shared/src/utils/helm-utils';
import { doesHpaMatch } from '@console/shared/src/utils/hpa-utils';
import { isOperatorBackedService } from '@console/shared/src/utils/operator-utils';
-import type { ResourceActionFactory } from './types';
const hpaRoute = (
{ metadata: { name = '', namespace = '' } = {} }: K8sResourceCommon,
kind: K8sKind,
) => `/workload-hpa/ns/${namespace}/${referenceForModel(kind)}/${name}`;
-export const HpaActionFactory: ResourceActionFactory = {
- AddHorizontalPodAutoScaler: (kind: K8sKind, obj: K8sResourceKind) => ({
- id: 'add-hpa',
- label: i18next.t('console-app~Add HorizontalPodAutoscaler'),
- cta: { href: hpaRoute(obj, kind) },
- insertBefore: 'add-pdb',
- accessReview: {
- group: HorizontalPodAutoscalerModel.apiGroup,
- resource: HorizontalPodAutoscalerModel.plural,
- namespace: obj.metadata?.namespace,
- verb: 'create',
- },
- }),
- EditHorizontalPodAutoScaler: (kind: K8sKind, obj: K8sResourceCommon) => ({
- id: 'edit-hpa',
- label: i18next.t('console-app~Edit HorizontalPodAutoscaler'),
- cta: { href: hpaRoute(obj, kind) },
- insertBefore: 'add-pdb',
- accessReview: {
- group: HorizontalPodAutoscalerModel.apiGroup,
- resource: HorizontalPodAutoscalerModel.plural,
- namespace: obj.metadata?.namespace,
- verb: 'update',
- },
- }),
- DeleteHorizontalPodAutoScaler: (
- kind: K8sKind,
- obj: K8sResourceCommon,
- relatedHPA: HorizontalPodAutoscalerKind,
- ) => ({
- id: 'delete-hpa',
- label: i18next.t('console-app~Remove HorizontalPodAutoscaler'),
- insertBefore: 'delete-pdb',
- cta: () => {
- deleteHPAModal({
- workload: obj,
- hpa: relatedHPA,
- });
- },
- accessReview: {
- group: HorizontalPodAutoscalerModel.apiGroup,
- resource: HorizontalPodAutoscalerModel.plural,
- namespace: obj.metadata?.namespace,
- verb: 'delete',
- },
- }),
-};
-
-export const getHpaActions = (
- kind: K8sKind,
- obj: K8sResourceKind,
- relatedHPAs: K8sResourceKind[],
-): Action[] => {
- if (relatedHPAs.length === 0) return [HpaActionFactory.AddHorizontalPodAutoScaler(kind, obj)];
-
- return [
- HpaActionFactory.EditHorizontalPodAutoScaler(kind, obj),
- HpaActionFactory.DeleteHorizontalPodAutoScaler(kind, obj, relatedHPAs[0]),
- ];
-};
-
type DeploymentActionExtraResources = {
hpas: HorizontalPodAutoscalerKind[];
csvs: ClusterServiceVersionKind[];
};
export const useHPAActions = (kindObj: K8sKind, resource: K8sResourceKind) => {
+ const launchOverlay = useOverlay();
const namespace = resource?.metadata?.namespace;
const watchedResources = useMemo(
@@ -121,9 +61,63 @@ export const useHPAActions = (kindObj: K8sKind, resource: K8sResourceKind) => {
[extraResources.csvs.data, resource],
);
- const result = useMemo<[Action[], HorizontalPodAutoscalerKind[]]>(() => {
- return [supportsHPA ? getHpaActions(kindObj, resource, relatedHPAs) : [], relatedHPAs];
+ const actions = useMemo(() => {
+ if (!supportsHPA) return [];
+
+ if (relatedHPAs.length === 0) {
+ return [
+ {
+ id: 'add-hpa',
+ label: i18next.t('console-app~Add HorizontalPodAutoscaler'),
+ cta: { href: hpaRoute(resource, kindObj) },
+ insertBefore: 'add-pdb',
+ accessReview: {
+ group: HorizontalPodAutoscalerModel.apiGroup,
+ resource: HorizontalPodAutoscalerModel.plural,
+ namespace: resource.metadata?.namespace,
+ verb: 'create',
+ },
+ },
+ ];
+ }
+ return [
+ {
+ id: 'edit-hpa',
+ label: i18next.t('console-app~Edit HorizontalPodAutoscaler'),
+ cta: { href: hpaRoute(resource, kindObj) },
+ insertBefore: 'add-pdb',
+ accessReview: {
+ group: HorizontalPodAutoscalerModel.apiGroup,
+ resource: HorizontalPodAutoscalerModel.plural,
+ namespace: resource.metadata?.namespace,
+ verb: 'update',
+ },
+ },
+ {
+ id: 'delete-hpa',
+ label: i18next.t('console-app~Remove HorizontalPodAutoscaler'),
+ insertBefore: 'delete-pdb',
+ cta: () => {
+ launchOverlay(LazyDeleteHPAModalOverlay, {
+ workload: resource,
+ hpa: relatedHPAs[0],
+ });
+ },
+ accessReview: {
+ group: HorizontalPodAutoscalerModel.apiGroup,
+ resource: HorizontalPodAutoscalerModel.plural,
+ namespace: resource.metadata?.namespace,
+ verb: 'delete',
+ },
+ },
+ ];
+ // missing launchModal dependency, that causes max depth exceeded error
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [kindObj, relatedHPAs, resource, supportsHPA]);
+ const result = useMemo<[Action[], HorizontalPodAutoscalerKind[]]>(() => {
+ return [actions, relatedHPAs];
+ }, [actions, relatedHPAs]);
+
return result;
};
diff --git a/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx b/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx
index 296805d7ce8..e537b8c80c4 100644
--- a/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx
+++ b/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx
@@ -11,6 +11,7 @@ import { Link } from 'react-router-dom';
import { useLocation } from 'react-router-dom-v5-compat';
import type { WatchK8sResource } from '@console/dynamic-plugin-sdk';
import { useAccessReview } from '@console/dynamic-plugin-sdk';
+import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
import {
getGroupVersionKindForModel,
getReferenceForModel,
@@ -35,7 +36,7 @@ import { referenceForModel } from '@console/internal/module/k8s';
import type { RootState } from '@console/internal/redux';
import { usePluginInfo } from '@console/plugin-sdk/src/api/usePluginInfo';
import PaneBody from '@console/shared/src/components/layout/PaneBody';
-import { consolePluginModal } from '@console/shared/src/components/modals/ConsolePluginModal';
+import { LazyConsolePluginModalOverlay } from '@console/shared/src/components/modals';
import {
GreenCheckCircleIcon,
YellowExclamationTriangleIcon,
@@ -102,6 +103,7 @@ export const ConsolePluginEnabledStatus: FC = (
enabled,
}) => {
const { t } = useTranslation('console-app');
+ const launchModal = useOverlay();
const {
consoleOperatorConfig,
@@ -121,7 +123,7 @@ export const ConsolePluginEnabledStatus: FC = (
type="button"
isInline
onClick={() =>
- consolePluginModal({
+ launchModal(LazyConsolePluginModalOverlay, {
consoleOperatorConfig,
pluginName,
trusted: false,
diff --git a/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx b/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
index 309527cc92d..ce584949dad 100644
--- a/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
+++ b/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.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 } from 'react-i18next';
+import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import type { ModalComponentProps } from '@console/internal/components/factory/modal';
import {
- createModalLauncher,
ModalBody,
ModalSubmitFooter,
ModalTitle,
+ ModalWrapper,
} from '@console/internal/components/factory/modal';
import { LoadingInline } from '@console/internal/components/utils/status-box';
import { HorizontalPodAutoscalerModel } from '@console/internal/models';
@@ -21,7 +22,7 @@ type DeleteHPAModalProps = ModalComponentProps & {
workload: K8sResourceCommon;
};
-const DeleteHPAModal: FC = ({ close, hpa, workload }) => {
+const DeleteHPAModal: FC = ({ close, cancel, hpa, workload }) => {
const [submitError, setSubmitError] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const { t } = useTranslation();
@@ -79,11 +80,29 @@ const DeleteHPAModal: FC = ({ close, hpa, workload }) => {
submitText={t('console-shared~Remove')}
submitDanger
submitDisabled={!!submitError}
- cancel={close}
+ cancel={cancel}
/>
);
};
-export default createModalLauncher(DeleteHPAModal);
+type DeleteHPAModalProviderProps = {
+ hpa: HorizontalPodAutoscalerKind;
+ workload: K8sResourceCommon;
+};
+
+const DeleteHPAModalProvider: OverlayComponent = (props) => {
+ return (
+
+
+
+ );
+};
+
+export default DeleteHPAModalProvider;
diff --git a/frontend/packages/console-shared/src/components/hpa/index.ts b/frontend/packages/console-shared/src/components/hpa/index.ts
index fc131d9b545..53549d32346 100644
--- a/frontend/packages/console-shared/src/components/hpa/index.ts
+++ b/frontend/packages/console-shared/src/components/hpa/index.ts
@@ -1 +1,10 @@
-export { default as deleteHPAModal } from './DeleteHPAModal';
+import { lazy } from 'react';
+
+export { DeleteHPAModalOverlay } from './DeleteHPAModal';
+
+// Lazy-loaded OverlayComponent for DeleteHPAModal
+export const LazyDeleteHPAModalOverlay = lazy(() =>
+ import('./DeleteHPAModal' /* webpackChunkName: "delete-hpa-modal" */).then((m) => ({
+ default: m.DeleteHPAModalOverlay,
+ })),
+);
\ No newline at end of file
diff --git a/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx b/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
index 84fe9826978..8347c781c0d 100644
--- a/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
@@ -1,11 +1,11 @@
import { useState } from 'react';
-import { Form } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
+import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import {
- createModalLauncher,
ModalTitle,
ModalBody,
ModalSubmitFooter,
+ ModalWrapper,
} from '@console/internal/components/factory/modal';
import { ConsoleOperatorConfigModel } from '@console/internal/models';
import type { K8sResourceKind } from '@console/internal/module/k8s';
@@ -49,19 +49,17 @@ export const ConsolePluginModal = (props: ConsolePluginModalProps) => {
'console-shared~This console plugin provides a custom interface that can be included in the console. Updating the enablement of this console plugin will prompt for the console to be refreshed once it has been updated. Make sure you trust this console plugin before enabling.',
)}
-
-
-
-
+
+
{
);
};
-export const consolePluginModal = createModalLauncher(ConsolePluginModal);
+type ConsolePluginModalProviderProps = {
+ consoleOperatorConfig: K8sResourceKind;
+ csvPluginsCount?: number;
+ pluginName: string;
+ trusted: boolean;
+};
+
+const ConsolePluginModalProvider: OverlayComponent = (props) => {
+ return (
+
+
+
+ );
+};
+
+export default ConsolePluginModalProvider;
export type ConsolePluginModalProps = {
consoleOperatorConfig: K8sResourceKind;
diff --git a/frontend/packages/console-shared/src/components/modals/index.ts b/frontend/packages/console-shared/src/components/modals/index.ts
index ac902b242de..53f05c1275a 100644
--- a/frontend/packages/console-shared/src/components/modals/index.ts
+++ b/frontend/packages/console-shared/src/components/modals/index.ts
@@ -1,6 +1,6 @@
export const consolePluginModal = (props) =>
import('./ConsolePluginModal' /* webpackChunkName: "shared-modals" */).then((m) =>
- m.consolePluginModal(props),
+ m.default(props),
);
export const deleteResourceModal = (props) =>
diff --git a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
index 8429d635d4b..00964b06dea 100644
--- a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
+++ b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
@@ -30,6 +30,7 @@ import {
useAccessReviewAllowed,
useAccessReview,
} from '@console/dynamic-plugin-sdk';
+import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
import { getGroupVersionKindForModel } from '@console/dynamic-plugin-sdk/src/lib-core';
import { Conditions, ConditionTypes } from '@console/internal/components/conditions';
import { ResourceEventStream } from '@console/internal/components/events';
@@ -66,7 +67,7 @@ import { DocumentTitle } from '@console/shared/src/components/document-title/Doc
import { withFallback } from '@console/shared/src/components/error';
import PaneBody from '@console/shared/src/components/layout/PaneBody';
import { ExternalLink } from '@console/shared/src/components/links/ExternalLink';
-import { consolePluginModal } from '@console/shared/src/components/modals';
+import ConsolePluginModalProvider from '@console/shared/src/components/modals/ConsolePluginModal';
import { RedExclamationCircleIcon } from '@console/shared/src/components/status/icons';
import { CONSOLE_OPERATOR_CONFIG_NAME } from '@console/shared/src/constants';
import { useActiveNamespace } from '@console/shared/src/hooks/redux-selectors';
@@ -228,6 +229,7 @@ const ManagedNamespaces: FC = ({ obj }) => {
};
const ConsolePlugins: FC = ({ csvPlugins, trusted }) => {
+ const launchOverlay = useOverlay();
const console: WatchK8sResource = {
kind: referenceForModel(ConsoleOperatorConfigModel),
isList: false,
@@ -262,7 +264,7 @@ const ConsolePlugins: FC = ({ csvPlugins, trusted }) => {
type="button"
isInline
onClick={() =>
- consolePluginModal({
+ launchOverlay(ConsolePluginModalProvider, {
consoleOperatorConfig,
pluginName,
trusted,
From e11a8f9945ce8544349048041a4e1bf2db6b4081 Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Thu, 29 Jan 2026 14:03:31 -0500
Subject: [PATCH 5/9] CONSOLE-4990: Migrate console-shared to useNavigate hook
Replace history object usage with useNavigate hook in console-shared package.
Changes:
- ActionMenuItem.tsx: Replace history.push with navigate
- CatalogTile.tsx: Replace history.push with navigate
- CatalogView.tsx: Replace history.push with navigate
- catalog-utils.tsx: Accept NavigateFunction as parameter
- dynamic-form/index.tsx: Replace history.goBack with navigate(-1)
- error-boundary.tsx: Use componentDidUpdate instead of key prop for location-based reset
- DeleteResourceModal.tsx: Replace history.push with navigate
- MultiTabListPage.tsx: Replace history.push with navigate
Migration patterns:
- Components: Added useNavigate() hook
- Utilities: Parameter injection for NavigateFunction
- ErrorBoundary: Pass locationPathname as prop and use componentDidUpdate to reset
error state only when there's an active error AND location changes. This avoids
unnecessary component remounting during tab navigation.
Co-Authored-By: Claude Sonnet 4.5
---
.../actions/menu/ActionMenuItem.tsx | 7 +--
.../src/components/catalog/CatalogTile.tsx | 5 +-
.../catalog/catalog-view/CatalogView.tsx | 46 ++++++++++++-------
.../catalog/utils/catalog-utils.tsx | 14 ++++--
.../src/components/dynamic-form/index.tsx | 7 ++-
.../src/components/error/error-boundary.tsx | 38 ++++++++++-----
.../components/modals/DeleteResourceModal.tsx | 5 +-
.../multi-tab-list/MultiTabListPage.tsx | 6 +--
8 files changed, 83 insertions(+), 45 deletions(-)
diff --git a/frontend/packages/console-shared/src/components/actions/menu/ActionMenuItem.tsx b/frontend/packages/console-shared/src/components/actions/menu/ActionMenuItem.tsx
index 185ab86cae1..e1522ce88be 100644
--- a/frontend/packages/console-shared/src/components/actions/menu/ActionMenuItem.tsx
+++ b/frontend/packages/console-shared/src/components/actions/menu/ActionMenuItem.tsx
@@ -5,10 +5,10 @@ import { KeyTypes, MenuItem, Tooltip } from '@patternfly/react-core';
import { css } from '@patternfly/react-styles';
import * as _ from 'lodash';
import { connect } from 'react-redux';
+import { useNavigate } from 'react-router-dom-v5-compat';
import type { Action, ImpersonateKind } from '@console/dynamic-plugin-sdk';
import { impersonateStateToProps } from '@console/dynamic-plugin-sdk';
import { useAccessReview } from '@console/internal/components/utils/rbac';
-import { history } from '@console/internal/components/utils/router';
export type ActionMenuItemProps = {
action: Action;
@@ -26,6 +26,7 @@ const ActionItem: FC = ({
isAllowed,
component,
}) => {
+ const navigate = useNavigate();
const { label, icon, disabled, cta } = action;
const { href, external } = cta as { href: string; external?: boolean };
const isDisabled = !isAllowed || disabled;
@@ -38,13 +39,13 @@ const ActionItem: FC = ({
cta();
} else if (_.isObject(cta)) {
if (!cta.external) {
- history.push(cta.href);
+ navigate(cta.href);
}
}
onClick && onClick();
event.stopPropagation();
},
- [cta, onClick],
+ [cta, onClick, navigate],
);
const handleKeyDown = (event) => {
diff --git a/frontend/packages/console-shared/src/components/catalog/CatalogTile.tsx b/frontend/packages/console-shared/src/components/catalog/CatalogTile.tsx
index c6b88b9100e..774fe624eba 100644
--- a/frontend/packages/console-shared/src/components/catalog/CatalogTile.tsx
+++ b/frontend/packages/console-shared/src/components/catalog/CatalogTile.tsx
@@ -7,8 +7,8 @@ import {
import { Badge } from '@patternfly/react-core';
import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom-v5-compat';
import type { CatalogItem } from '@console/dynamic-plugin-sdk/src/extensions';
-import { history } from '@console/internal/components/utils/router';
import { isModifiedEvent } from '../../utils';
import CatalogBadges from './CatalogBadges';
import { getIconProps } from './utils/catalog-utils';
@@ -25,6 +25,7 @@ type CatalogTileProps = {
const CatalogTile: FC = ({ item, catalogTypes, onClick, href }) => {
const { t } = useTranslation();
+ const navigate = useNavigate();
const { uid, name, title, provider, description, type, typeLabel, badges } = item;
const vendor = provider ? t('console-shared~Provided by {{provider}}', { provider }) : null;
const catalogType = _.find(catalogTypes, ['value', type]);
@@ -46,7 +47,7 @@ const CatalogTile: FC = ({ item, catalogTypes, onClick, href }
if (onClick) {
onClick(item);
} else if (href) {
- history.push(href);
+ navigate(href);
}
}}
href={href}
diff --git a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx
index a3e387bca8c..939a3065e9a 100644
--- a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx
+++ b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx
@@ -2,6 +2,7 @@ import type { ReactNode, FC } from 'react';
import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom-v5-compat';
import type { CatalogCategory } from '@console/dynamic-plugin-sdk/src';
import type { CatalogItem } from '@console/dynamic-plugin-sdk/src/extensions';
import { isModalOpen } from '@console/internal/components/modals';
@@ -76,6 +77,7 @@ const CatalogView: FC = ({
sortFilterGroups,
}) => {
const { t } = useTranslation();
+ const navigate = useNavigate();
const queryParams = useQueryParams();
const activeCategoryId = queryParams.get(CatalogQueryParams.CATEGORY) ?? ALL_CATEGORY;
const activeSearchKeyword = queryParams.get(CatalogQueryParams.KEYWORD) ?? '';
@@ -110,37 +112,49 @@ const CatalogView: FC = ({
const clearFilters = useCallback(() => {
const params = new URLSearchParams();
catalogType && items.length > 0 && params.set('catalogType', catalogType);
- setURLParams(params);
+ setURLParams(params, navigate);
// Don't take focus if a modal was opened while the page was loading.
if (!isModalOpen()) {
catalogToolbarRef.current && catalogToolbarRef.current.focus({ preventScroll: true });
}
- }, [catalogType, items.length]);
+ }, [catalogType, items.length, navigate]);
- const handleCategoryChange = (categoryId: string) => {
- updateURLParams(CatalogQueryParams.CATEGORY, categoryId);
- };
+ const handleCategoryChange = useCallback(
+ (categoryId: string) => {
+ updateURLParams(CatalogQueryParams.CATEGORY, categoryId, navigate);
+ },
+ [navigate],
+ );
const handleFilterChange = useCallback(
(filterType, id, value) => {
const updatedFilters = _.set(activeFilters, [filterType, id, 'active'], value);
- updateURLParams(filterType, getFilterSearchParam(updatedFilters[filterType]));
+ updateURLParams(filterType, getFilterSearchParam(updatedFilters[filterType]), navigate);
},
- [activeFilters],
+ [activeFilters, navigate],
);
- const handleSearchKeywordChange = useCallback((searchKeyword) => {
- updateURLParams(CatalogQueryParams.KEYWORD, searchKeyword);
- }, []);
+ const handleSearchKeywordChange = useCallback(
+ (searchKeyword) => {
+ updateURLParams(CatalogQueryParams.KEYWORD, searchKeyword, navigate);
+ },
+ [navigate],
+ );
- const handleGroupingChange = useCallback((grouping) => {
- updateURLParams(CatalogQueryParams.GROUPING, grouping);
- }, []);
+ const handleGroupingChange = useCallback(
+ (grouping) => {
+ updateURLParams(CatalogQueryParams.GROUPING, grouping, navigate);
+ },
+ [navigate],
+ );
- const handleSortOrderChange = useCallback((order) => {
- updateURLParams(CatalogQueryParams.SORT_ORDER, order);
- }, []);
+ const handleSortOrderChange = useCallback(
+ (order) => {
+ updateURLParams(CatalogQueryParams.SORT_ORDER, order, navigate);
+ },
+ [navigate],
+ );
const handleShowAllToggle = useCallback((groupName) => {
setFilterGroupsShowAll((showAll) => {
diff --git a/frontend/packages/console-shared/src/components/catalog/utils/catalog-utils.tsx b/frontend/packages/console-shared/src/components/catalog/utils/catalog-utils.tsx
index f180eb1cb3f..a48c7e9b20a 100644
--- a/frontend/packages/console-shared/src/components/catalog/utils/catalog-utils.tsx
+++ b/frontend/packages/console-shared/src/components/catalog/utils/catalog-utils.tsx
@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import * as _ from 'lodash';
+import type { NavigateFunction } from 'react-router-dom-v5-compat';
import type { CatalogItemType } from '@console/dynamic-plugin-sdk';
import { isCatalogItemType } from '@console/dynamic-plugin-sdk';
import type {
@@ -8,7 +9,6 @@ import type {
CatalogItemMetadataProviderFunction,
} from '@console/dynamic-plugin-sdk/src/extensions';
import { normalizeIconClass } from '@console/internal/components/catalog/catalog-item-icon';
-import { history } from '@console/internal/components/utils/router';
import catalogImg from '@console/internal/imgs/logos/catalog-icon.svg';
import { useExtensions } from '@console/plugin-sdk/src/api/useExtensions';
import type { CatalogType, CatalogTypeCounts } from './types';
@@ -299,14 +299,18 @@ export const getIconProps = (item: CatalogItem) => {
return { iconImg: catalogImg, iconClass: null };
};
-export const setURLParams = (params: URLSearchParams) => {
+export const setURLParams = (params: URLSearchParams, navigate: NavigateFunction) => {
const url = new URL(window.location.href);
const searchParams = `?${params.toString()}${url.hash}`;
- history.replace(`${url.pathname}${searchParams}`);
+ navigate(`${url.pathname}${searchParams}`, { replace: true });
};
-export const updateURLParams = (paramName: string, value: string | string[]) => {
+export const updateURLParams = (
+ paramName: string,
+ value: string | string[],
+ navigate: NavigateFunction,
+) => {
const params = new URLSearchParams(window.location.search);
if (value) {
@@ -314,7 +318,7 @@ export const updateURLParams = (paramName: string, value: string | string[]) =>
} else {
params.delete(paramName);
}
- setURLParams(params);
+ setURLParams(params, navigate);
};
export const getURLWithParams = (paramName: string, value: string | string[]): string => {
diff --git a/frontend/packages/console-shared/src/components/dynamic-form/index.tsx b/frontend/packages/console-shared/src/components/dynamic-form/index.tsx
index b28cbd0750d..306d9102211 100644
--- a/frontend/packages/console-shared/src/components/dynamic-form/index.tsx
+++ b/frontend/packages/console-shared/src/components/dynamic-form/index.tsx
@@ -1,11 +1,12 @@
import type { FC } from 'react';
+import { useCallback } from 'react';
import { Accordion, ActionGroup, Button, Alert } from '@patternfly/react-core';
import type { FormProps } from '@rjsf/core';
import Form from '@rjsf/core';
import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom-v5-compat';
import type { ErrorBoundaryFallbackProps } from '@console/dynamic-plugin-sdk';
-import { history } from '@console/internal/components/utils/router';
import { ErrorBoundary } from '@console/shared/src/components/error';
import { K8S_UI_SCHEMA } from './const';
import defaultFields from './fields';
@@ -42,6 +43,8 @@ export const DynamicForm: FC = ({
...restProps
}) => {
const { t } = useTranslation();
+ const navigate = useNavigate();
+ const handleCancel = useCallback(() => navigate(-1), [navigate]);
const schemaErrors = getSchemaErrors(schema);
// IF the top level schema is unsupported, don't render a form at all.
if (schemaErrors.length) {
@@ -112,7 +115,7 @@ export const DynamicForm: FC = ({
{t('console-shared~Create')}
-
+
{t('console-shared~Cancel')}
diff --git a/frontend/packages/console-shared/src/components/error/error-boundary.tsx b/frontend/packages/console-shared/src/components/error/error-boundary.tsx
index 9ee3be2c5db..3e34e07fefe 100644
--- a/frontend/packages/console-shared/src/components/error/error-boundary.tsx
+++ b/frontend/packages/console-shared/src/components/error/error-boundary.tsx
@@ -1,13 +1,17 @@
import type { ComponentType, ReactNode, FC } from 'react';
import { Component } from 'react';
+import { useLocation } from 'react-router-dom-v5-compat';
import type { ErrorBoundaryFallbackProps } from '@console/dynamic-plugin-sdk';
-import { history } from '@console/internal/components/utils/router';
type ErrorBoundaryProps = {
FallbackComponent?: ComponentType;
children?: ReactNode;
};
+type ErrorBoundaryInnerProps = ErrorBoundaryProps & {
+ locationPathname?: string;
+};
+
/** Needed for tests -- should not be imported by application logic */
export type ErrorBoundaryState = {
hasError: boolean;
@@ -17,9 +21,7 @@ export type ErrorBoundaryState = {
const DefaultFallback: FC = () =>
;
-class ErrorBoundary extends Component {
- unlisten: () => void = () => {};
-
+class ErrorBoundaryInner extends Component {
readonly defaultState: ErrorBoundaryState = {
hasError: false,
error: {
@@ -37,15 +39,16 @@ class ErrorBoundary extends Component {
this.state = this.defaultState;
}
- componentDidMount() {
- this.unlisten = history.listen(() => {
- // reset state to default when location changes
+ componentDidUpdate(prevProps: ErrorBoundaryInnerProps) {
+ // Reset error state when location changes
+ if (
+ this.state.hasError &&
+ prevProps.locationPathname &&
+ this.props.locationPathname !== prevProps.locationPathname
+ ) {
+ // eslint-disable-next-line react/no-did-update-set-state
this.setState(this.defaultState);
- });
- }
-
- componentWillUnmount() {
- this.unlisten();
+ }
}
componentDidCatch(error, errorInfo) {
@@ -75,4 +78,15 @@ class ErrorBoundary extends Component {
}
}
+// Functional wrapper to handle location changes
+const ErrorBoundary: FC = ({ children, FallbackComponent }) => {
+ const location = useLocation();
+
+ return (
+
+ {children}
+
+ );
+};
+
export default ErrorBoundary;
diff --git a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
index 97cdaeaa896..69ece81038d 100644
--- a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
@@ -3,13 +3,13 @@ import { TextInputTypes } from '@patternfly/react-core';
import type { FormikProps, FormikValues } from 'formik';
import { Formik } from 'formik';
import { useTranslation, Trans } from 'react-i18next';
+import { useNavigate } from 'react-router-dom-v5-compat';
import {
createModalLauncher,
ModalTitle,
ModalBody,
ModalSubmitFooter,
} from '@console/internal/components/factory/modal';
-import { history } from '@console/internal/components/utils/router';
import type { K8sResourceKind } from '@console/internal/module/k8s';
import { usePromiseHandler } from '../../hooks/promise-handler';
import { InputField } from '../formik-fields';
@@ -74,6 +74,7 @@ const DeleteResourceForm: FC & DeleteResourceModalProp
const DeleteResourceModal: FC = (props) => {
const [handlePromise] = usePromiseHandler();
+ const navigate = useNavigate();
const handleSubmit = (values: FormikValues, actions) => {
const { onSubmit, close, redirect } = props;
@@ -83,7 +84,7 @@ const DeleteResourceModal: FC = (props) => {
handlePromise(onSubmit(values))
.then(() => {
close();
- redirect && history.push(redirect);
+ redirect && navigate(redirect);
})
.catch((errorMessage) => {
actions.setStatus({ submitError: errorMessage });
diff --git a/frontend/packages/console-shared/src/components/multi-tab-list/MultiTabListPage.tsx b/frontend/packages/console-shared/src/components/multi-tab-list/MultiTabListPage.tsx
index 0ce20da6acd..498cd2a44a5 100644
--- a/frontend/packages/console-shared/src/components/multi-tab-list/MultiTabListPage.tsx
+++ b/frontend/packages/console-shared/src/components/multi-tab-list/MultiTabListPage.tsx
@@ -2,10 +2,9 @@ import type { ReactNode, FC } from 'react';
import { ActionListItem, Button } from '@patternfly/react-core';
import { SimpleDropdown } from '@patternfly/react-templates';
import { useTranslation } from 'react-i18next';
-import { useParams, Link } from 'react-router-dom-v5-compat';
+import { useParams, Link, useNavigate } from 'react-router-dom-v5-compat';
import type { Page } from '@console/internal/components/utils/horizontal-nav';
import { HorizontalNav } from '@console/internal/components/utils/horizontal-nav';
-import { history } from '@console/internal/components/utils/router';
import { referenceForModel } from '@console/internal/module/k8s';
import { PageHeading } from '@console/shared/src/components/heading/PageHeading';
import { PageTitleContext } from '../pagetitle/PageTitleContext';
@@ -29,6 +28,7 @@ const MultiTabListPage: FC = ({
telemetryPrefix,
}) => {
const { t } = useTranslation();
+ const navigate = useNavigate();
const { ns } = useParams();
const onSelectCreateAction = (actionName: string): void => {
const selectedMenuItem: MenuAction = menuActions[actionName];
@@ -42,7 +42,7 @@ const MultiTabListPage: FC = ({
url = selectedMenuItem.onSelection(actionName, selectedMenuItem, url);
}
if (url) {
- history.push(url);
+ navigate(url);
}
};
From 0df4972451c773478f4ca5087dd68c8703dbcee2 Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Fri, 27 Feb 2026 11:11:01 -0500
Subject: [PATCH 6/9] Migrate DeleteResourceModal and router-dependent modals
to React Router v6
---
.../components/modals/DeleteResourceModal.tsx | 47 ++++++++-----
.../src/components/modals/index.ts | 11 ++--
.../dev-console/src/actions/context-menu.ts | 57 +++++++++-------
.../dev-console/src/actions/providers.tsx | 8 +--
.../helm-plugin/src/actions/creators.ts | 66 +++++++++++--------
.../helm-plugin/src/actions/providers.ts | 9 +--
.../packages/helm-plugin/src/actions/types.ts | 2 +-
7 files changed, 120 insertions(+), 80 deletions(-)
diff --git a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
index 69ece81038d..7ad13ccff9b 100644
--- a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
@@ -4,28 +4,19 @@ import type { FormikProps, FormikValues } from 'formik';
import { Formik } from 'formik';
import { useTranslation, Trans } from 'react-i18next';
import { useNavigate } from 'react-router-dom-v5-compat';
+import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import {
- createModalLauncher,
ModalTitle,
ModalBody,
ModalSubmitFooter,
+ ModalWrapper,
} from '@console/internal/components/factory/modal';
+import type { ModalComponentProps } from '@console/internal/components/factory/modal';
import type { K8sResourceKind } from '@console/internal/module/k8s';
import { usePromiseHandler } from '../../hooks/promise-handler';
import { InputField } from '../formik-fields';
import { YellowExclamationTriangleIcon } from '../status';
-type DeleteResourceModalProps = {
- resourceName: string;
- resourceType: string;
- actionLabel?: string; // Used to send translated strings as action label.
- actionLabelKey?: string; // Used to send translation key for action label.
- redirect?: string;
- onSubmit: (values: FormikValues) => Promise;
- cancel?: () => void;
- close?: () => void;
-};
-
const DeleteResourceForm: FC & DeleteResourceModalProps> = ({
handleSubmit,
resourceName,
@@ -84,7 +75,9 @@ const DeleteResourceModal: FC = (props) => {
handlePromise(onSubmit(values))
.then(() => {
close();
- redirect && navigate(redirect);
+ if (redirect) {
+ navigate(redirect);
+ }
})
.catch((errorMessage) => {
actions.setStatus({ submitError: errorMessage });
@@ -103,6 +96,28 @@ const DeleteResourceModal: FC = (props) => {
);
};
-export const deleteResourceModal = createModalLauncher((props: DeleteResourceModalProps) => (
-
-));
+export const DeleteResourceModalOverlay: OverlayComponent = (props) => {
+ return (
+
+
+
+ );
+};
+
+type DeleteResourceModalProps = ModalComponentProps & {
+ resourceName: string;
+ resourceType: string;
+ actionLabel?: string; // Used to send translated strings as action label.
+ actionLabelKey?: string; // Used to send translation key for action label.
+ redirect?: string;
+ onSubmit: (values: FormikValues) => Promise;
+};
diff --git a/frontend/packages/console-shared/src/components/modals/index.ts b/frontend/packages/console-shared/src/components/modals/index.ts
index 53f05c1275a..468ddd64b52 100644
--- a/frontend/packages/console-shared/src/components/modals/index.ts
+++ b/frontend/packages/console-shared/src/components/modals/index.ts
@@ -1,9 +1,12 @@
+import { lazy } from 'react';
+
export const consolePluginModal = (props) =>
import('./ConsolePluginModal' /* webpackChunkName: "shared-modals" */).then((m) =>
m.default(props),
);
-export const deleteResourceModal = (props) =>
- import('./DeleteResourceModal' /* webpackChunkName: "shared-modals" */).then((m) =>
- m.deleteResourceModal(props),
- );
+export const LazyDeleteResourceModalOverlay = lazy(() =>
+ import('./DeleteResourceModal' /* webpackChunkName: "delete-resource-modal" */).then((m) => ({
+ default: m.DeleteResourceModalOverlay,
+ })),
+);
diff --git a/frontend/packages/dev-console/src/actions/context-menu.ts b/frontend/packages/dev-console/src/actions/context-menu.ts
index 70ea9158bbc..ae01022e86f 100644
--- a/frontend/packages/dev-console/src/actions/context-menu.ts
+++ b/frontend/packages/dev-console/src/actions/context-menu.ts
@@ -1,5 +1,4 @@
import { useMemo } from 'react';
-import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
import type { Action, K8sModel } from '@console/dynamic-plugin-sdk';
import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
@@ -7,36 +6,44 @@ import type { TopologyApplicationObject } from '@console/dynamic-plugin-sdk/src/
import { LazyDeleteModalOverlay } from '@console/internal/components/modals';
import { asAccessReview } from '@console/internal/components/utils';
import type { K8sResourceKind } from '@console/internal/module/k8s';
-import { deleteResourceModal } from '@console/shared';
+import { LazyDeleteResourceModalOverlay } from '@console/shared';
import { ApplicationModel } from '@console/topology/src/models';
import { cleanUpWorkload } from '@console/topology/src/utils';
-export const DeleteApplicationAction = (
+export const useDeleteApplicationAction = (
application: TopologyApplicationObject,
resourceModel: K8sModel,
): Action => {
- // accessReview needs a resource but group is not a k8s resource,
- // so currently picking the first resource to do the rbac checks (might change in future)
- const primaryResource = application.resources[0].resource;
- return {
- id: 'delete-application',
- label: i18next.t('devconsole~Delete application'),
- cta: () => {
- const reqs = [];
- deleteResourceModal({
- blocking: true,
- resourceName: application.name,
- resourceType: ApplicationModel.label,
- onSubmit: () => {
- application.resources.forEach((resource) => {
- reqs.push(cleanUpWorkload(resource.resource));
- });
- return Promise.all(reqs);
- },
- });
- },
- accessReview: asAccessReview(resourceModel, primaryResource, 'delete'),
- };
+ const { t } = useTranslation();
+ const launchModal = useOverlay();
+
+ return useMemo(() => {
+ if (!application?.resources?.[0]?.resource) {
+ return null;
+ }
+
+ // accessReview needs a resource but group is not a k8s resource,
+ // so currently picking the first resource to do the rbac checks (might change in future)
+ const primaryResource = application.resources[0].resource;
+ return {
+ id: 'delete-application',
+ label: t('devconsole~Delete application'),
+ cta: () => {
+ const reqs = [];
+ launchModal(LazyDeleteResourceModalOverlay, {
+ resourceName: application.name,
+ resourceType: ApplicationModel.label,
+ onSubmit: () => {
+ application.resources.forEach((resource) => {
+ reqs.push(cleanUpWorkload(resource.resource));
+ });
+ return Promise.all(reqs);
+ },
+ });
+ },
+ accessReview: asAccessReview(resourceModel, primaryResource, 'delete'),
+ };
+ }, [application, resourceModel, t, launchModal]);
};
export const useDeleteResourceAction = (
diff --git a/frontend/packages/dev-console/src/actions/providers.tsx b/frontend/packages/dev-console/src/actions/providers.tsx
index 8bee15c8395..affa549c1d5 100644
--- a/frontend/packages/dev-console/src/actions/providers.tsx
+++ b/frontend/packages/dev-console/src/actions/providers.tsx
@@ -34,7 +34,7 @@ import {
ADD_TO_PROJECT,
} from '../const';
import { AddActions, disabledActionsFilter } from './add-resources';
-import { DeleteApplicationAction } from './context-menu';
+import { useDeleteApplicationAction } from './context-menu';
import { EditImportApplication } from './creators';
type TopologyActionProvider = (data: {
@@ -295,6 +295,7 @@ export const useTopologyApplicationActionProvider: TopologyActionProvider = ({
);
const primaryResource = appData.resources?.[0]?.resource || {};
const [kindObj, inFlight] = useK8sModel(referenceFor(primaryResource));
+ const deleteApplicationAction = useDeleteApplicationAction(appData, kindObj);
return useMemo(() => {
if (element.getType() === TYPE_APPLICATION_GROUP) {
@@ -305,7 +306,7 @@ export const useTopologyApplicationActionProvider: TopologyActionProvider = ({
? `${referenceFor(sourceObj)}/${sourceObj?.metadata?.name}`
: undefined;
const actions = [
- ...(connectorSource ? [] : [DeleteApplicationAction(appData, kindObj)]),
+ ...(connectorSource ? [] : [deleteApplicationAction]),
AddActions.FromGit(namespace, application, sourceReference, path, !isImportResourceAccess),
AddActions.ContainerImage(
namespace,
@@ -352,13 +353,12 @@ export const useTopologyApplicationActionProvider: TopologyActionProvider = ({
element,
inFlight,
connectorSource,
- appData,
- kindObj,
namespace,
application,
isImportResourceAccess,
isCatalogImageResourceAccess,
isServerlessEnabled,
isJavaImageStreamEnabled,
+ deleteApplicationAction,
]);
};
diff --git a/frontend/packages/helm-plugin/src/actions/creators.ts b/frontend/packages/helm-plugin/src/actions/creators.ts
index a17bc43e5ec..5c8e2350071 100644
--- a/frontend/packages/helm-plugin/src/actions/creators.ts
+++ b/frontend/packages/helm-plugin/src/actions/creators.ts
@@ -1,39 +1,53 @@
+import { useMemo } from 'react';
import type { TFunction } from 'i18next';
import type { Action, K8sKind } from '@console/dynamic-plugin-sdk';
+import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';
import { coFetchJSON } from '@console/internal/co-fetch';
import type { K8sResourceKind } from '@console/internal/module/k8s';
import { referenceFor } from '@console/internal/module/k8s';
-import { deleteResourceModal } from '@console/shared';
+import { LazyDeleteResourceModalOverlay } from '@console/shared';
import { ProjectHelmChartRepositoryModel } from '../models';
import type { HelmActionsScope } from './types';
-export const getHelmDeleteAction = (
- {
- release: { name: releaseName, namespace, version: releaseVersion },
- redirect,
- }: HelmActionsScope,
- t: TFunction,
-): Action => ({
- id: 'delete-helm',
- label: t('helm-plugin~Delete Helm Release'),
- cta: () => {
- deleteResourceModal({
- blocking: true,
- resourceName: releaseName,
- resourceType: 'Helm Release',
- actionLabel: t('helm-plugin~Delete'),
+export const useHelmDeleteAction = (scope: HelmActionsScope, t: TFunction): Action => {
+ const launchModal = useOverlay();
+
+ return useMemo(() => {
+ if (!scope?.release) {
+ return {
+ id: 'delete-helm',
+ label: t('helm-plugin~Delete Helm Release'),
+ cta: () => {},
+ };
+ }
+
+ const {
+ release: { name: releaseName, namespace, version: releaseVersion },
redirect,
- onSubmit: () => {
- return coFetchJSON.delete(
- `/api/helm/release/async?name=${releaseName}&ns=${namespace}&version=${releaseVersion}`,
- null,
- null,
- -1,
- );
+ } = scope;
+
+ return {
+ id: 'delete-helm',
+ label: t('helm-plugin~Delete Helm Release'),
+ cta: () => {
+ launchModal(LazyDeleteResourceModalOverlay, {
+ resourceName: releaseName,
+ resourceType: 'Helm Release',
+ actionLabel: t('helm-plugin~Delete'),
+ redirect,
+ onSubmit: () => {
+ return coFetchJSON.delete(
+ `/api/helm/release/async?name=${releaseName}&ns=${namespace}&version=${releaseVersion}`,
+ null,
+ null,
+ -1,
+ );
+ },
+ });
},
- });
- },
-});
+ };
+ }, [scope, t, launchModal]);
+};
export const getHelmUpgradeAction = (
{ release: { name: releaseName, namespace }, actionOrigin }: HelmActionsScope,
diff --git a/frontend/packages/helm-plugin/src/actions/providers.ts b/frontend/packages/helm-plugin/src/actions/providers.ts
index 3f699626072..418702d76ee 100644
--- a/frontend/packages/helm-plugin/src/actions/providers.ts
+++ b/frontend/packages/helm-plugin/src/actions/providers.ts
@@ -20,7 +20,7 @@ import { TYPE_HELM_RELEASE } from '../topology/components/const';
import { HelmReleaseStatus } from '../types/helm-types';
import { AddHelmChartAction } from './add-resources';
import {
- getHelmDeleteAction,
+ useHelmDeleteAction,
getHelmRollbackAction,
getHelmUpgradeAction,
editChartRepository,
@@ -29,25 +29,26 @@ import type { HelmActionsScope } from './types';
export const useHelmActionProvider = (scope: HelmActionsScope) => {
const { t } = useTranslation();
+ const helmDeleteAction = useHelmDeleteAction(scope, t);
const result = useMemo(() => {
if (!scope) return [[], true, undefined];
switch (scope?.release?.info?.status) {
case HelmReleaseStatus.PendingInstall:
case HelmReleaseStatus.PendingRollback:
case HelmReleaseStatus.PendingUpgrade:
- return [[getHelmDeleteAction(scope, t)], true, undefined];
+ return [[helmDeleteAction], true, undefined];
default:
return [
[
getHelmUpgradeAction(scope, t),
...(Number(scope.release.version) > 1 ? [getHelmRollbackAction(scope, t)] : []),
- getHelmDeleteAction(scope, t),
+ helmDeleteAction,
],
true,
undefined,
];
}
- }, [scope, t]);
+ }, [scope, t, helmDeleteAction]);
return result;
};
diff --git a/frontend/packages/helm-plugin/src/actions/types.ts b/frontend/packages/helm-plugin/src/actions/types.ts
index 0cd4b2fc537..45caa47af46 100644
--- a/frontend/packages/helm-plugin/src/actions/types.ts
+++ b/frontend/packages/helm-plugin/src/actions/types.ts
@@ -10,5 +10,5 @@ type HelmActionObj = {
export type HelmActionsScope = {
release: HelmRelease | HelmActionObj;
actionOrigin?: string;
- redirect?: boolean;
+ redirect?: string;
};
From 0d9cf042159f37f5143adf968e150b860ab24f10 Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Fri, 27 Feb 2026 11:21:18 -0500
Subject: [PATCH 7/9] Add lazy loading to modals and migrate DeleteHPAModal to
PatternFly Modal
- Add LazyConsolePluginModalOverlay export with lazy loading
- Update clusterserviceversion.tsx to use lazy-loaded modal
- Migrate DeleteHPAModal from deprecated factory/modal to modern PatternFly Modal
- Use ModalHeader, ModalBody, and ModalFooterWithAlerts components
- Replace manual state management with usePromiseHandler hook
Co-Authored-By: Claude Sonnet 4.5
---
.../src/components/modals/ConsolePluginModal.tsx | 6 ++++--
.../console-shared/src/components/modals/index.ts | 10 ++++++----
.../src/components/clusterserviceversion.tsx | 4 ++--
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx b/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
index 8347c781c0d..a0891031238 100644
--- a/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
@@ -79,7 +79,9 @@ type ConsolePluginModalProviderProps = {
trusted: boolean;
};
-const ConsolePluginModalProvider: OverlayComponent = (props) => {
+export const ConsolePluginModalOverlay: OverlayComponent = (
+ props,
+) => {
return (
- import('./ConsolePluginModal' /* webpackChunkName: "shared-modals" */).then((m) =>
- m.default(props),
- );
+// Lazy-loaded OverlayComponent for ConsolePluginModal
+export const LazyConsolePluginModalOverlay = lazy(() =>
+ import('./ConsolePluginModal' /* webpackChunkName: "shared-modals" */).then((m) => ({
+ default: m.ConsolePluginModalOverlay,
+ })),
+);
export const LazyDeleteResourceModalOverlay = lazy(() =>
import('./DeleteResourceModal' /* webpackChunkName: "delete-resource-modal" */).then((m) => ({
diff --git a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
index 00964b06dea..b01ff139434 100644
--- a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
+++ b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
@@ -67,7 +67,7 @@ import { DocumentTitle } from '@console/shared/src/components/document-title/Doc
import { withFallback } from '@console/shared/src/components/error';
import PaneBody from '@console/shared/src/components/layout/PaneBody';
import { ExternalLink } from '@console/shared/src/components/links/ExternalLink';
-import ConsolePluginModalProvider from '@console/shared/src/components/modals/ConsolePluginModal';
+import { LazyConsolePluginModalOverlay } from '@console/shared/src/components/modals';
import { RedExclamationCircleIcon } from '@console/shared/src/components/status/icons';
import { CONSOLE_OPERATOR_CONFIG_NAME } from '@console/shared/src/constants';
import { useActiveNamespace } from '@console/shared/src/hooks/redux-selectors';
@@ -264,7 +264,7 @@ const ConsolePlugins: FC = ({ csvPlugins, trusted }) => {
type="button"
isInline
onClick={() =>
- launchOverlay(ConsolePluginModalProvider, {
+ launchOverlay(LazyConsolePluginModalOverlay, {
consoleOperatorConfig,
pluginName,
trusted,
From 7431f8ba0707ca8a5b9c50eaa0743b81be3f416b Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Fri, 27 Feb 2026 11:30:15 -0500
Subject: [PATCH 8/9] Batch renaming for consistency
- Rename launchOverlay to launchModal in useHPAActions and ConsolePlugins
- Rename DeleteHPAModalProvider to DeleteHPAModalOverlay
- Rename DeleteHPAModalProviderProps to DeleteHPAModalOverlayProps
- Export DeleteHPAModalOverlay with named export instead of default
Assisted-by: Claude
---
.../console-app/src/actions/creators/hpa-factory.ts | 6 +++---
.../console-shared/src/components/hpa/DeleteHPAModal.tsx | 6 +++---
.../src/components/clusterserviceversion.tsx | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/frontend/packages/console-app/src/actions/creators/hpa-factory.ts b/frontend/packages/console-app/src/actions/creators/hpa-factory.ts
index bc064bb5c02..16eaf393e42 100644
--- a/frontend/packages/console-app/src/actions/creators/hpa-factory.ts
+++ b/frontend/packages/console-app/src/actions/creators/hpa-factory.ts
@@ -29,7 +29,7 @@ type DeploymentActionExtraResources = {
};
export const useHPAActions = (kindObj: K8sKind, resource: K8sResourceKind) => {
- const launchOverlay = useOverlay();
+ const launchModal = useOverlay();
const namespace = resource?.metadata?.namespace;
const watchedResources = useMemo(
@@ -98,7 +98,7 @@ export const useHPAActions = (kindObj: K8sKind, resource: K8sResourceKind) => {
label: i18next.t('console-app~Remove HorizontalPodAutoscaler'),
insertBefore: 'delete-pdb',
cta: () => {
- launchOverlay(LazyDeleteHPAModalOverlay, {
+ launchModal(LazyDeleteHPAModalOverlay, {
workload: resource,
hpa: relatedHPAs[0],
});
@@ -111,7 +111,7 @@ export const useHPAActions = (kindObj: K8sKind, resource: K8sResourceKind) => {
},
},
];
- // missing launchModal dependency, that causes max depth exceeded error
+ // Missing launchModal dependency causes max depth exceeded error
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [kindObj, relatedHPAs, resource, supportsHPA]);
diff --git a/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx b/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
index ce584949dad..3958d7385e1 100644
--- a/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
+++ b/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
@@ -87,12 +87,12 @@ const DeleteHPAModal: FC = ({ close, cancel, hpa, workload
);
};
-type DeleteHPAModalProviderProps = {
+type DeleteHPAModalOverlayProps = {
hpa: HorizontalPodAutoscalerKind;
workload: K8sResourceCommon;
};
-const DeleteHPAModalProvider: OverlayComponent = (props) => {
+const DeleteHPAModalOverlay: OverlayComponent = (props) => {
return (
= (p
);
};
-export default DeleteHPAModalProvider;
+export { DeleteHPAModalOverlay };
diff --git a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
index b01ff139434..3b3ae1658ec 100644
--- a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
+++ b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
@@ -229,7 +229,7 @@ const ManagedNamespaces: FC = ({ obj }) => {
};
const ConsolePlugins: FC = ({ csvPlugins, trusted }) => {
- const launchOverlay = useOverlay();
+ const launchModal = useOverlay();
const console: WatchK8sResource = {
kind: referenceForModel(ConsoleOperatorConfigModel),
isList: false,
@@ -264,7 +264,7 @@ const ConsolePlugins: FC = ({ csvPlugins, trusted }) => {
type="button"
isInline
onClick={() =>
- launchOverlay(LazyConsolePluginModalOverlay, {
+ launchModal(LazyConsolePluginModalOverlay, {
consoleOperatorConfig,
pluginName,
trusted,
From 02947822e0d3b6230216d9ad9666a8ee6b835015 Mon Sep 17 00:00:00 2001
From: Robb Hamilton
Date: Fri, 27 Feb 2026 12:40:57 -0500
Subject: [PATCH 9/9] CONSOLE-4447: Migrate modals to PatternFly v6 Modal
components
Migrates DeleteHPAModal, ConsolePluginModal, and DeleteResourceModal
from deprecated factory modal components to modern PatternFly v6 Modal.
Changes:
- Replace ModalWrapper with Modal component with state management
- Replace ModalTitle with ModalHeader
- Replace ModalSubmitFooter with ModalFooterWithAlerts
- Replace HTML form with PatternFly Form component
- Use modern Button components with consistent loading/disabled states
- Maintain Formik integration in DeleteResourceModal
Co-Authored-By: Claude Sonnet 4.5
---
.../locales/en/console-shared.json | 3 +-
.../src/components/hpa/DeleteHPAModal.tsx | 106 ++++++++---------
.../components/modals/ConsolePluginModal.tsx | 108 ++++++++++--------
.../components/modals/DeleteResourceModal.tsx | 100 +++++++++-------
frontend/public/locales/en/public.json | 4 +-
5 files changed, 179 insertions(+), 142 deletions(-)
diff --git a/frontend/packages/console-shared/locales/en/console-shared.json b/frontend/packages/console-shared/locales/en/console-shared.json
index 98dda309822..d7e135f9488 100644
--- a/frontend/packages/console-shared/locales/en/console-shared.json
+++ b/frontend/packages/console-shared/locales/en/console-shared.json
@@ -174,7 +174,6 @@
"Container {{containersName}} does not have health checks to ensure your Application is running correctly.": "Container {{containersName}} does not have health checks to ensure your Application is running correctly.",
"Health checks": "Health checks",
"Add health checks": "Add health checks",
- "Unknown error removing {{hpaLabel}} {{hpaName}}.": "Unknown error removing {{hpaLabel}} {{hpaName}}.",
"Remove {{label}}?": "Remove {{label}}?",
"Are you sure you want to remove the {{hpaLabel}}": "Are you sure you want to remove the {{hpaLabel}}",
"from": "from",
@@ -205,7 +204,7 @@
"Description": "Description",
"Delete": "Delete",
"This action cannot be undone. All associated Deployments, Routes, Builds, Pipelines, Storage/PVCs, Secrets, and ConfigMaps will be deleted.": "This action cannot be undone. All associated Deployments, Routes, Builds, Pipelines, Storage/PVCs, Secrets, and ConfigMaps will be deleted.",
- "Confirm deletion by typing <1>{{resourceName}}1> below:": "Confirm deletion by typing <1>{{resourceName}}1> below:",
+ "Confirm deletion by typing <2>{{resourceName}}2> below:": "Confirm deletion by typing <2>{{resourceName}}2> below:",
"Description:": "Description:",
"Component trace:": "Component trace:",
"Stack trace:": "Stack trace:",
diff --git a/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx b/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
index 3958d7385e1..a001db08542 100644
--- a/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
+++ b/frontend/packages/console-shared/src/components/hpa/DeleteHPAModal.tsx
@@ -1,21 +1,16 @@
-import type { FC } from 'react';
import { useState } from 'react';
-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 type { FC } from 'react';
+import { Button, Form, Modal, ModalBody, ModalHeader, ModalVariant } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import type { ModalComponentProps } from '@console/internal/components/factory/modal';
-import {
- ModalBody,
- ModalSubmitFooter,
- ModalTitle,
- ModalWrapper,
-} from '@console/internal/components/factory/modal';
import { LoadingInline } from '@console/internal/components/utils/status-box';
import { HorizontalPodAutoscalerModel } from '@console/internal/models';
import type { HorizontalPodAutoscalerKind, K8sResourceCommon } from '@console/internal/module/k8s';
import { k8sKill } from '@console/internal/module/k8s';
+import { usePromiseHandler } from '../../hooks/promise-handler';
+import { ModalFooterWithAlerts } from '../modals/ModalFooterWithAlerts';
+import { YellowExclamationTriangleIcon } from '../status';
type DeleteHPAModalProps = ModalComponentProps & {
hpa: HorizontalPodAutoscalerKind;
@@ -23,38 +18,30 @@ type DeleteHPAModalProps = ModalComponentProps & {
};
const DeleteHPAModal: FC = ({ close, cancel, hpa, workload }) => {
- const [submitError, setSubmitError] = useState(null);
- const [isSubmitting, setIsSubmitting] = useState(false);
+ const [handlePromise, inProgress, errorMessage] = usePromiseHandler();
const { t } = useTranslation();
const hpaName = hpa.metadata.name;
const workloadName = workload.metadata.name;
const handleSubmit = (e) => {
e.preventDefault();
- setIsSubmitting(true);
- k8sKill(HorizontalPodAutoscalerModel, hpa)
- .then(() => {
- close();
- })
- .catch((error) => {
- setSubmitError(
- error?.message ||
- t('console-shared~Unknown error removing {{hpaLabel}} {{hpaName}}.', {
- hpaLabel: HorizontalPodAutoscalerModel.label,
- hpaName,
- }),
- );
- });
+ return handlePromise(k8sKill(HorizontalPodAutoscalerModel, hpa)).then(() => {
+ close();
+ });
};
return (
-
-
-
- {' '}
- {t('console-shared~Remove {{label}}?', { label: HorizontalPodAutoscalerModel.label })}
-
-
+ <>
+
+ {' '}
+ {t('console-shared~Remove {{label}}?', { label: HorizontalPodAutoscalerModel.label })}
+ >
+ }
+ />
+
+
{hpaName ? (
<>
@@ -71,19 +58,26 @@ const DeleteHPAModal: FC = ({ close, cancel, hpa, workload
>
) : (
- !submitError &&
+ !errorMessage &&
)}
-
-
-
-
+
+
+
+
+ {t('console-shared~Remove')}
+
+
+ {t('console-shared~Cancel')}
+
+
+ >
);
};
@@ -92,17 +86,23 @@ type DeleteHPAModalOverlayProps = {
workload: K8sResourceCommon;
};
-const DeleteHPAModalOverlay: OverlayComponent = (props) => {
- return (
-
+export const DeleteHPAModalOverlay: OverlayComponent = (props) => {
+ const [isOpen, setIsOpen] = useState(true);
+ const handleClose = () => {
+ setIsOpen(false);
+ props.closeOverlay();
+ };
+
+ return isOpen ? (
+
-
- );
+
+ ) : null;
};
-export { DeleteHPAModalOverlay };
+export default DeleteHPAModalOverlay;
diff --git a/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx b/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
index a0891031238..958277bcff6 100644
--- a/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/ConsolePluginModal.tsx
@@ -1,12 +1,7 @@
import { useState } from 'react';
+import { Button, Form, Modal, ModalBody, ModalHeader, ModalVariant } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
-import {
- ModalTitle,
- ModalBody,
- ModalSubmitFooter,
- ModalWrapper,
-} from '@console/internal/components/factory/modal';
import { ConsoleOperatorConfigModel } from '@console/internal/models';
import type { K8sResourceKind } from '@console/internal/module/k8s';
import { k8sPatch } from '@console/internal/module/k8s';
@@ -16,6 +11,7 @@ import {
} from '@console/shared/src/components/utils';
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
import { getPluginPatch, isPluginEnabled } from '@console/shared/src/utils';
+import { ModalFooterWithAlerts } from './ModalFooterWithAlerts';
export const ConsolePluginModal = (props: ConsolePluginModalProps) => {
const { cancel, close, consoleOperatorConfig, csvPluginsCount, pluginName, trusted } = props;
@@ -33,42 +29,56 @@ export const ConsolePluginModal = (props: ConsolePluginModalProps) => {
};
return (
-
-
- {csvPluginsCount > 1
- ? t('console-shared~Console plugin enablement - {{plugin}}', { plugin: pluginName })
- : t('console-shared~Console plugin enablement')}
-
+ <>
+ 1
+ ? t('console-shared~Console plugin enablement - {{plugin}}', { plugin: pluginName })
+ : t('console-shared~Console plugin enablement')
+ }
+ />
-
- {csvPluginsCount
- ? t(
- 'console-shared~This operator includes a console plugin which provides a custom interface that can be included in the console. Updating the enablement of this console plugin will prompt for the console to be refreshed once it has been updated. Make sure you trust this console plugin before enabling.',
- )
- : t(
- 'console-shared~This console plugin provides a custom interface that can be included in the console. Updating the enablement of this console plugin will prompt for the console to be refreshed once it has been updated. Make sure you trust this console plugin before enabling.',
- )}
-
-
-
+
+
+ {csvPluginsCount
+ ? t(
+ 'console-shared~This operator includes a console plugin which provides a custom interface that can be included in the console. Updating the enablement of this console plugin will prompt for the console to be refreshed once it has been updated. Make sure you trust this console plugin before enabling.',
+ )
+ : t(
+ 'console-shared~This console plugin provides a custom interface that can be included in the console. Updating the enablement of this console plugin will prompt for the console to be refreshed once it has been updated. Make sure you trust this console plugin before enabling.',
+ )}
+
+
+
+
-
-
+
+
+ {t('public~Save')}
+
+
+ {t('public~Cancel')}
+
+
+ >
);
};
@@ -82,18 +92,24 @@ type ConsolePluginModalProviderProps = {
export const ConsolePluginModalOverlay: OverlayComponent = (
props,
) => {
- return (
-
+ const [isOpen, setIsOpen] = useState(true);
+ const handleClose = () => {
+ setIsOpen(false);
+ props.closeOverlay();
+ };
+
+ return isOpen ? (
+
-
- );
+
+ ) : null;
};
export default ConsolePluginModalOverlay;
diff --git a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
index 7ad13ccff9b..adb2d71f6f5 100644
--- a/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
+++ b/frontend/packages/console-shared/src/components/modals/DeleteResourceModal.tsx
@@ -1,21 +1,24 @@
+import { useState } from 'react';
import type { FC } from 'react';
-import { TextInputTypes } from '@patternfly/react-core';
+import {
+ Button,
+ Form,
+ Modal,
+ ModalBody,
+ ModalHeader,
+ ModalVariant,
+ TextInputTypes,
+} from '@patternfly/react-core';
import type { FormikProps, FormikValues } from 'formik';
import { Formik } from 'formik';
import { useTranslation, Trans } from 'react-i18next';
import { useNavigate } from 'react-router-dom-v5-compat';
import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
-import {
- ModalTitle,
- ModalBody,
- ModalSubmitFooter,
- ModalWrapper,
-} from '@console/internal/components/factory/modal';
import type { ModalComponentProps } from '@console/internal/components/factory/modal';
import type { K8sResourceKind } from '@console/internal/module/k8s';
import { usePromiseHandler } from '../../hooks/promise-handler';
import { InputField } from '../formik-fields';
-import { YellowExclamationTriangleIcon } from '../status';
+import { ModalFooterWithAlerts } from './ModalFooterWithAlerts';
const DeleteResourceForm: FC & DeleteResourceModalProps> = ({
handleSubmit,
@@ -32,34 +35,47 @@ const DeleteResourceForm: FC & DeleteResourceModalProp
const { t } = useTranslation();
const isValid = values.resourceName === resourceName;
const submitLabel = actionLabel || t(actionLabelKey);
+
+ const onSubmit = (e) => {
+ e.preventDefault();
+ handleSubmit(e);
+ };
+
return (
-
-
- {submitLabel} {resourceType}?
-
+ <>
+ {resourceType}?>} titleIconVariant="warning" />
-
- {t(
- `console-shared~This action cannot be undone. All associated Deployments, Routes, Builds, Pipelines, Storage/PVCs, Secrets, and ConfigMaps will be deleted.`,
- )}
-
-
-
- Confirm deletion by typing {{ resourceName }} {' '}
- below:
-
-
-
+
+
+ {t(
+ `console-shared~This action cannot be undone. All associated Deployments, Routes, Builds, Pipelines, Storage/PVCs, Secrets, and ConfigMaps will be deleted.`,
+ )}
+
+
+
+ Confirm deletion by typing{' '}
+ {{ resourceName }} below:
+
+
+
+
-
-
+
+
+ {submitLabel}
+
+
+ {t('console-shared~Cancel')}
+
+
+ >
);
};
@@ -97,11 +113,17 @@ const DeleteResourceModal: FC = (props) => {
};
export const DeleteResourceModalOverlay: OverlayComponent = (props) => {
- return (
-
+ const [isOpen, setIsOpen] = useState(true);
+ const handleClose = () => {
+ setIsOpen(false);
+ props.closeOverlay();
+ };
+
+ return isOpen ? (
+
-
- );
+
+ ) : null;
};
type DeleteResourceModalProps = ModalComponentProps & {
diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json
index 2884855fcee..0fa805e3627 100644
--- a/frontend/public/locales/en/public.json
+++ b/frontend/public/locales/en/public.json
@@ -1804,7 +1804,6 @@
"Disable": "Disable",
"Disabled": "Disabled",
"Enabled": "Enabled",
- "Select service account": "Select service account",
"Failed to load kubecontrollermanager": "Failed to load kubecontrollermanager",
"Failed to parse cloud provider config {{cm}}": "Failed to parse cloud provider config {{cm}}",
"The following content was expected to be defined in the configMap: {{ expectedValues }}": "The following content was expected to be defined in the configMap: {{ expectedValues }}",
@@ -1813,5 +1812,6 @@
"Failed to patch cloud provider config": "Failed to patch cloud provider config",
"Failed to add taint to nodes": "Failed to add taint to nodes",
"Failed to patch infrastructure spec": "Failed to patch infrastructure spec",
- "Unexpected error": "Unexpected error"
+ "Unexpected error": "Unexpected error",
+ "Select service account": "Select service account"
}
\ No newline at end of file