diff --git a/models/operator_session_response.go b/models/operator_session_response.go index b3c4dfed2..4ac206280 100644 --- a/models/operator_session_response.go +++ b/models/operator_session_response.go @@ -37,6 +37,9 @@ import ( // swagger:model operatorSessionResponse type OperatorSessionResponse struct { + // features + Features []string `json:"features"` + // operator Operator bool `json:"operator,omitempty"` diff --git a/operatorapi/config.go b/operatorapi/config.go index 1738d745b..40c52c2fe 100644 --- a/operatorapi/config.go +++ b/operatorapi/config.go @@ -65,3 +65,8 @@ func getK8sSAToken() string { } return string(dat) } + +// Marketplace Mode Token +func getMPMode() string { + return env.Get(ConsoleMPMode, "") +} diff --git a/operatorapi/consts.go b/operatorapi/consts.go index f668a1f9d..3cf7b7e35 100644 --- a/operatorapi/consts.go +++ b/operatorapi/consts.go @@ -21,6 +21,7 @@ const ( ConsoleSubnetLicense = "CONSOLE_SUBNET_LICENSE" ConsoleOperatorSAToken = "CONSOLE_OPERATOR_SA_TOKEN" MinIOSubnetLicense = "MINIO_SUBNET_LICENSE" + ConsoleMPMode = "CONSOLE_OPERATOR_MARKETPLACE" // Constants for prometheus annotations prometheusPath = "prometheus.io/path" diff --git a/operatorapi/embedded_spec.go b/operatorapi/embedded_spec.go index ebc6c991a..41afbd101 100644 --- a/operatorapi/embedded_spec.go +++ b/operatorapi/embedded_spec.go @@ -2361,6 +2361,12 @@ func init() { "operatorSessionResponse": { "type": "object", "properties": { + "features": { + "type": "array", + "items": { + "type": "string" + } + }, "operator": { "type": "boolean" }, @@ -6230,6 +6236,12 @@ func init() { "operatorSessionResponse": { "type": "object", "properties": { + "features": { + "type": "array", + "items": { + "type": "string" + } + }, "operator": { "type": "boolean" }, diff --git a/operatorapi/operator_session.go b/operatorapi/operator_session.go index 020242063..b594d5a35 100644 --- a/operatorapi/operator_session.go +++ b/operatorapi/operator_session.go @@ -17,6 +17,8 @@ package operatorapi import ( + "fmt" + "github.com/go-openapi/runtime/middleware" "github.com/minio/console/models" "github.com/minio/console/operatorapi/operations" @@ -44,6 +46,19 @@ func getSessionResponse(session *models.Principal) (*models.OperatorSessionRespo Status: models.OperatorSessionResponseStatusOk, Operator: true, Permissions: map[string][]string{}, + Features: getListOfOperatorFeatures(), } return sessionResp, nil } + +// getListOfEnabledFeatures returns a list of features +func getListOfOperatorFeatures() []string { + features := []string{} + mpEnabled := getMPMode() + + if mpEnabled != "" { + features = append(features, fmt.Sprintf("mp-mode-%s", mpEnabled)) + } + + return features +} diff --git a/operatorapi/operator_tenants.go b/operatorapi/operator_tenants.go index 2459ad5d7..e27a89a8b 100644 --- a/operatorapi/operator_tenants.go +++ b/operatorapi/operator_tenants.go @@ -1257,9 +1257,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre if tenantReq.LogSearchConfiguration.StorageClass != "" { logSearchStorageClass = tenantReq.LogSearchConfiguration.StorageClass } - if tenantReq.LogSearchConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 { - logSearchStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName - } if tenantReq.LogSearchConfiguration.Image != "" { logSearchImage = tenantReq.LogSearchConfiguration.Image } @@ -1346,12 +1343,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre if tenantReq.PrometheusConfiguration.StorageClass != "" { prometheusStorageClass = tenantReq.PrometheusConfiguration.StorageClass } - - // Default class name for prometheus - if tenantReq.PrometheusConfiguration.StorageClass == "" && len(tenantReq.Pools) > 0 { - prometheusStorageClass = tenantReq.Pools[0].VolumeConfiguration.StorageClassName - } - if tenantReq.PrometheusConfiguration.Image != "" { prometheusImage = tenantReq.PrometheusConfiguration.Image } diff --git a/portal-ui/src/common/__tests__/utils.test.ts b/portal-ui/src/common/__tests__/utils.test.ts index 13e6778aa..6fa375bc4 100644 --- a/portal-ui/src/common/__tests__/utils.test.ts +++ b/portal-ui/src/common/__tests__/utils.test.ts @@ -37,6 +37,7 @@ test("From value and unit to a number of bytes for kubernetes", () => { expect(getBytes("1", "Ki", true)).toBe("1024"); expect(getBytes("1", "Mi", true)).toBe("1048576"); expect(getBytes("1", "Gi", true)).toBe("1073741824"); + expect(getBytes("7500", "Gi", true)).toBe("8053063680000"); }); test("Determine the amount of memory to use", () => { diff --git a/portal-ui/src/common/types.ts b/portal-ui/src/common/types.ts index 3accaa8b3..3fa4d26bd 100644 --- a/portal-ui/src/common/types.ts +++ b/portal-ui/src/common/types.ts @@ -147,18 +147,18 @@ export interface ITolerationSeconds { } export interface IResourceModel { - requests: IResourceRequests; + requests?: IResourceRequests; limits?: IResourceLimits; } export interface IResourceRequests { - memory: number; - cpu: number; + memory?: number; + cpu?: number; } export interface IResourceLimits { - memory: number; - cpu: number; + memory?: number; + cpu?: number; } export interface ITLSTenantConfiguration { @@ -272,7 +272,7 @@ export interface IActiveDirectoryConfiguration { } export interface IStorageDistribution { - error: number; + error: number | string; nodes: number; persistentVolumes: number; disks: number; diff --git a/portal-ui/src/common/utils.ts b/portal-ui/src/common/utils.ts index 4369b9c33..6076ff312 100644 --- a/portal-ui/src/common/utils.ts +++ b/portal-ui/src/common/utils.ts @@ -15,10 +15,13 @@ // along with this program. If not, see . import storage from "local-storage-fallback"; -import get from "lodash/get"; -import { ICapacity, IErasureCodeCalc, IStorageFactors } from "./types"; +import { + ICapacity, + IErasureCodeCalc, + IStorageDistribution, + IStorageFactors, +} from "./types"; import { IPool } from "../screens/Console/Tenants/ListTenants/types"; -import { AllocableResourcesResponse } from "../screens/Console/Tenants/types"; const minStReq = 1073741824; // Minimal Space required for MinIO const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes @@ -199,8 +202,7 @@ export const calculateDistribution = ( forcedNodes: number = 0, limitSize: number = 0, drivesPerServer: number = 0 -) => { - let numberOfNodes = {}; +): IStorageDistribution => { const requestedSizeBytes = getBytes( capacityToUse.value, capacityToUse.unit, @@ -227,7 +229,7 @@ export const calculateDistribution = ( }; } - numberOfNodes = calculateStorage( + let numberOfNodes = calculateStorage( requestedSizeBytes, forcedNodes, limitSize, @@ -242,7 +244,7 @@ const calculateStorage = ( forcedNodes: number, limitSize: number, drivesPerServer: number -) => { +): IStorageDistribution => { // Size validation const intReqBytes = parseInt(requestedBytes, 10); const maxDiskSize = minStReq * 256; // 256 GiB @@ -263,7 +265,7 @@ const structureCalc = ( maxDiskSize: number, maxClusterSize: number, disksPerNode: number = 0 -) => { +): IStorageDistribution => { if ( isNaN(nodes) || isNaN(desiredCapacity) || @@ -275,7 +277,7 @@ const structureCalc = ( nodes: 0, persistentVolumes: 0, disks: 0, - volumePerDisk: 0, + pvSize: 0, }; // Invalid Data } @@ -316,7 +318,7 @@ const structureCalc = ( nodes: 0, persistentVolumes: 0, disks: 0, - volumePerDisk: 0, + pvSize: 0, }; // Cannot allocate this server } } @@ -328,7 +330,7 @@ const structureCalc = ( nodes: 0, persistentVolumes: 0, disks: 0, - volumePerDisk: 0, + pvSize: 0, }; // Cannot allocate this volume size } @@ -501,7 +503,8 @@ export const getTimeFromTimestamp = ( export const calculateBytes = ( x: string, showDecimals = false, - roundFloor = true + roundFloor = true, + k8sUnit = false ) => { const bytes = parseInt(x, 10); @@ -523,7 +526,7 @@ export const calculateBytes = ( // Get Unit parsed const unitParsed = parseFloat(roundedUnit.toFixed(fractionDigits)); - const finalUnit = units[i]; + const finalUnit = k8sUnit ? k8sCalcUnits[i] : units[i]; return { total: unitParsed, unit: finalUnit }; }; @@ -596,113 +599,3 @@ export const decodeFileName = (text: string) => { return text; } }; - -export const setResourcesValidation = ( - memorySize: number, - cpusSelected: number, - maxAllocatableResources: AllocableResourcesResponse -) => { - const requestedSizeBytes = getBytes(memorySize.toString(10), "GB"); - const memReqSize = parseInt(requestedSizeBytes, 10); - - const minimalRequiredMemory = 2147483648; // Minimal required memory, 2Gi - - const memoryExists = get( - maxAllocatableResources, - "min_allocatable_mem", - false - ); - - const cpuExists = get(maxAllocatableResources, "min_allocatable_cpu", false); - - if (memoryExists === false) { - return { - error: - "No available memory for the selected number of nodes. Please try another combination.", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - if (cpuExists === false) { - return { - error: - "No available CPUs for the selected number of nodes. Please try another combination", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - if (memReqSize < minimalRequiredMemory) { - return { - error: "Memory size is set bellow minimum required", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - if (cpusSelected < 1) { - return { - error: "CPU amount is set bellow minimum available", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - if ( - memReqSize <= maxAllocatableResources.mem_priority.max_allocatable_mem && - cpusSelected > maxAllocatableResources.mem_priority.max_allocatable_cpu - ) { - return { - error: - "It is not possible to allocate this amount of memory in all the CPUs", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - if ( - cpusSelected <= maxAllocatableResources.cpu_priority.max_allocatable_cpu && - memReqSize > maxAllocatableResources.cpu_priority.max_allocatable_mem - ) { - return { - error: - "It is not possible to allocate this amount of CPUs with the available memory", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - if ( - cpusSelected > maxAllocatableResources.cpu_priority.max_allocatable_cpu || - memReqSize > maxAllocatableResources.mem_priority.max_allocatable_mem - ) { - return { - error: "CPUs or Memory selected is beyond bounds", - memoryRequest: 0, - memoryLimit: 0, - cpuRequest: 0, - cpuLimit: 0, - }; - } - - return { - error: "", - memoryRequest: memReqSize, - memoryLimit: maxAllocatableResources.mem_priority.max_allocatable_mem, - cpuRequest: cpusSelected, - cpuLimit: maxAllocatableResources.cpu_priority.max_allocatable_cpu, - }; -}; diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx index 854ea4f1e..40fb8149f 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx @@ -39,7 +39,6 @@ import { KeyPair } from "../ListTenants/utils"; import { setErrorSnackMessage } from "../../../../actions"; import { getDefaultAffinity, getNodeSelector } from "../TenantDetails/utils"; import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt"; -import NameTenant from "./Steps/NameTenant"; import { AppState } from "../../../../store"; import { ICertificatesItems, IFieldStore } from "../types"; import { resetAddTenantForm, updateAddField } from "../actions"; @@ -53,6 +52,7 @@ import history from "../../../../history"; import Images from "./Steps/Images"; import PageLayout from "../../Common/Layout/PageLayout"; import BackLink from "../../../../common/BackLink"; +import TenantResources from "./Steps/TenantResources/TenantResources"; interface IAddTenantProps { setErrorSnackMessage: typeof setErrorSnackMessage; @@ -174,7 +174,6 @@ const AddTenant = ({ const enableTLS = fields.security.enableTLS; const ecParity = fields.tenantSize.ecParity; const distribution = fields.tenantSize.distribution; - const resourcesSize = fields.tenantSize.resourcesSize; const tenantCustom = fields.configure.tenantCustom; const logSearchCustom = fields.configure.logSearchCustom; const prometheusCustom = fields.configure.prometheusCustom; @@ -253,16 +252,6 @@ const AddTenant = ({ size: distribution.pvSize, storage_class_name: selectedStorageClass, }, - resources: { - requests: { - memory: resourcesSize.memoryRequest, - cpu: resourcesSize.cpuRequest, - }, - limits: { - memory: resourcesSize.memoryRequest, - cpu: resourcesSize.cpuRequest, - }, - }, securityContext: tenantCustom ? tenantSecurityContext : null, ...affinityObject, }, @@ -270,6 +259,49 @@ const AddTenant = ({ erasureCodingParity: parseInt(erasureCode, 10), }; + // Set Resources + if ( + fields.tenantSize.resourcesCPURequest !== "" || + fields.tenantSize.resourcesCPULimit !== "" || + fields.tenantSize.resourcesMemoryRequest !== "" || + fields.tenantSize.resourcesMemoryLimit !== "" + ) { + dataSend.pools[0].resources = {}; + // requests + if ( + fields.tenantSize.resourcesCPURequest !== "" || + fields.tenantSize.resourcesMemoryRequest !== "" + ) { + dataSend.pools[0].resources.requests = {}; + if (fields.tenantSize.resourcesCPURequest !== "") { + dataSend.pools[0].resources.requests.cpu = parseInt( + fields.tenantSize.resourcesCPURequest + ); + } + if (fields.tenantSize.resourcesMemoryRequest !== "") { + dataSend.pools[0].resources.requests.memory = parseInt( + fields.tenantSize.resourcesMemoryRequest + ); + } + } + // limits + if ( + fields.tenantSize.resourcesCPULimit !== "" || + fields.tenantSize.resourcesMemoryLimit !== "" + ) { + dataSend.pools[0].resources.limits = {}; + if (fields.tenantSize.resourcesCPULimit !== "") { + dataSend.pools[0].resources.limits.cpu = parseInt( + fields.tenantSize.resourcesCPULimit + ); + } + if (fields.tenantSize.resourcesMemoryLimit !== "") { + dataSend.pools[0].resources.limits.memory = parseInt( + fields.tenantSize.resourcesMemoryLimit + ); + } + } + } if (customDockerhub) { dataSend = { ...dataSend, @@ -285,7 +317,10 @@ const AddTenant = ({ dataSend = { ...dataSend, logSearchConfiguration: { - storageClass: logSearchSelectedStorageClass, + storageClass: + logSearchSelectedStorageClass === "default" + ? "" + : logSearchSelectedStorageClass, storageSize: parseInt(logSearchVolumeSize), image: logSearchImage, postgres_image: logSearchPostgresImage, @@ -309,7 +344,10 @@ const AddTenant = ({ dataSend = { ...dataSend, prometheusConfiguration: { - storageClass: prometheusSelectedStorageClass, + storageClass: + prometheusSelectedStorageClass === "default" + ? "" + : prometheusSelectedStorageClass, storageSize: parseInt(prometheusVolumeSize), image: prometheusImage, sidecar_image: prometheusSidecarImage, @@ -671,7 +709,7 @@ const AddTenant = ({ const wizardSteps: IWizardElement[] = [ { label: "Setup", - componentRender: , + componentRender: , buttons: [cancelButton, createButton], }, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx index 77c9050a3..00f266a8b 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx @@ -163,6 +163,11 @@ const Configure = ({ }: IConfigureProps) => { const [validationErrors, setValidationErrors] = useState({}); + const configureSTClasses = [ + { label: "Default", value: "default" }, + ...storageClasses, + ]; + // Common const updateField = useCallback( (field: string, value: any) => { @@ -450,20 +455,20 @@ const Configure = ({ ]); useEffect(() => { - // New default values is current selection is invalid + // New default values in current selection is invalid if (storageClasses.length > 0) { const filterPrometheus = storageClasses.filter( (item: any) => item.value === prometheusSelectedStorageClass ); if (filterPrometheus.length === 0) { - updateField("prometheusSelectedStorageClass", selectedStorageClass); + updateField("prometheusSelectedStorageClass", "default"); } const filterLogSearch = storageClasses.filter( (item: any) => item.value === logSearchSelectedStorageClass ); if (filterLogSearch.length === 0) { - updateField("logSearchSelectedStorageClass", selectedStorageClass); + updateField("logSearchSelectedStorageClass", "default"); } } }, [ @@ -673,8 +678,8 @@ const Configure = ({ }} label="Log Search Storage Class" value={logSearchSelectedStorageClass} - options={storageClasses} - disabled={storageClasses.length < 1} + options={configureSTClasses} + disabled={configureSTClasses.length < 1} /> @@ -925,8 +930,8 @@ const Configure = ({ }} label="Prometheus Storage Class" value={prometheusSelectedStorageClass} - options={storageClasses} - disabled={storageClasses.length < 1} + options={configureSTClasses} + disabled={configureSTClasses.length < 1} /> diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx index 36aa2a85e..c34ba2ea4 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx @@ -277,31 +277,6 @@ const Images = ({ logSearchVolumeSize, ]); - useEffect(() => { - // New default values is current selection is invalid - if (storageClasses.length > 0) { - const filterPrometheus = storageClasses.filter( - (item: any) => item.value === prometheusSelectedStorageClass - ); - if (filterPrometheus.length === 0) { - updateField("prometheusSelectedStorageClass", selectedStorageClass); - } - - const filterLogSearch = storageClasses.filter( - (item: any) => item.value === logSearchSelectedStorageClass - ); - if (filterLogSearch.length === 0) { - updateField("logSearchSelectedStorageClass", selectedStorageClass); - } - } - }, [ - logSearchSelectedStorageClass, - prometheusSelectedStorageClass, - selectedStorageClass, - storageClasses, - updateField, - ]); - const cleanValidation = (fieldName: string) => { setValidationErrors(clearValidationError(validationErrors, fieldName)); }; diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/SizePreview.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/SizePreview.tsx index d5ca3b190..e820a90f9 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/SizePreview.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/SizePreview.tsx @@ -35,12 +35,12 @@ import { IResourcesSize } from "../../ListTenants/types"; import { IErasureCodeCalc } from "../../../../../common/types"; import { Divider } from "@mui/material"; +import { IntegrationConfiguration } from "./TenantResources/utils"; interface ISizePreviewProps { classes: any; updateAddField: typeof updateAddField; isPageValid: typeof isPageValid; - advancedMode: boolean; volumeSize: string; sizeFactor: string; drivesPerServer: string; @@ -49,13 +49,13 @@ interface ISizePreviewProps { ecParity: string; ecParityChoices: Opts[]; cleanECChoices: string[]; - maxAllocableMemo: number; resourcesSize: IResourcesSize; distribution: any; ecParityCalc: IErasureCodeCalc; limitSize: any; selectedStorageClass: string; cpuToUse: string; + integrationSelection: IntegrationConfiguration; } const styles = (theme: Theme) => @@ -76,7 +76,6 @@ const SizePreview = ({ classes, updateAddField, isPageValid, - advancedMode, volumeSize, sizeFactor, drivesPerServer, @@ -85,13 +84,13 @@ const SizePreview = ({ ecParity, ecParityChoices, cleanECChoices, - maxAllocableMemo, resourcesSize, distribution, ecParityCalc, limitSize, selectedStorageClass, cpuToUse, + integrationSelection, }: ISizePreviewProps) => { const usableInformation = ecParityCalc.storageFactors.find( (element) => element.erasureCode === ecParity @@ -109,34 +108,47 @@ const SizePreview = ({ {parseInt(nodes) > 0 ? nodes : "-"} - - Drives per Server - - {distribution ? distribution.disks : "-"} - - - - Drive Capacity - - {distribution ? niceBytes(distribution.pvSize) : "-"} - - + {integrationSelection.typeSelection === "" && + integrationSelection.storageClass === "" && ( + + + Drives per Server + + {distribution ? distribution.disks : "-"} + + + + Drive Capacity + + {distribution ? niceBytes(distribution.pvSize) : "-"} + + + + )} + Total Volumes {distribution ? distribution.persistentVolumes : "-"} - {!advancedMode && ( - - Memory per Node - {memoryNode} Gi - - )} - - CPU Selection - {cpuToUse} - + {integrationSelection.typeSelection === "" && + integrationSelection.storageClass === "" && ( + + + Memory per Node + {memoryNode} Gi + + + + CPU Selection + + + {cpuToUse} + + + + )} {ecParityCalc.error === 0 && usableInformation && ( @@ -184,12 +196,59 @@ const SizePreview = ({ )} + {integrationSelection.typeSelection !== "" && + integrationSelection.storageClass !== "" && ( + +

Single Instance Configuration

+ + + + + CPU + + {integrationSelection.CPU !== 0 + ? integrationSelection.CPU + : "-"} + + + + Memory + + {integrationSelection.memory !== 0 + ? `${integrationSelection.memory} Gi` + : "-"} + + + + Drives per Server + + {integrationSelection.drivesPerServer !== 0 + ? `${integrationSelection.drivesPerServer}` + : "-"} + + + + + Drive Size + + + {integrationSelection.driveSize.driveSize} + {integrationSelection.driveSize.sizeUnit} + + + +
+
+ )} ); }; const mapState = (state: AppState) => ({ - advancedMode: state.tenants.createTenant.advancedModeOn, volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize, sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor, drivesPerServer: state.tenants.createTenant.fields.tenantSize.drivesPerServer, @@ -198,8 +257,6 @@ const mapState = (state: AppState) => ({ ecParity: state.tenants.createTenant.fields.tenantSize.ecParity, ecParityChoices: state.tenants.createTenant.fields.tenantSize.ecParityChoices, cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices, - maxAllocableMemo: - state.tenants.createTenant.fields.tenantSize.maxAllocableMemo, resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize, distribution: state.tenants.createTenant.fields.tenantSize.distribution, ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc, @@ -207,6 +264,8 @@ const mapState = (state: AppState) => ({ selectedStorageClass: state.tenants.createTenant.fields.nameTenant.selectedStorageClass, cpuToUse: state.tenants.createTenant.fields.tenantSize.cpuToUse, + integrationSelection: + state.tenants.createTenant.fields.tenantSize.integrationSelection, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/NameTenant.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/NameTenantMain.tsx similarity index 72% rename from portal-ui/src/screens/Console/Tenants/AddTenant/Steps/NameTenant.tsx rename to portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/NameTenantMain.tsx index c1ee527e2..30a334d49 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/NameTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/NameTenantMain.tsx @@ -32,32 +32,33 @@ import { formFieldStyles, modalBasic, wizardCommon, -} from "../../../Common/FormComponents/common/styleLibrary"; -import { setModalErrorSnackMessage } from "../../../../../actions"; +} from "../../../../Common/FormComponents/common/styleLibrary"; +import { setModalErrorSnackMessage } from "../../../../../../actions"; import { isPageValid, setLimitSize, setStorageClassesList, updateAddField, -} from "../../actions"; +} from "../../../actions"; import { getLimitSizes, IQuotaElement, IQuotas, Opts, -} from "../../ListTenants/utils"; -import { AppState } from "../../../../../store"; -import { commonFormValidation } from "../../../../../utils/validationFunctions"; -import { clearValidationError } from "../../utils"; -import { ErrorResponseHandler } from "../../../../../common/types"; -import api from "../../../../../common/api"; -import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; -import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; -import AddIcon from "../../../../../icons/AddIcon"; -import AddNamespaceModal from "./helpers/AddNamespaceModal"; -import SizePreview from "./SizePreview"; +} from "../../../ListTenants/utils"; +import { AppState } from "../../../../../../store"; +import { commonFormValidation } from "../../../../../../utils/validationFunctions"; +import { clearValidationError } from "../../../utils"; +import { ErrorResponseHandler } from "../../../../../../common/types"; +import api from "../../../../../../common/api"; +import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import AddIcon from "../../../../../../icons/AddIcon"; +import AddNamespaceModal from "../helpers/AddNamespaceModal"; +import SizePreview from "../SizePreview"; import TenantSize from "./TenantSize"; import { Paper, SelectChangeEvent } from "@mui/material"; +import { IMkEnvs, mkPanelConfigurations } from "./utils"; const styles = (theme: Theme) => createStyles({ @@ -73,7 +74,7 @@ const styles = (theme: Theme) => ...wizardCommon, }); -interface INameTenantScreen { +interface INameTenantMainScreen { classes: any; storageClasses: Opts[]; setModalErrorSnackMessage: typeof setModalErrorSnackMessage; @@ -84,20 +85,24 @@ interface INameTenantScreen { tenantName: string; namespace: string; selectedStorageClass: string; + selectedStorageType: string; + formToRender?: IMkEnvs; } -const NameTenant = ({ +const NameTenantMain = ({ classes, storageClasses, tenantName, namespace, selectedStorageClass, + selectedStorageType, + formToRender = IMkEnvs.default, updateAddField, setStorageClassesList, setLimitSize, isPageValid, setModalErrorSnackMessage, -}: INameTenantScreen) => { +}: INameTenantMainScreen) => { const [validationErrors, setValidationErrors] = useState({}); const [emptyNamespace, setEmptyNamespace] = useState(true); const [loadingNamespaceInfo, setLoadingNamespaceInfo] = @@ -230,7 +235,8 @@ const NameTenant = ({ const isValid = !("tenant-name" in commonValidation) && !("namespace" in commonValidation) && - storageClasses.length > 0; + ((formToRender === IMkEnvs.default && storageClasses.length > 0) || + (formToRender !== IMkEnvs.default && selectedStorageType !== "")); isPageValid("nameTenant", isValid); @@ -242,6 +248,8 @@ const NameTenant = ({ isPageValid, emptyNamespace, loadingNamespaceInfo, + selectedStorageType, + formToRender, ]); const frmValidationCleanup = (fieldName: string) => { @@ -271,11 +279,11 @@ const NameTenant = ({ )} - +
-

Name Tenant

+

Name

How would you like to name this new tenant? @@ -311,23 +319,57 @@ const NameTenant = ({ required /> - - ) => { - updateField( - "selectedStorageClass", - e.target.value as string - ); - }} - label="Storage Class" - value={selectedStorageClass} - options={storageClasses} - disabled={storageClasses.length < 1} - /> - - + {formToRender === IMkEnvs.default ? ( + + ) => { + updateField( + "selectedStorageClass", + e.target.value as string + ); + }} + label="Storage Class" + value={selectedStorageClass} + options={storageClasses} + disabled={storageClasses.length < 1} + /> + + ) : ( + + ) => { + updateField( + "selectedStorageType", + e.target.value as string + ); + }} + label={get( + mkPanelConfigurations, + `${formToRender}.variantSelectorLabel`, + "Storage Type" + )} + value={selectedStorageType} + options={get( + mkPanelConfigurations, + `${formToRender}.variantSelectorValues`, + [] + )} + /> + + )} + {formToRender === IMkEnvs.default ? ( + + ) : ( + get( + mkPanelConfigurations, + `${formToRender}.sizingComponent`, + null + ) + )} @@ -346,6 +388,8 @@ const mapState = (state: AppState) => ({ namespace: state.tenants.createTenant.fields.nameTenant.namespace, selectedStorageClass: state.tenants.createTenant.fields.nameTenant.selectedStorageClass, + selectedStorageType: + state.tenants.createTenant.fields.nameTenant.selectedStorageType, storageClasses: state.tenants.createTenant.storageClasses, }); @@ -357,4 +401,4 @@ const connector = connect(mapState, { isPageValid, }); -export default withStyles(styles)(connector(NameTenant)); +export default withStyles(styles)(connector(NameTenantMain)); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantResources.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantResources.tsx new file mode 100644 index 000000000..eab73623b --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantResources.tsx @@ -0,0 +1,64 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import get from "lodash/get"; +import NameTenantMain from "./NameTenantMain"; +import { IMkEnvs, resourcesConfigurations } from "./utils"; +import { AppState } from "../../../../../../store"; + +interface ITenantResources { + features?: string[]; +} + +const TenantResources = ({ features }: ITenantResources) => { + const [formRender, setFormRender] = useState(null); + + useEffect(() => { + let setConfiguration = IMkEnvs.default; + + if (features && features.length !== 0) { + const possibleVariables = Object.keys(resourcesConfigurations); + + possibleVariables.forEach((element) => { + if (features.includes(element)) { + setConfiguration = get( + resourcesConfigurations, + element, + IMkEnvs.default + ); + } + }); + } + + setFormRender(setConfiguration); + }, [features]); + + if (formRender === null) { + return null; + } + + return ; +}; + +const mapState = (state: AppState) => ({ + features: state.console.session.features, +}); + +const connector = connect(mapState, null); + +export default connector(TenantResources); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantSize.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSize.tsx similarity index 63% rename from portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantSize.tsx rename to portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSize.tsx index b7dd5eb82..8bd9b85a1 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantSize.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSize.tsx @@ -20,14 +20,13 @@ import { Theme } from "@mui/material/styles"; import { SelectChangeEvent } from "@mui/material"; import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; -import get from "lodash/get"; -import { AppState } from "../../../../../store"; -import { isPageValid, updateAddField } from "../../actions"; +import { AppState } from "../../../../../../store"; +import { isPageValid, updateAddField } from "../../../actions"; import { formFieldStyles, modalBasic, wizardCommon, -} from "../../../Common/FormComponents/common/styleLibrary"; +} from "../../../../Common/FormComponents/common/styleLibrary"; import Grid from "@mui/material/Grid"; import { calculateDistribution, @@ -35,23 +34,21 @@ import { getBytes, k8sfactorForDropdown, niceBytes, - setResourcesValidation, -} from "../../../../../common/utils"; -import { clearValidationError } from "../../utils"; -import { ecListTransform, Opts } from "../../ListTenants/utils"; -import { IResourcesSize } from "../../ListTenants/types"; -import { AllocableResourcesResponse } from "../../types"; -import { ICapacity, IErasureCodeCalc } from "../../../../../common/types"; -import { commonFormValidation } from "../../../../../utils/validationFunctions"; -import api from "../../../../../common/api"; -import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; -import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +} from "../../../../../../common/utils"; +import { clearValidationError } from "../../../utils"; +import { ecListTransform, Opts } from "../../../ListTenants/utils"; +import { IResourcesSize } from "../../../ListTenants/types"; +import { ICapacity, IErasureCodeCalc } from "../../../../../../common/types"; +import { commonFormValidation } from "../../../../../../utils/validationFunctions"; +import api from "../../../../../../common/api"; +import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import TenantSizeResources from "./TenantSizeResources"; interface ITenantSizeProps { classes: any; updateAddField: typeof updateAddField; isPageValid: typeof isPageValid; - advancedMode: boolean; volumeSize: string; sizeFactor: string; drivesPerServer: string; @@ -60,16 +57,11 @@ interface ITenantSizeProps { ecParity: string; ecParityChoices: Opts[]; cleanECChoices: string[]; - maxAllocableMemo: number; resourcesSize: IResourcesSize; distribution: any; ecParityCalc: IErasureCodeCalc; limitSize: any; selectedStorageClass: string; - cpuToUse: string; - maxAllocatableResources: AllocableResourcesResponse; - maxCPUsUse: string; - maxMemorySize: string; } const styles = (theme: Theme) => @@ -98,7 +90,6 @@ const TenantSize = ({ classes, updateAddField, isPageValid, - advancedMode, volumeSize, sizeFactor, drivesPerServer, @@ -107,16 +98,11 @@ const TenantSize = ({ ecParity, ecParityChoices, cleanECChoices, - maxAllocableMemo, resourcesSize, distribution, ecParityCalc, limitSize, - cpuToUse, selectedStorageClass, - maxAllocatableResources, - maxCPUsUse, - maxMemorySize, }: ITenantSizeProps) => { const [validationErrors, setValidationErrors] = useState({}); const [errorFlag, setErrorFlag] = useState(false); @@ -138,23 +124,6 @@ const TenantSize = ({ // Storage Quotas - const validateResourcesSize = useCallback(() => { - const memSize = memoryNode || "0"; - const cpusSelected = cpuToUse; - - const resourcesSize = setResourcesValidation( - parseInt(memSize), - parseInt(cpusSelected), - maxAllocatableResources - ); - - updateField("resourcesSize", resourcesSize); - }, [memoryNode, cpuToUse, maxAllocatableResources, updateField]); - - useEffect(() => { - validateResourcesSize(); - }, [memoryNode, cpuToUse, validateResourcesSize]); - useEffect(() => { if (ecParityChoices.length > 0 && distribution.error === "") { const ecCodeValidated = erasureCodeCalc( @@ -165,9 +134,11 @@ const TenantSize = ({ ); updateField("ecParityCalc", ecCodeValidated); - updateField("ecParity", ecCodeValidated.defaultEC); + if (!cleanECChoices.includes(ecParity) || ecParity === "") { + updateField("ecParity", ecCodeValidated.defaultEC); + } } - }, [ecParityChoices.length, distribution, cleanECChoices, updateField]); + }, [ecParity, ecParityChoices.length, distribution, cleanECChoices, updateField]); /*End debounce functions*/ /*Calculate Allocation*/ @@ -192,74 +163,7 @@ const TenantSize = ({ updateField("distribution", distrCalculate); setErrorFlag(false); setNodeError(""); - - // Get allocatable Resources - api - .invoke("GET", `api/v1/cluster/allocatable-resources?num_nodes=${nodes}`) - .then((res: AllocableResourcesResponse) => { - updateField("maxAllocatableResources", res); - - const maxAllocatableResources = res; - - const memoryExists = get( - maxAllocatableResources, - "min_allocatable_mem", - false - ); - - const cpuExists = get( - maxAllocatableResources, - "min_allocatable_cpu", - false - ); - - if (memoryExists === false || cpuExists === false) { - updateField("cpuToUse", 0); - - updateField("maxMemorySize", "0"); - updateField("maxCPUsUse", "0"); - - validateResourcesSize(); - return; - } - - // We default to Best CPU Configuration - updateField( - "maxMemorySize", - res.mem_priority.max_allocatable_mem.toString() - ); - updateField( - "maxCPUsUse", - res.cpu_priority.max_allocatable_cpu.toString() - ); - - updateField("maxAllocableMemo", res.mem_priority.max_allocatable_mem); - - const cpuInt = parseInt(cpuToUse); - const maxAlocatableCPU = get( - maxAllocatableResources, - "cpu_priority.max_allocatable_cpu", - 0 - ); - - if (cpuInt === 0 && cpuInt !== maxAlocatableCPU) { - updateField("cpuToUse", maxAlocatableCPU); - } else if (cpuInt > maxAlocatableCPU) { - updateField("cpuToUse", maxAlocatableCPU); - } - - // We reset error states - validateResourcesSize(); - }) - .catch((err: any) => { - updateField("maxAllocableMemo", 0); - updateField("cpuToUse", "0"); - setErrorFlag(true); - setNodeError(err.errorMessage); - console.error(err); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [nodes, volumeSize, sizeFactor, updateField]); + }, [nodes, volumeSize, sizeFactor, updateField, drivesPerServer]); /*Calculate Allocation End*/ @@ -288,13 +192,6 @@ const TenantSize = ({ true )}`, }, - { - fieldKey: "memory_per_node", - required: true, - value: memoryNode, - customValidation: parseInt(memoryNode) < 2, - customValidationMessage: "Memory size must be greater than 2Gi", - }, { fieldKey: "drivesps", required: true, @@ -308,13 +205,9 @@ const TenantSize = ({ "tenantSize", !("nodes" in commonValidation) && !("volume_size" in commonValidation) && - !("memory_per_node" in commonValidation) && !("drivesps" in commonValidation) && distribution.error === "" && ecParityCalc.error === 0 && - resourcesSize.error === "" && - parseInt(cpuToUse) <= parseInt(maxCPUsUse) && - parseInt(cpuToUse) > 0 && ecParity !== "" ); @@ -329,8 +222,6 @@ const TenantSize = ({ resourcesSize, limitSize, selectedStorageClass, - cpuToUse, - maxCPUsUse, isPageValid, errorFlag, nodeError, @@ -365,7 +256,7 @@ const TenantSize = ({
-

Tenant Size

+

Capacity

Please select the desired capacity @@ -376,11 +267,6 @@ const TenantSize = ({
{distribution.error}
)} - {resourcesSize.error !== "" && ( - -
{resourcesSize.error}
-
- )} - - { - if (e.target.validity.valid) { - updateField("cpuToUse", e.target.value); - } - }} - value={cpuToUse} - disabled={selectedStorageClass === ""} - min="1" - max={maxCPUsUse} - error={ - parseInt(cpuToUse) > parseInt(maxCPUsUse) || - parseInt(cpuToUse) <= 0 || - isNaN(parseInt(cpuToUse)) - ? "Invalid CPU Configuration" - : "" - } - pattern={"[0-9]*"} - /> - - - - ) => { - updateField("memoryNode", e.target.value); - cleanValidation("memory_per_node"); - }} - label="Memory per Node [Gi]" - value={memoryNode} - disabled={selectedStorageClass === ""} - required - error={validationErrors["memory_per_node"] || ""} - min="2" - max={maxMemorySize} - /> - + + ); }; const mapState = (state: AppState) => ({ - advancedMode: state.tenants.createTenant.advancedModeOn, volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize, sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor, drivesPerServer: state.tenants.createTenant.fields.tenantSize.drivesPerServer, @@ -530,19 +374,13 @@ const mapState = (state: AppState) => ({ ecParity: state.tenants.createTenant.fields.tenantSize.ecParity, ecParityChoices: state.tenants.createTenant.fields.tenantSize.ecParityChoices, cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices, - maxAllocableMemo: - state.tenants.createTenant.fields.tenantSize.maxAllocableMemo, + resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize, distribution: state.tenants.createTenant.fields.tenantSize.distribution, ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc, limitSize: state.tenants.createTenant.limitSize, selectedStorageClass: state.tenants.createTenant.fields.nameTenant.selectedStorageClass, - cpuToUse: state.tenants.createTenant.fields.tenantSize.cpuToUse, - maxAllocatableResources: - state.tenants.createTenant.fields.tenantSize.maxAllocatableResources, - maxCPUsUse: state.tenants.createTenant.fields.tenantSize.maxCPUsUse, - maxMemorySize: state.tenants.createTenant.fields.tenantSize.maxMemorySize, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSizeMK.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSizeMK.tsx new file mode 100644 index 000000000..3d54b5b6f --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSizeMK.tsx @@ -0,0 +1,385 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useCallback, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { Theme } from "@mui/material/styles"; +import { SelectChangeEvent } from "@mui/material"; +import get from "lodash/get"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { AppState } from "../../../../../../store"; +import { isPageValid, updateAddField } from "../../../actions"; +import { + formFieldStyles, + modalBasic, + wizardCommon, +} from "../../../../Common/FormComponents/common/styleLibrary"; +import Grid from "@mui/material/Grid"; +import { erasureCodeCalc, getBytes } from "../../../../../../common/utils"; +import { clearValidationError } from "../../../utils"; +import { ecListTransform, Opts } from "../../../ListTenants/utils"; +import { IResourcesSize } from "../../../ListTenants/types"; +import { + IErasureCodeCalc, + IStorageDistribution, +} from "../../../../../../common/types"; +import { commonFormValidation } from "../../../../../../utils/validationFunctions"; +import api from "../../../../../../common/api"; +import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import { + IMkEnvs, + IntegrationConfiguration, + mkPanelConfigurations, +} from "./utils"; + +interface ITenantSizeAWSProps { + classes: any; + updateAddField: typeof updateAddField; + isPageValid: typeof isPageValid; + volumeSize: string; + sizeFactor: string; + drivesPerServer: string; + nodes: string; + memoryNode: string; + ecParity: string; + ecParityChoices: Opts[]; + cleanECChoices: string[]; + resourcesSize: IResourcesSize; + distribution: any; + ecParityCalc: IErasureCodeCalc; + limitSize: any; + selectedStorageType: string; + cpuToUse: string; + maxCPUsUse: string; + formToRender?: IMkEnvs; + integrationSelection: IntegrationConfiguration; +} + +const styles = (theme: Theme) => + createStyles({ + compositeFieldContainer: { + display: "flex", + alignItems: "center", + }, + compositeAddOn: { + marginLeft: 10, + "& div": { + marginBottom: 0, + }, + "@media (max-width: 900px)": { + "& div": { + marginTop: 5, + }, + }, + }, + ...formFieldStyles, + ...modalBasic, + ...wizardCommon, + }); + +const TenantSizeMK = ({ + classes, + updateAddField, + isPageValid, + volumeSize, + sizeFactor, + drivesPerServer, + nodes, + memoryNode, + ecParity, + ecParityChoices, + cleanECChoices, + resourcesSize, + distribution, + ecParityCalc, + limitSize, + cpuToUse, + selectedStorageType, + maxCPUsUse, + formToRender, + integrationSelection, +}: ITenantSizeAWSProps) => { + const [validationErrors, setValidationErrors] = useState({}); + + // Common + const updateField = useCallback( + (field: string, value: any) => { + updateAddField("tenantSize", field, value); + }, + [updateAddField] + ); + + const updateMainField = useCallback( + (field: string, value: string) => { + updateAddField("nameTenant", field, value); + }, + [updateAddField] + ); + + const cleanValidation = (fieldName: string) => { + setValidationErrors(clearValidationError(validationErrors, fieldName)); + }; + + /*Debounce functions*/ + + // Storage Quotas + useEffect(() => { + if (ecParityChoices.length > 0 && distribution.error === "") { + const ecCodeValidated = erasureCodeCalc( + cleanECChoices, + distribution.persistentVolumes, + distribution.pvSize, + distribution.nodes + ); + + updateField("ecParityCalc", ecCodeValidated); + + if (!cleanECChoices.includes(ecParity) || ecParity === "") { + updateField("ecParity", ecCodeValidated.defaultEC); + } + } + }, [ecParity, ecParityChoices, distribution, cleanECChoices, updateField]); + /*End debounce functions*/ + + /*Set location Storage Types*/ + useEffect(() => { + if (formToRender !== undefined && parseInt(nodes) >= 4) { + const setConfigs = mkPanelConfigurations[formToRender]; + const keyCount = Object.keys(setConfigs).length; + + //Configuration is filled + if (keyCount > 0) { + const configs: IntegrationConfiguration[] = get( + setConfigs, + "configurations", + [] + ); + + const mainSelection = configs.find( + (item) => item.typeSelection === selectedStorageType + ); + + if (mainSelection) { + updateField("integrationSelection", mainSelection); + updateMainField("selectedStorageClass", mainSelection.storageClass); + + let pvSize = parseInt( + getBytes( + mainSelection.driveSize.driveSize, + mainSelection.driveSize.sizeUnit, + true + ), + 10 + ); + + const distrCalculate: IStorageDistribution = { + pvSize, + nodes: parseInt(nodes), + disks: mainSelection.drivesPerServer, + persistentVolumes: mainSelection.drivesPerServer * parseInt(nodes), + error: "", + }; + + updateField("distribution", distrCalculate); + // apply requests, half of the available resources + updateField( + "resourcesCPURequest", + Math.max(1, mainSelection.CPU / 2) + ); + updateField( + "resourcesMemoryRequest", + Math.max(2, mainSelection.memory / 2) + ); + } + } + } + }, [nodes, selectedStorageType, formToRender, updateField, updateMainField]); + + /*Calculate Allocation End*/ + + /* Validations of pages */ + + useEffect(() => { + const commonValidation = commonFormValidation([ + { + fieldKey: "nodes", + required: true, + value: nodes, + customValidation: parseInt(nodes) < 4, + customValidationMessage: "Al least 4 servers must be selected", + }, + ]); + + isPageValid( + "tenantSize", + !("nodes" in commonValidation) && + distribution.error === "" && + ecParityCalc.error === 0 && + resourcesSize.error === "" && + ecParity !== "" && + parseInt(nodes) >= 4 + ); + + setValidationErrors(commonValidation); + }, [ + nodes, + volumeSize, + sizeFactor, + memoryNode, + distribution, + ecParityCalc, + resourcesSize, + limitSize, + selectedStorageType, + cpuToUse, + maxCPUsUse, + isPageValid, + drivesPerServer, + ecParity, + ]); + + useEffect(() => { + if (integrationSelection.drivesPerServer !== 0) { + // Get EC Value + if (nodes.trim() !== "") { + api + .invoke( + "GET", + `api/v1/get-parity/${nodes}/${integrationSelection.drivesPerServer}` + ) + .then((ecList: string[]) => { + updateField("ecParityChoices", ecListTransform(ecList)); + updateField("cleanECChoices", ecList); + }) + .catch((err: any) => { + updateField("ecparityChoices", []); + isPageValid("tenantSize", false); + updateField("ecParity", ""); + }); + } + } + }, [integrationSelection, nodes, isPageValid, updateField]); + + /* End Validation of pages */ + + return ( + + +
+

Tenant Size

+ + Please select the desired capacity + +
+
+ {distribution.error !== "" && ( + +
{distribution.error}
+
+ )} + {resourcesSize.error !== "" && ( + +
{resourcesSize.error}
+
+ )} + + ) => { + if (e.target.validity.valid) { + updateField("nodes", e.target.value); + cleanValidation("nodes"); + } + }} + label="Number of Servers" + disabled={selectedStorageType === ""} + value={nodes} + min="4" + required + error={validationErrors["nodes"] || ""} + pattern={"[0-9]*"} + /> + + + ) => { + updateField("ecParity", e.target.value as string); + }} + label="Erasure Code Parity" + disabled={selectedStorageType === ""} + value={ecParity} + options={ecParityChoices} + /> + + Please select the desired parity. This setting will change the max + usable capacity in the cluster + + +
+ ); +}; + +const mapState = (state: AppState) => () => { + const createTenant = state.tenants.createTenant; + + const { + memoryNode, + ecParityChoices, + distribution, + cleanECChoices, + sizeFactor, + ecParity, + cpuToUse, + integrationSelection, + resourcesSize, + drivesPerServer, + maxCPUsUse, + ecParityCalc, + volumeSize, + nodes, + } = createTenant.fields.tenantSize; + + return { + volumeSize, + sizeFactor, + drivesPerServer, + nodes, + memoryNode, + ecParity, + ecParityChoices, + cleanECChoices, + resourcesSize, + distribution, + ecParityCalc, + cpuToUse, + maxCPUsUse, + integrationSelection, + limitSize: createTenant.limitSize, + selectedStorageType: createTenant.fields.nameTenant.selectedStorageType, + }; +}; + +const connector = connect(mapState, { + updateAddField, + isPageValid, +}); + +export default withStyles(styles)(connector(TenantSizeMK)); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSizeResources.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSizeResources.tsx new file mode 100644 index 000000000..24fd2ec36 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/TenantSizeResources.tsx @@ -0,0 +1,400 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useCallback, useEffect } from "react"; +import { connect } from "react-redux"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import get from "lodash/get"; +import { AppState } from "../../../../../../store"; +import { isPageValid, updateAddField } from "../../../actions"; +import { + formFieldStyles, + modalBasic, + wizardCommon, +} from "../../../../Common/FormComponents/common/styleLibrary"; +import Grid from "@mui/material/Grid"; +import { IResourcesSize } from "../../../ListTenants/types"; +import { AllocableResourcesResponse } from "../../../types"; +import api from "../../../../../../common/api"; +import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import { floor } from "lodash"; + +interface ITenantSizeResourcesProps { + classes: any; + updateAddField: typeof updateAddField; + isPageValid: typeof isPageValid; + nodes: string; + resourcesSize: IResourcesSize; + selectedStorageClass: string; + maxAllocatableResources: AllocableResourcesResponse; + maxCPUsUse: string; + maxMemorySize: string; + + resourcesSpecifyLimit: boolean; + resourcesCPURequestError: string; + resourcesCPURequest: string; + resourcesCPULimitError: string; + resourcesCPULimit: string; + resourcesMemoryRequestError: string; + resourcesMemoryRequest: string; + resourcesMemoryLimitError: string; + resourcesMemoryLimit: string; +} + +const styles = (theme: Theme) => + createStyles({ + compositeFieldContainer: { + display: "flex", + alignItems: "center", + }, + compositeAddOn: { + marginLeft: 10, + "& div": { + marginBottom: 0, + }, + "@media (max-width: 900px)": { + "& div": { + marginTop: 5, + }, + }, + }, + ...formFieldStyles, + ...modalBasic, + ...wizardCommon, + }); + +const TenantSizeResources = ({ + classes, + updateAddField, + isPageValid, + nodes, + + resourcesSize, + selectedStorageClass, + maxAllocatableResources, + maxCPUsUse, + maxMemorySize, + resourcesSpecifyLimit, + resourcesCPURequestError, + resourcesCPURequest, + resourcesCPULimitError, + resourcesCPULimit, + resourcesMemoryRequestError, + resourcesMemoryRequest, + resourcesMemoryLimitError, + resourcesMemoryLimit, +}: ITenantSizeResourcesProps) => { + // Common + const updateField = useCallback( + (field: string, value: any) => { + updateAddField("tenantSize", field, value); + }, + [updateAddField] + ); + + /*Debounce functions*/ + + useEffect(() => { + isPageValid( + "tenantSize", + resourcesMemoryRequestError === "" && + resourcesMemoryLimitError === "" && + resourcesCPURequestError === "" && + resourcesCPULimitError === "" + ); + }, [ + isPageValid, + resourcesMemoryRequestError, + resourcesMemoryLimitError, + resourcesCPURequestError, + resourcesCPULimitError, + ]); + + /*End debounce functions*/ + + /*Calculate Allocation*/ + useEffect(() => { + // Get allocatable Resources + api + .invoke("GET", `api/v1/cluster/allocatable-resources?num_nodes=${nodes}`) + .then((res: AllocableResourcesResponse) => { + updateField("maxAllocatableResources", res); + + const maxAllocatableResources = res; + + const memoryExists = get( + maxAllocatableResources, + "min_allocatable_mem", + false + ); + + const cpuExists = get( + maxAllocatableResources, + "min_allocatable_cpu", + false + ); + + if (memoryExists === false || cpuExists === false) { + updateField("cpuToUse", 0); + + updateField("maxMemorySize", ""); + updateField("maxCPUsUse", ""); + + return; + } + + const maxMemory = floor( + res.mem_priority.max_allocatable_mem / 1024 / 1024 / 1024 + ); + // We default to Best CPU Configuration + updateField("maxMemorySize", maxMemory.toString()); + updateField( + "maxCPUsUse", + res.cpu_priority.max_allocatable_cpu.toString() + ); + + const maxAllocatableCPU = get( + maxAllocatableResources, + "cpu_priority.max_allocatable_cpu", + 0 + ); + + const baseCpuUse = Math.max(1, floor(maxAllocatableCPU / 2)); + updateField("resourcesCPURequest", baseCpuUse); + + const baseMemoryUse = Math.max(2, floor(maxMemory / 2)); + updateField("resourcesMemoryRequest", baseMemoryUse); + }) + .catch((err: any) => { + updateField("maxMemorySize", 0); + updateField("resourcesCPURequest", ""); + + console.error(err); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [nodes, updateField]); + + /*Calculate Allocation End*/ + + return ( + + +
+

Resources

+ + You may specify the amount of CPU and Memory that MinIO servers + should reserve on each node. + +
+
+ {resourcesSize.error !== "" && ( + +
{resourcesSize.error}
+
+ )} + + + { + let value = parseInt(e.target.value); + if (e.target.value === "") { + updateField("resourcesCPURequestError", ""); + } else if (isNaN(value)) { + updateField("resourcesCPURequestError", "Invalid number"); + } else if (value > parseInt(maxCPUsUse)) { + updateField( + "resourcesCPURequestError", + `Request exceeds available cores (${maxCPUsUse})` + ); + } else if (e.target.validity.valid) { + updateField("resourcesCPURequestError", ""); + } else { + updateField("resourcesCPURequestError", "Invalid configuration"); + } + updateField("resourcesCPURequest", e.target.value); + }} + value={resourcesCPURequest} + disabled={selectedStorageClass === ""} + max={maxCPUsUse} + error={resourcesCPURequestError} + pattern={"[0-9]*"} + /> + + + + ) => { + let value = parseInt(e.target.value); + if (e.target.value === "") { + updateField("resourcesMemoryRequestError", ""); + } else if (isNaN(value)) { + updateField("resourcesMemoryRequestError", "Invalid number"); + } else if (value > parseInt(maxMemorySize)) { + updateField( + "resourcesMemoryRequestError", + `Request exceeds available memory across ${nodes} nodes (${maxMemorySize}Gi)` + ); + } else if (value < 2) { + updateField( + "resourcesMemoryRequestError", + "At least 2Gi must be requested" + ); + } else if (e.target.validity.valid) { + updateField("resourcesMemoryRequestError", ""); + } else { + updateField( + "resourcesMemoryRequestError", + "Invalid configuration" + ); + } + updateField("resourcesMemoryRequest", e.target.value); + }} + label="Memory Request [Gi]" + value={resourcesMemoryRequest} + disabled={selectedStorageClass === ""} + error={resourcesMemoryRequestError} + pattern={"[0-9]*"} + /> + + + + { + const targetD = e.target; + const checked = targetD.checked; + + updateField("resourcesSpecifyLimit", checked); + }} + label={"Specify Limit"} + /> + + + {resourcesSpecifyLimit && ( + + + { + let value = parseInt(e.target.value); + if (e.target.value === "") { + updateField("resourcesCPULimitError", ""); + } else if (isNaN(value)) { + updateField("resourcesCPULimitError", "Invalid number"); + } else if (e.target.validity.valid) { + updateField("resourcesCPULimitError", ""); + } else { + updateField( + "resourcesCPULimitError", + "Invalid configuration" + ); + } + updateField("resourcesCPULimit", e.target.value); + }} + value={resourcesCPULimit} + disabled={selectedStorageClass === ""} + max={maxCPUsUse} + error={resourcesCPULimitError} + pattern={"[0-9]*"} + /> + + + + ) => { + let value = parseInt(e.target.value); + console.log("value", value); + if (e.target.value === "") { + updateField("resourcesMemoryLimitError", ""); + } else if (isNaN(value)) { + updateField("resourcesMemoryLimitError", "Invalid number"); + } else if (e.target.validity.valid) { + updateField("resourcesMemoryLimitError", ""); + } else { + updateField( + "resourcesMemoryLimitError", + "Invalid configuration" + ); + } + updateField("resourcesMemoryLimit", e.target.value); + }} + label="Memory Limit [Gi]" + value={resourcesMemoryLimit} + disabled={selectedStorageClass === ""} + error={resourcesMemoryLimitError} + pattern={"[0-9]*"} + /> + + + )} +
+ ); +}; + +const mapState = (state: AppState) => ({ + nodes: state.tenants.createTenant.fields.tenantSize.nodes, + + resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize, + selectedStorageClass: + state.tenants.createTenant.fields.nameTenant.selectedStorageClass, + maxAllocatableResources: + state.tenants.createTenant.fields.tenantSize.maxAllocatableResources, + maxCPUsUse: state.tenants.createTenant.fields.tenantSize.maxCPUsUse, + maxMemorySize: state.tenants.createTenant.fields.tenantSize.maxMemorySize, + + resourcesSpecifyLimit: + state.tenants.createTenant.fields.tenantSize.resourcesSpecifyLimit, + + resourcesCPURequestError: + state.tenants.createTenant.fields.tenantSize.resourcesCPURequestError, + resourcesCPURequest: + state.tenants.createTenant.fields.tenantSize.resourcesCPURequest, + resourcesCPULimitError: + state.tenants.createTenant.fields.tenantSize.resourcesCPULimitError, + resourcesCPULimit: + state.tenants.createTenant.fields.tenantSize.resourcesCPULimit, + + resourcesMemoryRequestError: + state.tenants.createTenant.fields.tenantSize.resourcesMemoryRequestError, + resourcesMemoryRequest: + state.tenants.createTenant.fields.tenantSize.resourcesMemoryRequest, + resourcesMemoryLimitError: + state.tenants.createTenant.fields.tenantSize.resourcesMemoryLimitError, + resourcesMemoryLimit: + state.tenants.createTenant.fields.tenantSize.resourcesMemoryLimit, +}); + +const connector = connect(mapState, { + updateAddField, + isPageValid, +}); + +export default withStyles(styles)(connector(TenantSizeResources)); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/utils.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/utils.tsx new file mode 100644 index 000000000..1ab515580 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/TenantResources/utils.tsx @@ -0,0 +1,84 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React from "react"; +import { Opts } from "../../../ListTenants/utils"; +import TenantSizeMK from "./TenantSizeMK"; + +export enum IMkEnvs { + "aws", + "azure", + "gcs", + "default", + undefined, +} + +export interface IDriveSizing { + driveSize: string; + sizeUnit: string; +} + +export interface IntegrationConfiguration { + typeSelection: string; + storageClass: string; + CPU: number; + memory: number; + drivesPerServer: number; + driveSize: IDriveSizing; +} + +export const AWSStorageTypes: Opts[] = [ + { label: "NVME", value: "nvme" }, + { label: "HDD", value: "hdd" }, +]; + +export const resourcesConfigurations = { + "mp-mode-aws": IMkEnvs.aws, + "mp-mode-azure": IMkEnvs.azure, + "mp-mode-gcs": IMkEnvs.gcs, +}; + +export const AWSConfigurations: IntegrationConfiguration[] = [ + { + typeSelection: "nvme", + storageClass: "nvme-i3en-12xlarge", + CPU: 48, + memory: 384, + driveSize: { driveSize: "7500", sizeUnit: "Gi" }, + drivesPerServer: 4, + }, + { + typeSelection: "hdd", + storageClass: "hdd-d3en-12xlarge", + CPU: 8, + memory: 32, + driveSize: { driveSize: "12.7", sizeUnit: "Ti" }, + drivesPerServer: 4, + }, +]; + +export const mkPanelConfigurations = { + [IMkEnvs.aws]: { + variantSelectorLabel: "Storage Type", + variantSelectorValues: AWSStorageTypes, + configurations: AWSConfigurations, + sizingComponent: , + }, + [IMkEnvs.azure]: {}, + [IMkEnvs.gcs]: {}, + [IMkEnvs.default]: {}, + [IMkEnvs.undefined]: {}, +}; diff --git a/portal-ui/src/screens/Console/Tenants/actions.ts b/portal-ui/src/screens/Console/Tenants/actions.ts index 49450cde0..017599b99 100644 --- a/portal-ui/src/screens/Console/Tenants/actions.ts +++ b/portal-ui/src/screens/Console/Tenants/actions.ts @@ -17,32 +17,31 @@ import { ITenant } from "./ListTenants/types"; import { Opts } from "./ListTenants/utils"; import { - ADD_TENANT_SET_ADVANCED_MODE, + ADD_TENANT_ADD_CA_KEYPAIR, + ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR, + ADD_TENANT_ADD_CONSOLE_CERT, + ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR, + ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR, + ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR, + ADD_TENANT_ADD_MINIO_KEYPAIR, + ADD_TENANT_DELETE_CA_KEYPAIR, + ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR, + ADD_TENANT_DELETE_MINIO_KEYPAIR, + ADD_TENANT_ENCRYPTION_CLIENT_CERT, + ADD_TENANT_ENCRYPTION_GEMALTO_CA, + ADD_TENANT_ENCRYPTION_SERVER_CERT, + ADD_TENANT_ENCRYPTION_VAULT_CA, + ADD_TENANT_ENCRYPTION_VAULT_CERT, + ADD_TENANT_RESET_FORM, ADD_TENANT_SET_CURRENT_PAGE, - ADD_TENANT_UPDATE_FIELD, + ADD_TENANT_SET_LIMIT_SIZE, ADD_TENANT_SET_PAGE_VALID, ADD_TENANT_SET_STORAGE_CLASSES_LIST, - ADD_TENANT_SET_LIMIT_SIZE, - ADD_TENANT_ADD_CA_KEYPAIR, - ADD_TENANT_DELETE_CA_KEYPAIR, - ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR, - ADD_TENANT_ADD_MINIO_KEYPAIR, - ADD_TENANT_DELETE_MINIO_KEYPAIR, - ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR, - ADD_TENANT_ADD_CONSOLE_CERT, - ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR, - ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR, - ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR, - ADD_TENANT_ENCRYPTION_SERVER_CERT, - ADD_TENANT_ENCRYPTION_CLIENT_CERT, - ADD_TENANT_ENCRYPTION_VAULT_CERT, - ADD_TENANT_ENCRYPTION_VAULT_CA, - ADD_TENANT_ENCRYPTION_GEMALTO_CA, - ADD_TENANT_RESET_FORM, - TENANT_DETAILS_SET_LOADING, - TENANT_DETAILS_SET_TENANT, + ADD_TENANT_UPDATE_FIELD, TENANT_DETAILS_SET_CURRENT_TENANT, + TENANT_DETAILS_SET_LOADING, TENANT_DETAILS_SET_TAB, + TENANT_DETAILS_SET_TENANT, } from "./types"; // Basic actions @@ -53,13 +52,6 @@ export const setWizardPage = (page: number) => { }; }; -export const setAdvancedMode = (state: boolean) => { - return { - type: ADD_TENANT_SET_ADVANCED_MODE, - state, - }; -}; - export const updateAddField = ( pageName: string, fieldName: string, diff --git a/portal-ui/src/screens/Console/Tenants/reducer.ts b/portal-ui/src/screens/Console/Tenants/reducer.ts index 827bdadaa..3dd0a97d8 100644 --- a/portal-ui/src/screens/Console/Tenants/reducer.ts +++ b/portal-ui/src/screens/Console/Tenants/reducer.ts @@ -16,34 +16,33 @@ import has from "lodash/has"; import get from "lodash/get"; import { - TenantsManagementTypes, - ITenantState, + ADD_TENANT_ADD_CA_KEYPAIR, + ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR, + ADD_TENANT_ADD_CONSOLE_CERT, + ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR, + ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR, + ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR, + ADD_TENANT_ADD_MINIO_KEYPAIR, + ADD_TENANT_DELETE_CA_KEYPAIR, + ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR, + ADD_TENANT_DELETE_MINIO_KEYPAIR, + ADD_TENANT_ENCRYPTION_CLIENT_CERT, + ADD_TENANT_ENCRYPTION_GEMALTO_CA, + ADD_TENANT_ENCRYPTION_SERVER_CERT, + ADD_TENANT_ENCRYPTION_VAULT_CA, + ADD_TENANT_ENCRYPTION_VAULT_CERT, + ADD_TENANT_RESET_FORM, ADD_TENANT_SET_CURRENT_PAGE, - ADD_TENANT_SET_ADVANCED_MODE, - ADD_TENANT_UPDATE_FIELD, + ADD_TENANT_SET_LIMIT_SIZE, ADD_TENANT_SET_PAGE_VALID, ADD_TENANT_SET_STORAGE_CLASSES_LIST, - ADD_TENANT_ADD_MINIO_KEYPAIR, - ADD_TENANT_DELETE_MINIO_KEYPAIR, - ADD_TENANT_ADD_CA_KEYPAIR, - ADD_TENANT_ADD_FILE_TO_CA_KEYPAIR, - ADD_TENANT_DELETE_CA_KEYPAIR, - ADD_TENANT_ADD_CONSOLE_CERT, - ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR, - ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR, - ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR, - ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR, - ADD_TENANT_ENCRYPTION_SERVER_CERT, - ADD_TENANT_ENCRYPTION_CLIENT_CERT, - ADD_TENANT_ENCRYPTION_VAULT_CERT, - ADD_TENANT_ENCRYPTION_VAULT_CA, - ADD_TENANT_ENCRYPTION_GEMALTO_CA, - ADD_TENANT_RESET_FORM, - ADD_TENANT_SET_LIMIT_SIZE, - TENANT_DETAILS_SET_LOADING, + ADD_TENANT_UPDATE_FIELD, + ITenantState, TENANT_DETAILS_SET_CURRENT_TENANT, - TENANT_DETAILS_SET_TENANT, + TENANT_DETAILS_SET_LOADING, TENANT_DETAILS_SET_TAB, + TENANT_DETAILS_SET_TENANT, + TenantsManagementTypes, } from "./types"; import { KeyPair } from "./ListTenants/utils"; import { getRandomString } from "./utils"; @@ -61,7 +60,6 @@ const initialState: ITenantState = { "security", "encryption", ], - advancedModeOn: false, storageClasses: [], limitSize: {}, fields: { @@ -69,6 +67,7 @@ const initialState: ITenantState = { tenantName: "", namespace: "", selectedStorageClass: "", + selectedStorageType: "", }, configure: { customImage: true, @@ -90,8 +89,8 @@ const initialState: ITenantState = { logSearchPostgresInitImage: "", prometheusVolumeSize: "5", prometheusSizeFactor: "Gi", - logSearchSelectedStorageClass: "", - prometheusSelectedStorageClass: "", + logSearchSelectedStorageClass: "default", + prometheusSelectedStorageClass: "default", prometheusImage: "", prometheusSidecarImage: "", prometheusInitImage: "", @@ -200,8 +199,17 @@ const initialState: ITenantState = { ecParity: "", ecParityChoices: [], cleanECChoices: [], - maxAllocableMemo: 0, cpuToUse: "0", + // resource request + resourcesSpecifyLimit: false, + resourcesCPURequestError: "", + resourcesCPURequest: "", + resourcesCPULimitError: "", + resourcesCPULimit: "", + resourcesMemoryRequestError: "", + resourcesMemoryRequest: "", + resourcesMemoryLimitError: "", + resourcesMemoryLimit: "", resourcesSize: { error: "", memoryRequest: 0, @@ -214,7 +222,6 @@ const initialState: ITenantState = { nodes: 0, persistentVolumes: 0, disks: 0, - volumePerDisk: 0, }, ecParityCalc: { error: 0, @@ -239,6 +246,14 @@ const initialState: ITenantState = { }, maxCPUsUse: "0", maxMemorySize: "0", + integrationSelection: { + driveSize: { driveSize: "0", sizeUnit: "B" }, + CPU: 0, + typeSelection: "", + memory: 0, + drivesPerServer: 0, + storageClass: "", + }, }, affinity: { nodeSelectorLabels: "", @@ -338,10 +353,7 @@ export function tenantsReducer( newState.createTenant.page = action.page; return { ...newState }; - case ADD_TENANT_SET_ADVANCED_MODE: - newState.createTenant.advancedModeOn = action.state; - return { ...newState }; case ADD_TENANT_UPDATE_FIELD: if ( has(newState.createTenant.fields, `${action.pageName}.${action.field}`) @@ -599,7 +611,6 @@ export function tenantsReducer( "security", "encryption", ], - advancedModeOn: false, storageClasses: [], limitSize: {}, fields: { @@ -607,6 +618,7 @@ export function tenantsReducer( tenantName: "", namespace: "", selectedStorageClass: "", + selectedStorageType: "", }, configure: { customImage: false, @@ -622,14 +634,14 @@ export function tenantsReducer( prometheusCustom: false, logSearchVolumeSize: "5", logSearchSizeFactor: "Gi", - logSearchSelectedStorageClass: "", + logSearchSelectedStorageClass: "default", logSearchImage: "", kesImage: "", logSearchPostgresImage: "", logSearchPostgresInitImage: "", prometheusVolumeSize: "5", prometheusSizeFactor: "Gi", - prometheusSelectedStorageClass: "", + prometheusSelectedStorageClass: "default", prometheusImage: "", prometheusSidecarImage: "", prometheusInitImage: "", @@ -738,13 +750,11 @@ export function tenantsReducer( ecParity: "", ecParityChoices: [], cleanECChoices: [], - maxAllocableMemo: 0, distribution: { error: "", nodes: 0, persistentVolumes: 0, disks: 0, - volumePerDisk: 0, }, ecParityCalc: { error: 0, @@ -756,6 +766,16 @@ export function tenantsReducer( }, limitSize: {}, cpuToUse: "0", + // resource request + resourcesSpecifyLimit: false, + resourcesCPURequestError: "", + resourcesCPURequest: "", + resourcesCPULimitError: "", + resourcesCPULimit: "", + resourcesMemoryRequestError: "", + resourcesMemoryRequest: "", + resourcesMemoryLimitError: "", + resourcesMemoryLimit: "", resourcesSize: { error: "", memoryRequest: 0, @@ -777,6 +797,14 @@ export function tenantsReducer( }, maxCPUsUse: "0", maxMemorySize: "0", + integrationSelection: { + driveSize: { driveSize: "0", sizeUnit: "B" }, + CPU: 0, + typeSelection: "", + memory: 0, + drivesPerServer: 0, + storageClass: "", + }, }, affinity: { nodeSelectorLabels: "", diff --git a/portal-ui/src/screens/Console/Tenants/types.ts b/portal-ui/src/screens/Console/Tenants/types.ts index af0165eb4..2f8fce72b 100644 --- a/portal-ui/src/screens/Console/Tenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/types.ts @@ -15,8 +15,9 @@ // along with this program. If not, see . import { IErasureCodeCalc } from "../../../common/types"; -import { ITenant, IResourcesSize } from "./ListTenants/types"; +import { IResourcesSize, ITenant } from "./ListTenants/types"; import { KeyPair, Opts } from "./ListTenants/utils"; +import { IntegrationConfiguration } from "./AddTenant/Steps/TenantResources/utils"; export const ADD_TENANT_SET_CURRENT_PAGE = "ADD_TENANT/SET_CURRENT_PAGE"; export const ADD_TENANT_SET_ADVANCED_MODE = "ADD_TENANT/SET_ADVANCED_MODE"; @@ -64,6 +65,7 @@ export const TENANT_DETAILS_SET_CURRENT_TENANT = "TENANT_DETAILS/SET_CURRENT_TENANT"; export const TENANT_DETAILS_SET_TENANT = "TENANT_DETAILS/SET_TENANT"; export const TENANT_DETAILS_SET_TAB = "TENANT_DETAILS/SET_TAB"; + export interface ICertificateInfo { name: string; serialNumber: string; @@ -86,7 +88,6 @@ export interface ITenantSecurityResponse { export interface ICreateTenant { page: number; validPages: string[]; - advancedModeOn: boolean; storageClasses: Opts[]; limitSize: any; fields: IFieldStore; @@ -119,6 +120,7 @@ export interface INameTenantFields { tenantName: string; namespace: string; selectedStorageClass: string; + selectedStorageType: string; } export interface ISecurityContext { @@ -237,7 +239,6 @@ export interface ITenantSizeFields { ecParity: string; ecParityChoices: Opts[]; cleanECChoices: string[]; - maxAllocableMemo: number; resourcesSize: IResourcesSize; distribution: any; ecParityCalc: IErasureCodeCalc; @@ -246,6 +247,19 @@ export interface ITenantSizeFields { maxAllocatableResources: AllocableResourcesResponse; maxCPUsUse: string; maxMemorySize: string; + integrationSelection: IntegrationConfiguration; + + resourcesSpecifyLimit: boolean; + + resourcesCPURequestError: string; + resourcesCPURequest: string; + resourcesCPULimitError: string; + resourcesCPULimit: string; + + resourcesMemoryRequestError: string; + resourcesMemoryRequest: string; + resourcesMemoryLimitError: string; + resourcesMemoryLimit: string; } export interface ITenantAffinity { @@ -289,11 +303,6 @@ interface SetTenantWizardPage { page: number; } -interface SetAdvancedMode { - type: typeof ADD_TENANT_SET_ADVANCED_MODE; - state: boolean; -} - interface UpdateATField { type: typeof ADD_TENANT_UPDATE_FIELD; pageName: keyof IFieldStore; @@ -333,6 +342,7 @@ interface DeleteMinioKeyPair { type: typeof ADD_TENANT_DELETE_MINIO_KEYPAIR; id: string; } + interface AddCAKeyPair { type: typeof ADD_TENANT_ADD_CA_KEYPAIR; } @@ -349,6 +359,7 @@ interface DeleteCAKeyPair { type: typeof ADD_TENANT_DELETE_CA_KEYPAIR; id: string; } + interface AddConsoleCAKeyPair { type: typeof ADD_TENANT_ADD_CONSOLE_CA_KEYPAIR; } @@ -436,7 +447,6 @@ export type FieldsToHandle = INameTenantFields; export type TenantsManagementTypes = | SetTenantWizardPage - | SetAdvancedMode | UpdateATField | SetPageValid | SetStorageClassesList diff --git a/swagger-operator.yml b/swagger-operator.yml index f9bc8c174..e37724871 100644 --- a/swagger-operator.yml +++ b/swagger-operator.yml @@ -998,6 +998,10 @@ definitions: operatorSessionResponse: type: object properties: + features: + type: array + items: + type: string status: type: string enum: [ok]