diff --git a/frontend/packages/ceph-storage-plugin/locales/en/ceph-storage-plugin.json b/frontend/packages/ceph-storage-plugin/locales/en/ceph-storage-plugin.json
index d6d31b73fe0..6ba0d95d71f 100644
--- a/frontend/packages/ceph-storage-plugin/locales/en/ceph-storage-plugin.json
+++ b/frontend/packages/ceph-storage-plugin/locales/en/ceph-storage-plugin.json
@@ -176,6 +176,45 @@
"Zone": "Zone",
"Selected nodes table": "Selected nodes table",
"Connection details": "Connection details",
+ "LocalVolumeSet Name": "LocalVolumeSet Name",
+ "StorageClass Name": "StorageClass Name",
+ "Filter Disks By": "Filter Disks By",
+ "Disks on all nodes": "Disks on all nodes",
+ "{{nodes, number}} node": "{{nodes, number}} node",
+ "{{nodes, number}} node_plural": "{{nodes, number}} nodes",
+ "Disks on selected nodes": "Disks on selected nodes",
+ "Uses the available disks that match the selected filters only on selected nodes.": "Uses the available disks that match the selected filters only on selected nodes.",
+ "Disk Type": "Disk Type",
+ "Volume Mode": "Volume Mode",
+ "Disk Size": "Disk Size",
+ "Min": "Min",
+ "Max": "Max",
+ "Maximum Disks Limit": "Maximum Disks Limit",
+ "Disks limit will set the maximum number of PVs to create on a node. If the field is empty we will create PVs for all available disks on the matching nodes.": "Disks limit will set the maximum number of PVs to create on a node. If the field is empty we will create PVs for all available disks on the matching nodes.",
+ "All": "All",
+ "Uses the available disks that match the selected filters on all nodes selected in the previous step.": "Uses the available disks that match the selected filters on all nodes selected in the previous step.",
+ "A LocalVolumeSet allows you to filter a set of disks, group them and create a dedicated StorageClass to consume storage from them.": "A LocalVolumeSet allows you to filter a set of disks, group them and create a dedicated StorageClass to consume storage from them.",
+ "Local Storage Operator not installed": "Local Storage Operator not installed",
+ "Before we can create a StorageCluster, the Local Storage operator needs to be installed. When installation is finished come back to OpenShift Container Storage to create a StorageCluster.<1><0>Install0>1>": "Before we can create a StorageCluster, the Local Storage operator needs to be installed. When installation is finished come back to OpenShift Container Storage to create a StorageCluster.<1><0>Install0>1>",
+ "Minimum Node Requirement": "Minimum Node Requirement",
+ "A minimum of 3 nodes are required for the initial deployment. Only {{nodes}} node match to the selected filters. Please adjust the filters to include more nodes.": "A minimum of 3 nodes are required for the initial deployment. Only {{nodes}} node match to the selected filters. Please adjust the filters to include more nodes.",
+ "After the LocalVolumeSet and StorageClass are created you won't be able to go back to this step.": "After the LocalVolumeSet and StorageClass are created you won't be able to go back to this step.",
+ "Note:": "Note:",
+ "Create StorageClass": "Create StorageClass",
+ "Yes": "Yes",
+ "Are you sure you want to continue?": "Are you sure you want to continue?",
+ "Node": "Node",
+ "Model": "Model",
+ "Capacity": "Capacity",
+ "Selected Disks": "Selected Disks",
+ "Disk List": "Disk List",
+ "Selected Capacity": "Selected Capacity",
+ "{{nodes, number}} Node": "{{nodes, number}} Node",
+ "{{nodes, number}} Node_plural": "{{nodes, number}} Nodes",
+ "{{disks, number}} Disk": "{{disks, number}} Disk",
+ "{{disks, number}} Disk_plural": "{{disks, number}} Disks",
+ "Selected versus Available Capacity": "Selected versus Available Capacity",
+ "Out of {{capacity}}": "Out of {{capacity}}",
"StorageClass name": "StorageClass name",
"Backing storage": "Backing storage",
"StorageClass:": "StorageClass:",
@@ -192,6 +231,7 @@
"An error has occurred": "An error has occurred",
"Create StorageSystem": "Create StorageSystem",
"StorageSystem is an entity of OpenShift Data Foundation. It represents all of the required storage and compute resources.": "StorageSystem is an entity of OpenShift Data Foundation. It represents all of the required storage and compute resources.",
+ "Not found": "Not found",
"Details": "Details",
"Replicas": "Replicas",
"Inventory": "Inventory",
@@ -249,7 +289,6 @@
"Projects": "Projects",
"BucketClasses": "BucketClasses",
"Service type": "Service type",
- "All": "All",
"Cluster-wide": "Cluster-wide",
"Any NON Object bucket claims that were created via an S3 client or via the NooBaa UI system.": "Any NON Object bucket claims that were created via an S3 client or via the NooBaa UI system.",
"Capacity breakdown": "Capacity breakdown",
@@ -333,8 +372,6 @@
"Recovery": "Recovery",
"Disk State": "Disk State",
"OpenShift Data Foundation status": "OpenShift Data Foundation status",
- "Model": "Model",
- "Capacity": "Capacity",
"Filesystem": "Filesystem",
"Disks List": "Disks List",
"Start Disk Replacement": "Start Disk Replacement",
@@ -459,25 +496,7 @@
"Object Bucket Details": "Object Bucket Details",
"Object Bucket Claim": "Object Bucket Claim",
"OBTableHeader": "OBTableHeader",
- "Uses the available disks that match the selected filters on all nodes selected in the previous step.": "Uses the available disks that match the selected filters on all nodes selected in the previous step.",
- "A Local Volume Set allows you to filter a set of disks, group them and create a dedicated StorageClass to consume storage from them.": "A Local Volume Set allows you to filter a set of disks, group them and create a dedicated StorageClass to consume storage from them.",
- "Minimum Node Requirement": "Minimum Node Requirement",
"OpenShift Container Storage's StorageCluster requires a minimum of 3 nodes for the initial deployment. Only {{nodes}} node match to the selected filters. Please adjust the filters to include more nodes.": "OpenShift Container Storage's StorageCluster requires a minimum of 3 nodes for the initial deployment. Only {{nodes}} node match to the selected filters. Please adjust the filters to include more nodes.",
- "After the LocalVolumeSet and StorageClass are created you won't be able to go back to this step.": "After the LocalVolumeSet and StorageClass are created you won't be able to go back to this step.",
- "Note:": "Note:",
- "Create StorageClass": "Create StorageClass",
- "Yes": "Yes",
- "Are you sure you want to continue?": "Are you sure you want to continue?",
- "Node": "Node",
- "Selected Disks": "Selected Disks",
- "Disk List": "Disk List",
- "Selected Capacity": "Selected Capacity",
- "{{nodes, number}} Node": "{{nodes, number}} Node",
- "{{nodes, number}} Node_plural": "{{nodes, number}} Nodes",
- "{{disks, number}} Disk": "{{disks, number}} Disk",
- "{{disks, number}} Disk_plural": "{{disks, number}} Disks",
- "Selected versus Available Capacity": "Selected versus Available Capacity",
- "Out of {{capacity}}": "Out of {{capacity}}",
"Review StorageCluster": "Review StorageCluster",
"Storage and nodes": "Storage and nodes",
"Arbiter zone:": "Arbiter zone:",
@@ -493,8 +512,6 @@
"Review and create": "Review and create",
"Internal - Attached devices": "Internal - Attached devices",
"Can be used on any platform where there are attached devices to the nodes, using the Local Storage Operator. The infrastructure StorageClass is provided by Local Storage Operator, on top of the attached drives.": "Can be used on any platform where there are attached devices to the nodes, using the Local Storage Operator. The infrastructure StorageClass is provided by Local Storage Operator, on top of the attached drives.",
- "Local Storage Operator not installed": "Local Storage Operator not installed",
- "Before we can create a StorageCluster, the Local Storage operator needs to be installed. When installation is finished come back to OpenShift Container Storage to create a StorageCluster.<1><0>Install0>1>": "Before we can create a StorageCluster, the Local Storage operator needs to be installed. When installation is finished come back to OpenShift Container Storage to create a StorageCluster.<1><0>Install0>1>",
"Node Table": "Node Table",
"StorageCluster exists": "StorageCluster exists",
"Back to operator page": "Back to operator page",
diff --git a/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-steps.tsx b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-steps.tsx
index 68b2725928c..34a1a0fc60a 100644
--- a/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-steps.tsx
+++ b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-steps.tsx
@@ -6,6 +6,7 @@ import {
CreateStorageClass,
ConnectionDetails,
ReviewAndCreate,
+ CreateLocalVolumeSet,
} from './create-storage-system-steps';
import { WizardDispatch, WizardState } from './reducer';
import {
@@ -122,6 +123,13 @@ export const createSteps = (
name: StepsName(t)[Steps.CreateLocalVolumeSet],
canJumpTo: stepIdReached >= 2,
id: 2,
+ component: (
+
+ ),
},
{
canJumpTo: stepIdReached >= 3,
diff --git a/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx
index 51914df404d..4c67d282ae8 100644
--- a/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx
+++ b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx
@@ -18,6 +18,7 @@ import {
} from '../../../../constants/create-storage-system';
import { ErrorHandler } from '../../error-handler';
import { ExternalStorage } from '../../external-storage/types';
+import { NO_PROVISIONER } from '../../../../constants';
const ExternalSystemSelection: React.FC = ({
dispatch,
@@ -114,6 +115,8 @@ export const BackingStorage: React.FC = ({
},
);
+ const { type, externalStorage, deployment, isAdvancedOpen } = state;
+
React.useEffect(() => {
/*
Allow pre selecting the "external connection" option instead of the "existing" option
@@ -124,7 +127,17 @@ export const BackingStorage: React.FC = ({
}
}, [dispatch, allowedExternalStorage.length, hasOCS]);
- const { type, externalStorage, deployment, isAdvancedOpen } = state;
+ React.useEffect(() => {
+ /*
+ Update storage class state when no storage class is used.
+ */
+ if (type === BackingStorageType.LOCAL_DEVICES) {
+ dispatch({
+ type: 'wizard/setStorageClass',
+ payload: { name: '', provisioner: NO_PROVISIONER },
+ });
+ }
+ }, [dispatch, type]);
const showExternalStorageSelection =
type === BackingStorageType.EXTERNAL && allowedExternalStorage.length;
diff --git a/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/create-local-volume-set/body.scss b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/create-local-volume-set/body.scss
new file mode 100644
index 00000000000..bf3b15ee374
--- /dev/null
+++ b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/create-local-volume-set/body.scss
@@ -0,0 +1,51 @@
+.odf-create-lvs__all-nodes-radio--padding {
+ padding-bottom: var(--pf-global--spacer--sm);
+ }
+
+.odf-create-lvs__filter-volumes-text--margin {
+ margin: 0;
+}
+
+.odf-create-lvs__max-disk-limit-help-text--margin {
+ margin-top: 0;
+}
+
+.odf-create-lvs__node-selection-table--margin {
+ margin-top: 0;
+}
+
+.odf-create-lvs__disk-mode-dropdown--margin {
+ margin-bottom: var(--pf-global--spacer--md);
+}
+
+.odf-create-lvs__device-type-dropdown--margin {
+ margin-bottom: var(--pf-global--spacer--md);
+}
+
+.odf-create-lvs__disk-size-form-group--margin {
+ margin: var(--pf-global--spacer--lg) 0;
+}
+
+.odf-create-lvs__disk-size-form-group-div {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ width: 22em;
+}
+
+.odf-create-lvs__disk-size-form-group-max-min-input {
+ display: flex;
+ flex-direction: column;
+}
+
+.odf-create-lvs__disk-input {
+ max-width: 100px;
+}
+
+.odf-create-lvs__select-nodes {
+// overrides extra space added by `co-m-nav-title`
+ .co-m-nav-title {
+ padding: 0;
+ margin: 0;
+ }
+}
diff --git a/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/create-local-volume-set/body.tsx b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/create-local-volume-set/body.tsx
new file mode 100644
index 00000000000..8bf239d2f98
--- /dev/null
+++ b/frontend/packages/ceph-storage-plugin/src/components/create-storage-system/create-storage-system-steps/create-local-volume-set/body.tsx
@@ -0,0 +1,321 @@
+import * as React from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ FormGroup,
+ TextInput,
+ Radio,
+ ExpandableSection,
+ TextInputTypes,
+ Text,
+ TextVariants,
+ Tooltip,
+} from '@patternfly/react-core';
+import { ListPage } from '@console/internal/components/factory';
+import { Dropdown } from '@console/internal/components/utils';
+import { NodeModel } from '@console/internal/models';
+import { NodeKind } from '@console/internal/module/k8s';
+import { getName, MultiSelectDropdown } from '@console/shared';
+import {
+ deviceTypeDropdownItems,
+ diskSizeUnitOptions,
+ diskTypeDropdownItems,
+} from '@console/local-storage-operator-plugin/src/constants';
+import { NodesTable } from '@console/local-storage-operator-plugin/src/components/tables/nodes-table';
+import { diskModeDropdownItems, NO_PROVISIONER } from '../../../../constants';
+import { LocalVolumeSet, WizardDispatch, WizardState } from '../../reducer';
+
+import './body.scss';
+
+export const LocalVolumeSetBody: React.FC = ({
+ dispatch,
+ state,
+ storageClassName,
+ taintsFilter,
+ diskModeOptions = diskModeDropdownItems,
+ allNodesHelpTxt,
+ lvsNameHelpTxt,
+ deviceTypeOptions = deviceTypeDropdownItems,
+}) => {
+ const { t } = useTranslation();
+ const formHandler = React.useCallback(
+ (field: keyof LocalVolumeSet, value: LocalVolumeSet[keyof LocalVolumeSet]) =>
+ dispatch({ type: 'wizard/setCreateLocalVolumeSet', payload: { field, value } }),
+ [dispatch],
+ );
+
+ const diskDropdownOptions = diskTypeDropdownItems(t);
+
+ const INTEGER_MAX_REGEX = /^\+?([1-9]\d*)$/;
+ const INTEGER_MIN_REGEX = /^\+?([0-9]\d*)$/;
+ const [activeMinDiskSize, setMinActiveState] = React.useState(false);
+ const [activeMaxDiskSize, setMaxActiveState] = React.useState(false);
+ const validMinDiskSize = INTEGER_MIN_REGEX.test(state.minDiskSize || '1');
+ const validMaxDiskSize = INTEGER_MAX_REGEX.test(state.maxDiskSize || '1');
+ const validMaxDiskLimit = INTEGER_MAX_REGEX.test(state.maxDiskLimit || '1');
+ const invalidMinGreaterThanMax =
+ state.minDiskSize !== '' &&
+ state.maxDiskSize !== '' &&
+ Number(state.minDiskSize) > Number(state.maxDiskSize);
+
+ const toggleShowNodesList = () => formHandler('lvsIsSelectNodes', !state.lvsIsSelectNodes);
+
+ React.useEffect(() => {
+ if (!validMinDiskSize || !validMaxDiskSize || !validMaxDiskLimit || invalidMinGreaterThanMax) {
+ formHandler('isValidDiskSize', false);
+ } else {
+ formHandler('isValidDiskSize', true);
+ }
+ }, [
+ dispatch,
+ validMinDiskSize,
+ validMaxDiskSize,
+ validMaxDiskLimit,
+ invalidMinGreaterThanMax,
+ formHandler,
+ ]);
+
+ return (
+ <>
+
+ formHandler('volumeSetName', name)}
+ isRequired
+ />
+ {lvsNameHelpTxt ?