diff --git a/frontend/packages/console-shared/src/selectors/common.ts b/frontend/packages/console-shared/src/selectors/common.ts
index c4adb16429d..76bfb777540 100644
--- a/frontend/packages/console-shared/src/selectors/common.ts
+++ b/frontend/packages/console-shared/src/selectors/common.ts
@@ -23,6 +23,11 @@ export const getLabels = (
value: A,
defaultValue?: K8sResourceCommon['metadata']['labels'],
) => (_.has(value, 'metadata.labels') ? value.metadata.labels : defaultValue);
+export const getLabel = (
+ value: A,
+ label: string,
+ defaultValue?: string,
+) => (_.has(value, 'metadata.labels') ? value.metadata.labels[label] : defaultValue);
export const getAnnotations = (
value: A,
defaultValue?: K8sResourceCommon['metadata']['annotations'],
diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/consts.ts b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/consts.ts
new file mode 100644
index 00000000000..44a4b47b5d4
--- /dev/null
+++ b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/consts.ts
@@ -0,0 +1,4 @@
+export const DEDICATED_RESOURCES = 'Dedicated Resources';
+export const RESOURCE_PINNED = 'Workload scheduled with dedicated resources (guaranteed policy)';
+export const RESOURCE_NOT_PINNED = 'No Dedicated resources applied';
+export const RESOURCE_NO_NODES_AVAILABLE = 'There are no qualified Nodes with the required labels';
diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.scss b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.scss
new file mode 100644
index 00000000000..9fd253a7892
--- /dev/null
+++ b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.scss
@@ -0,0 +1,8 @@
+.kubevirt-cpu-pinning__checkbox {
+ margin-top: var(--pf-global--spacer--md);
+ margin-bottom: var(--pf-global--spacer--md);
+}
+
+.kubevirt-cpu-pinning__helper-text {
+ color: var(--pf-global--Color--200);
+}
diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx
new file mode 100644
index 00000000000..e52516b939d
--- /dev/null
+++ b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx
@@ -0,0 +1,116 @@
+import * as React from 'react';
+import { Checkbox, Modal, Text, TextVariants } from '@patternfly/react-core';
+import { k8sPatch } from '@console/internal/module/k8s';
+import { NodeModel } from '@console/internal/models';
+import { getLabel } from '@console/shared';
+import {
+ Firehose,
+ withHandlePromise,
+ HandlePromiseProps,
+ FirehoseResult,
+ Label,
+} from '@console/internal/components/utils';
+import { ModalFooter } from '../modal/modal-footer';
+import { getVMLikeModel, isDedicatedCPUPlacement, asVM } from '../../../selectors/vm';
+import { getDedicatedCpuPatch } from '../../../k8s/patches/vm/vm-cpu-patches';
+import { VMLikeEntityKind } from '../../../types';
+import { getLoadedData, isLoaded, getLoadError } from '../../../utils';
+import { RESOURCE_NO_NODES_AVAILABLE, DEDICATED_RESOURCES } from './consts';
+import './dedicated-resources-modal.scss';
+
+const ResourceModal = withHandlePromise(
+ ({ vmLikeEntity, nodes, isOpen, setOpen, handlePromise, inProgress, errorMessage }) => {
+ const isLoading = !isLoaded(nodes);
+ const loadError = getLoadError(nodes, NodeModel);
+ const isCPUPinned = isDedicatedCPUPlacement(asVM(vmLikeEntity));
+ const loadedNodes = getLoadedData(nodes, []);
+
+ const [isPinned, setIsPinned] = React.useState(isCPUPinned);
+ const [showPatchError, setPatchError] = React.useState(false);
+ const isNodeAvailable = loadedNodes.some((node) => getLabel(node, 'cpumanager') === 'true');
+
+ const submit = async () => {
+ if (isPinned !== isCPUPinned) {
+ handlePromise(
+ k8sPatch(
+ getVMLikeModel(vmLikeEntity),
+ vmLikeEntity,
+ await getDedicatedCpuPatch(vmLikeEntity, isPinned),
+ ),
+ )
+ .then(() => setOpen(false))
+ .catch(() => setPatchError(true));
+ } else {
+ setOpen(false);
+ }
+ };
+ const footer = (
+ setOpen(false)}
+ submitButtonText="Save"
+ />
+ );
+
+ return (
+ setOpen(false)}
+ footer={footer}
+ isFooterLeftAligned
+ >
+
+
+ Available only on Nodes with labels{' '}
+
+
+
+ );
+ },
+);
+
+type ResourceModalProps = HandlePromiseProps & {
+ vmLikeEntity: VMLikeEntityKind;
+ isOpen: boolean;
+ nodes?: FirehoseResult;
+ setOpen: (isOpen: boolean) => void;
+};
+
+export const DedicatedResourcesModal: React.FC = (props) => {
+ const { vmLikeEntity, ...restProps } = props;
+
+ const resources = [
+ {
+ kind: NodeModel.kind,
+ isList: true,
+ namespaced: false,
+ prop: 'nodes',
+ },
+ ];
+
+ return (
+
+
+
+ );
+};
+
+type DedicatedResourcesModalProps = {
+ vmLikeEntity: VMLikeEntityKind;
+ isOpen: boolean;
+ setOpen: (isOpen: boolean) => void;
+};
diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.scss b/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.scss
index 3e49a8c9cd9..4e57f419dd4 100644
--- a/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.scss
+++ b/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.scss
@@ -1,3 +1,4 @@
.kubevirt-create-nic-modal__buttons {
+ padding: 0;
text-align: left;
}
diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.tsx
index 6b9a1916882..16b887228fa 100644
--- a/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.tsx
+++ b/frontend/packages/kubevirt-plugin/src/components/modals/modal/modal-footer.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
-import { Alert, Button, ButtonVariant } from '@patternfly/react-core';
+import classNames from 'classnames';
+import { Alert, Button, ButtonVariant, AlertProps } from '@patternfly/react-core';
import { LoadingInline } from '@console/internal/components/utils';
import { prefixedID } from '../../../utils';
@@ -20,13 +21,15 @@ export const ModalErrorMessage: React.FC = ({ message })
);
-type ModalSimpleErrorMessageProps = {
+type ModalSimpleMessageProps = {
message: string;
+ variant?: AlertProps['variant'];
};
-export const ModalSimpleErrorMessage: React.FC = ({ message }) => (
-
-);
+export const ModalSimpleMessage: React.FC = ({
+ message,
+ variant = 'danger',
+}) => ;
type ModalInfoMessageProps = {
title: string;
@@ -41,7 +44,9 @@ export const ModalInfoMessage: React.FC = ({ title, child
type ModalFooterProps = {
id?: string;
+ className?: string;
errorMessage?: string;
+ warningMessage?: string;
isSimpleError?: boolean;
onSubmit: (e) => void;
onCancel: (e) => void;
@@ -55,7 +60,9 @@ type ModalFooterProps = {
export const ModalFooter: React.FC = ({
id,
+ className = '',
errorMessage = null,
+ warningMessage = null,
isDisabled = false,
inProgress = false,
isSimpleError = false,
@@ -66,8 +73,16 @@ export const ModalFooter: React.FC = ({
infoMessage = null,
infoTitle = null,
}) => (
-