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, }) => ( -