From 4cb7bcb7ceffe340d0b31360f4e2bf20e7fc5a31 Mon Sep 17 00:00:00 2001 From: Matan Schatzman Date: Wed, 17 Jul 2024 13:29:27 +0300 Subject: [PATCH] CNV-37371: New setting for change bootable volumes default ns Signed-off-by: Matan Schatzman --- locales/en/plugin__kubevirt-plugin.json | 2 + .../FilterSelect/InlineFilterSelect.tsx | 1 - .../hooks/useHyperConvergeConfiguration.ts | 1 + .../vm/hooks/useProvisioningPercentage.tsx | 2 +- .../BootableVolumeProjectSection.tsx | 45 +++++++ .../utils/utils.ts | 36 ++++++ .../GeneralSettings/GeneralSettings.tsx | 18 ++- .../TemplatesProjectSection.tsx | 120 +++--------------- .../shared/GeneralSettingsError.tsx | 28 ++++ .../shared/GeneralSettingsProject.tsx | 78 ++++++++++++ .../shared/GeneralSettingsProjectSelector.tsx | 70 ++++++++++ .../general-settings.scss} | 2 +- 12 files changed, 295 insertions(+), 108 deletions(-) create mode 100644 src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/BootableVolumeProjectSection.tsx create mode 100644 src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/utils/utils.ts create mode 100644 src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsError.tsx create mode 100644 src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProject.tsx create mode 100644 src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProjectSelector.tsx rename src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/{TemplatesProjectSection/templates-project-section.scss => shared/general-settings.scss} (90%) diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index f150ec311..39abe28f5 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -203,6 +203,7 @@ "bootable": "bootable", "Bootable volume: {{bootableVolumeName}}": "Bootable volume: {{bootableVolumeName}}", "Bootable volumes": "Bootable volumes", + "Bootable volumes project": "Bootable volumes project", "Breakdown by network": "Breakdown by network", "Bridge": "Bridge", "Build with guided documentation": "Build with guided documentation", @@ -1035,6 +1036,7 @@ "See the <2>catalog tab to quickly create a VirtualMachine from the available Templates.": "See the <2>catalog tab to quickly create a VirtualMachine from the available Templates.", "Select": "Select", "Select {{label}}": "Select {{label}}", + "Select a project for Red Hat bootable volumes. The default project is 'openshift-virtualization-os-images'": "Select a project for Red Hat bootable volumes. The default project is 'openshift-virtualization-os-images'", "Select a project for Red Hat templates. The default project is 'openshift'. If you want to store Red Hat templates in multiple projects, you must clone
the Red Hat template by selecting <3>Clone template from the template action menu and then selecting another project for the cloned template.": "Select a project for Red Hat templates. The default project is 'openshift'. If you want to store Red Hat templates in multiple projects, you must clone
the Red Hat template by selecting <3>Clone template from the template action menu and then selecting another project for the cloned template.", "Select a project in order to see user-provided InstanceTypes": "Select a project in order to see user-provided InstanceTypes", "Select a resource": "Select a resource", diff --git a/src/utils/components/FilterSelect/InlineFilterSelect.tsx b/src/utils/components/FilterSelect/InlineFilterSelect.tsx index 698feda24..fa02baf33 100644 --- a/src/utils/components/FilterSelect/InlineFilterSelect.tsx +++ b/src/utils/components/FilterSelect/InlineFilterSelect.tsx @@ -49,7 +49,6 @@ const InlineFilterSelect: FC = ({ const onSelect = (_event: React.MouseEvent | undefined, value: string) => { if (value && value !== NO_RESULTS) { - setSelected(value); setFilterValue(''); } setIsOpen(false); diff --git a/src/utils/hooks/useHyperConvergeConfiguration.ts b/src/utils/hooks/useHyperConvergeConfiguration.ts index e0166e8d8..eb74384b3 100644 --- a/src/utils/hooks/useHyperConvergeConfiguration.ts +++ b/src/utils/hooks/useHyperConvergeConfiguration.ts @@ -8,6 +8,7 @@ import { K8sResourceCommon, useK8sWatchResource } from '@openshift-console/dynam export type HyperConverged = K8sResourceCommon & { spec: { + commonBootImageNamespace?: string; commonTemplatesNamespace?: string; dataImportCronTemplates: K8sResourceCommon[]; evictionStrategy?: string; diff --git a/src/utils/resources/vm/hooks/useProvisioningPercentage.tsx b/src/utils/resources/vm/hooks/useProvisioningPercentage.tsx index d6312529f..ba62ab91e 100644 --- a/src/utils/resources/vm/hooks/useProvisioningPercentage.tsx +++ b/src/utils/resources/vm/hooks/useProvisioningPercentage.tsx @@ -1,5 +1,5 @@ import { DataVolumeModelGroupVersionKind } from '@kubevirt-ui/kubevirt-api/console'; -import type { V1beta1DataVolume } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models'; +import { V1beta1DataVolume } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models'; import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { getNamespace, getVMStatus } from '@kubevirt-utils/resources/shared'; import { getVolumes } from '@kubevirt-utils/resources/vm'; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/BootableVolumeProjectSection.tsx b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/BootableVolumeProjectSection.tsx new file mode 100644 index 000000000..f3576d3d3 --- /dev/null +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/BootableVolumeProjectSection.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react'; + +import { OPENSHIFT_OS_IMAGES_NS } from '@kubevirt-utils/constants/constants'; +import { HyperConverged } from '@kubevirt-utils/hooks/useHyperConvergeConfiguration'; +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk'; + +import GeneralSettingsProject from '../shared/GeneralSettingsProject'; + +import { + getCurrentBootableVolumesNamespaceFromHCO, + updateHCOBootableVolumesNamespace, +} from './utils/utils'; + +import '../shared/general-settings.scss'; + +type BootableVolumeProjectSectionProps = { + hyperConvergeConfiguration: [hyperConvergeConfig: HyperConverged, loaded: boolean, error: any]; + projectsData: [projects: K8sResourceCommon[], loaded: boolean, error: any]; +}; + +const BootableVolumeProjectSection: FC = ({ + hyperConvergeConfiguration, + projectsData, +}) => { + const { t } = useKubevirtTranslation(); + + return ( + + ); +}; + +export default BootableVolumeProjectSection; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/utils/utils.ts b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/utils/utils.ts new file mode 100644 index 000000000..072e51e9b --- /dev/null +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/BootableVolumeProjectSection/utils/utils.ts @@ -0,0 +1,36 @@ +import HyperConvergedModel from '@kubevirt-ui/kubevirt-api/console/models/HyperConvergedModel'; +import { OPENSHIFT_OS_IMAGES_NS } from '@kubevirt-utils/constants/constants'; +import { HyperConverged } from '@kubevirt-utils/hooks/useHyperConvergeConfiguration'; +import { k8sPatch } from '@openshift-console/dynamic-plugin-sdk'; + +export const getCurrentBootableVolumesNamespaceFromHCO = (hyperConverged: HyperConverged): string => + hyperConverged?.spec?.commonBootImageNamespace || OPENSHIFT_OS_IMAGES_NS; + +export const updateHCOBootableVolumesNamespace = async ( + hyperConverged: HyperConverged, + newNamespace: null | number | string, + handelError: (value: string) => void, + handleLoading: (value: boolean) => void, +) => { + const currentTemplatesNamespace = getCurrentBootableVolumesNamespaceFromHCO(hyperConverged); + if (newNamespace !== currentTemplatesNamespace) { + handleLoading(true); + try { + await k8sPatch({ + data: [ + { + op: 'replace', + path: `/spec/commonBootImageNamespace`, + value: newNamespace === OPENSHIFT_OS_IMAGES_NS ? null : newNamespace, + }, + ], + model: HyperConvergedModel, + resource: hyperConverged, + }); + } catch (error) { + handelError(error?.message || error); + } finally { + handleLoading(false); + } + } +}; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/GeneralSettings.tsx b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/GeneralSettings.tsx index 8d5e179cc..a5bd5dba7 100644 --- a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/GeneralSettings.tsx +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/GeneralSettings.tsx @@ -2,11 +2,14 @@ import React, { FC } from 'react'; import { HyperConverged } from '@kubevirt-utils/hooks/useHyperConvergeConfiguration'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { modelToGroupVersionKind, ProjectModel } from '@kubevirt-utils/models'; +import { K8sResourceCommon, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { Stack, StackItem } from '@patternfly/react-core'; import ExpandSection from '../../../ExpandSection/ExpandSection'; import AutomaticImagesDownload from './AutomaticImagesDownload/AutomaticImagesDownload'; +import BootableVolumeProjectSection from './BootableVolumeProjectSection/BootableVolumeProjectSection'; import LiveMigrationSection from './LiveMigrationSection/LiveMigrationSection'; import MemoryDensity from './MemoryDensity/MemoryDensity'; import SSHConfiguration from './SSHConfiguration/SSHConfiguration'; @@ -19,6 +22,10 @@ type GeneralSettingsProps = { const GeneralSettings: FC = ({ hyperConvergeConfiguration, newBadge }) => { const { t } = useKubevirtTranslation(); + const projectsData = useK8sWatchResource({ + groupVersionKind: modelToGroupVersionKind(ProjectModel), + isList: true, + }); return ( @@ -30,7 +37,16 @@ const GeneralSettings: FC = ({ hyperConvergeConfiguration, - + + + + = ({ hyperConvergeConfiguration, + projectsData, }) => { const { t } = useKubevirtTranslation(); - const [loading, setLoading] = useState(false); - const [selectedProject, setSelectedProject] = useState(); - const [error, setError] = useState(); - const { createModal } = useModal(); - - const [projects, projectsLoaded, projectsLoadingError] = useK8sWatchResource( - { - groupVersionKind: modelToGroupVersionKind(ProjectModel), - isList: true, - }, - ); - - const [hyperConverge, hyperLoaded] = hyperConvergeConfiguration; - - useEffect(() => { - if (hyperConverge) { - const currentNamespaceHCO = getCurrentTemplatesNamespaceFromHCO(hyperConverge); - !selectedProject && setSelectedProject(currentNamespaceHCO ?? OPENSHIFT); - } - }, [hyperConverge, selectedProject]); - - const onSelect = (value: string) => { - setSelectedProject(value); - updateHCOCommonTemplatesNamespace(hyperConverge, value, setError, setLoading).catch((e) => - kubevirtConsole.log(e), - ); - }; return ( - - + Select a project for Red Hat templates. The default project is 'openshift'. If you want to store Red Hat templates in multiple projects, you must clone @@ -78,60 +36,14 @@ const TemplatesProjectSection: FC = ({ the Red Hat template by selecting Clone template from the template action menu and then selecting another project for the cloned template. - - - {t('Project')} - - {projectsLoaded && hyperLoaded ? ( - - - - } - options={[ - ...projects?.map((proj) => ({ - groupVersionKind: modelToGroupVersionKind(ProjectModel), - value: getName(proj), - })), - ]} - toggleProps={{ - icon: loading && , - isDisabled: loading, - placeholder: t('Select project'), - }} - selected={selectedProject} - setSelected={onSelect} - /> - ) : ( - - )} - {(error || projectsLoadingError) && ( - - {error || projectsLoadingError} - - )} - + } + hcoResourceNamespace={getCurrentTemplatesNamespaceFromHCO(hyperConvergeConfiguration?.[0])} + hyperConvergeConfiguration={hyperConvergeConfiguration} + namespace={OPENSHIFT} + onChange={updateHCOCommonTemplatesNamespace} + projectsData={projectsData} + toggleText={t('Template project')} + /> ); }; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsError.tsx b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsError.tsx new file mode 100644 index 000000000..a9aafd042 --- /dev/null +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsError.tsx @@ -0,0 +1,28 @@ +import React, { FC } from 'react'; + +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { Alert, AlertVariant } from '@patternfly/react-core'; + +type GeneralSettingsErrorProps = { + error: string; + loading: string; +}; + +const GeneralSettingsError: FC = ({ error, loading }) => { + const { t } = useKubevirtTranslation(); + + return ( + (error || loading) && ( + + {error || loading} + + ) + ); +}; + +export default GeneralSettingsError; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProject.tsx b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProject.tsx new file mode 100644 index 000000000..a40a66ed7 --- /dev/null +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProject.tsx @@ -0,0 +1,78 @@ +import React, { FC, ReactNode, useEffect, useState } from 'react'; + +import { HyperConverged } from '@kubevirt-utils/hooks/useHyperConvergeConfiguration'; +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk'; +import { Text, TextVariants } from '@patternfly/react-core'; + +import ExpandSection from '../../../../ExpandSection/ExpandSection'; +import GeneralSettingsError from '../shared/GeneralSettingsError'; +import GeneralSettingsProjectSelector from '../shared/GeneralSettingsProjectSelector'; + +import '../shared/general-settings.scss'; + +type GeneralSettingsProjectProps = { + description: ReactNode; + hcoResourceNamespace: string; + hyperConvergeConfiguration: [hyperConvergeConfig: HyperConverged, loaded: boolean, error: any]; + namespace: string; + onChange: ( + hyperConverged: HyperConverged, + newNamespace: null | number | string, + handelError: (value: string) => void, + handleLoading: (value: boolean) => void, + ) => void; + projectsData: [projects: K8sResourceCommon[], loaded: boolean, error: any]; + toggleText: string; +}; + +const GeneralSettingsProject: FC = ({ + description, + hcoResourceNamespace, + hyperConvergeConfiguration, + namespace, + onChange, + projectsData, + toggleText, +}) => { + const { t } = useKubevirtTranslation(); + const [loading, setLoading] = useState(false); + const [selectedProject, setSelectedProject] = useState(); + const [error, setError] = useState(); + + const [hyperConverge, hyperLoaded] = hyperConvergeConfiguration; + const [projects, projectsLoaded, projectsLoadingError] = projectsData; + + useEffect(() => { + if (hyperConverge) { + !selectedProject && setSelectedProject(hcoResourceNamespace ?? namespace); + } + }, [hcoResourceNamespace, hyperConverge, namespace, selectedProject]); + + const onSelect = (value: string) => { + setError(null); + setSelectedProject(value); + onChange(hyperConverge, value, setError, setLoading); + }; + + return ( + + + {description} + + + {t('Project')} + + + + + ); +}; + +export default GeneralSettingsProject; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProjectSelector.tsx b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProjectSelector.tsx new file mode 100644 index 000000000..538246fc3 --- /dev/null +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/GeneralSettingsProjectSelector.tsx @@ -0,0 +1,70 @@ +import React, { FC } from 'react'; + +import CreateProjectModal from '@kubevirt-utils/components/CreateProjectModal/CreateProjectModal'; +import InlineFilterSelect from '@kubevirt-utils/components/FilterSelect/InlineFilterSelect'; +import { useModal } from '@kubevirt-utils/components/ModalProvider/ModalProvider'; +import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { modelToGroupVersionKind, ProjectModel } from '@kubevirt-utils/models'; +import { getName } from '@kubevirt-utils/resources/shared'; +import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk'; +import { Button, ButtonVariant, MenuFooter, Spinner } from '@patternfly/react-core'; + +type GeneralSettingsProjectSelectorProps = { + loaded: boolean; + onSelect: (value: string) => void; + projects: K8sResourceCommon[]; + selectedProject: string; + setSelectedProject: (value: string) => void; +}; +const GeneralSettingsProjectSelector: FC = ({ + loaded, + onSelect, + projects, + selectedProject, + setSelectedProject, +}) => { + const { t } = useKubevirtTranslation(); + const { createModal } = useModal(); + + return ( + + + + } + options={[ + ...projects + ?.map((proj) => ({ + groupVersionKind: modelToGroupVersionKind(ProjectModel), + value: getName(proj), + })) + .sort((a, b) => a.value.localeCompare(b.value)), + ]} + toggleProps={{ + icon: !loaded && , + isDisabled: !loaded, + placeholder: t('Select project'), + }} + selected={selectedProject} + setSelected={onSelect} + /> + ); +}; + +export default GeneralSettingsProjectSelector; diff --git a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/TemplatesProjectSection/templates-project-section.scss b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/general-settings.scss similarity index 90% rename from src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/TemplatesProjectSection/templates-project-section.scss rename to src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/general-settings.scss index 8dd7fa11a..b22e4ce7c 100644 --- a/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/TemplatesProjectSection/templates-project-section.scss +++ b/src/views/clusteroverview/SettingsTab/ClusterTab/components/GeneralSettings/shared/general-settings.scss @@ -1,4 +1,4 @@ -.templates-project-tab { +.project-tab { &__main { &--help { color: var(--pf-global--Color--200);