diff --git a/portal-ui/src/common/types.ts b/portal-ui/src/common/types.ts index db1fd890a..75a50efb8 100644 --- a/portal-ui/src/common/types.ts +++ b/portal-ui/src/common/types.ts @@ -134,10 +134,21 @@ export interface IMatchExpressionItem { values: string[]; } +export enum ITolerationEffect { + "NoSchedule" = "NoSchedule", + "PreferNoSchedule" = "PreferNoSchedule", + "NoExecute" = "NoExecute", +} + +export enum ITolerationOperator { + "Equal" = "Equal", + "Exists" = "Exists", +} + export interface ITolerationModel { - effect: string; + effect: ITolerationEffect; key: string; - operator: string; + operator: ITolerationOperator; value?: string; tolerationSeconds?: ITolerationSeconds; } @@ -194,6 +205,7 @@ export interface IGCPSecretManager { endpoint?: string; credentials?: IGCPCredentials; } + export interface IGCPConfig { secretmanager: IGCPSecretManager; } @@ -203,6 +215,7 @@ export interface IAzureCredentials { client_id: string; client_secret: string; } + export interface IAzureKeyVault { endpoint: string; credentials?: IAzureCredentials; diff --git a/portal-ui/src/screens/Console/Common/TolerationSelector/TolerationSelector.tsx b/portal-ui/src/screens/Console/Common/TolerationSelector/TolerationSelector.tsx new file mode 100644 index 000000000..975108cc8 --- /dev/null +++ b/portal-ui/src/screens/Console/Common/TolerationSelector/TolerationSelector.tsx @@ -0,0 +1,222 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 { + ITolerationEffect, + ITolerationOperator, +} from "../../../../common/types"; +import SelectWrapper, { + selectorTypes, +} from "../FormComponents/SelectWrapper/SelectWrapper"; +import { Grid, SelectChangeEvent } from "@mui/material"; +import InputBoxWrapper from "../FormComponents/InputBoxWrapper/InputBoxWrapper"; +import InputUnitMenu from "../FormComponents/InputUnitMenu/InputUnitMenu"; +import withStyles from "@mui/styles/withStyles"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; + +interface ITolerationSelector { + effect: ITolerationEffect; + onEffectChange: (value: ITolerationEffect) => void; + tolerationKey: string; + onTolerationKeyChange: (value: string) => void; + operator: ITolerationOperator; + onOperatorChange: (value: ITolerationOperator) => void; + value?: string; + onValueChange: (value: string) => void; + tolerationSeconds?: number; + onSecondsChange: (value: number) => void; + index: number; + classes: any; +} + +const styles = (theme: Theme) => + createStyles({ + labelsStyle: { + fontSize: 18, + fontWeight: "bold", + color: "#AEAEAE", + display: "flex", + alignItems: "center", + justifyContent: "center", + maxWidth: 45, + marginRight: 10, + }, + fieldsetStyle: { + border: "1px solid #EAEAEA", + borderRadius: 2, + padding: 10, + marginBottom: 15, + }, + firstLevel: { + marginBottom: 10, + }, + fieldContainer: { + marginRight: 10, + }, + legendStyle: { + fontSize: 12, + color: "#696969", + fontWeight: "bold", + }, + }); + +const TolerationSelector = ({ + effect, + onEffectChange, + tolerationKey, + onTolerationKeyChange, + operator, + onOperatorChange, + value, + onValueChange, + tolerationSeconds, + onSecondsChange, + index, + classes, +}: ITolerationSelector) => { + const operatorOptions: selectorTypes[] = []; + const effectOptions: selectorTypes[] = []; + + for (let operator in ITolerationOperator) { + operatorOptions.push({ + value: operator, + label: operator, + }); + } + + for (let effect in ITolerationEffect) { + effectOptions.push({ + value: effect, + label: effect, + }); + } + + return ( + +
+ Toleration {index + 1} + + + + If + + + { + onTolerationKeyChange(e.target.value); + }} + index={index} + placeholder={"Toleration Key"} + /> + + {ITolerationOperator[operator] === ITolerationOperator.Equal && ( + + is + + )} + + ) => { + onOperatorChange( + ITolerationOperator[e.target.value as ITolerationOperator] + ); + }} + id={`operator-${index}`} + name="operator" + label={""} + value={ITolerationOperator[operator]} + options={operatorOptions} + /> + + {ITolerationOperator[operator] === ITolerationOperator.Equal && ( + + to + + )} + {ITolerationOperator[operator] === ITolerationOperator.Equal && ( + + { + onValueChange(e.target.value); + }} + index={index} + placeholder={"Toleration Value"} + /> + + )} + + + + then + + + ) => { + onEffectChange( + ITolerationEffect[e.target.value as ITolerationEffect] + ); + }} + id={`effects-${index}`} + name="effects" + label={""} + value={ITolerationEffect[effect]} + options={effectOptions} + /> + + + after + + + { + if (e.target.validity.valid) { + onSecondsChange(parseInt(e.target.value)); + } + }} + index={index} + pattern={"[0-9]*"} + overlayObject={ + + } + /> + + + +
+
+ ); +}; + +export default withStyles(styles)(TolerationSelector); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx index 39fecdb90..c6f03bc6a 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx @@ -33,7 +33,11 @@ import { generatePoolName, getBytes } from "../../../../common/utils"; import GenericWizard from "../../Common/GenericWizard/GenericWizard"; import { IWizardElement } from "../../Common/GenericWizard/types"; import { NewServiceAccount } from "../../Common/CredentialsPrompt/types"; -import { ErrorResponseHandler, ITenantCreator } from "../../../../common/types"; +import { + ErrorResponseHandler, + ITenantCreator, + ITolerationModel, +} from "../../../../common/types"; import { KeyPair } from "../ListTenants/utils"; import { setErrorSnackMessage } from "../../../../actions"; @@ -73,6 +77,7 @@ interface IAddTenantProps { validPages: string[]; classes: any; features?: string[]; + tolerations: ITolerationModel[]; } const styles = (theme: Theme) => @@ -95,6 +100,7 @@ const AddTenant = ({ setErrorSnackMessage, resetAddTenantForm, features, + tolerations, }: IAddTenantProps) => { // Modals const [showNewCredentials, setShowNewCredentials] = useState(false); @@ -232,6 +238,10 @@ const AddTenant = ({ const kesReplicas = fields.encryption.replicas; if (addSending) { + const tolerationValues = tolerations.filter( + (toleration) => toleration.key.trim() !== "" + ); + const poolName = generatePoolName([]); let affinityObject = {}; @@ -281,6 +291,7 @@ const AddTenant = ({ }, securityContext: tenantCustom ? tenantSecurityContext : null, ...affinityObject, + tolerations: tolerationValues, }, ], erasureCodingParity: parseInt(erasureCode, 10), @@ -847,6 +858,7 @@ const mapState = (state: AppState) => ({ selectedStorageClass: state.tenants.createTenant.fields.nameTenant.selectedStorageClass, features: state.console.session.features, + tolerations: state.tenants.createTenant.tolerations, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx index 67318c738..e72bf4887 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx @@ -21,7 +21,14 @@ import createStyles from "@mui/styles/createStyles"; import withStyles from "@mui/styles/withStyles"; import { Grid, IconButton, Paper, SelectChangeEvent } from "@mui/material"; import { AppState } from "../../../../../store"; -import { isPageValid, setKeyValuePairs, updateAddField } from "../../actions"; +import { + addNewToleration, + isPageValid, + removeToleration, + setKeyValuePairs, + setTolerationInfo, + updateAddField, +} from "../../actions"; import { setModalErrorSnackMessage } from "../../../../../actions"; import { modalBasic, @@ -31,7 +38,10 @@ import { commonFormValidation, IValidation, } from "../../../../../utils/validationFunctions"; -import { ErrorResponseHandler } from "../../../../../common/types"; +import { + ErrorResponseHandler, + ITolerationModel, +} from "../../../../../common/types"; import { LabelKeyPair } from "../../types"; import RadioGroupSelector from "../../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector"; import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; @@ -40,6 +50,7 @@ import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/Inpu import AddIcon from "../../../../../icons/AddIcon"; import RemoveIcon from "../../../../../icons/RemoveIcon"; import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import TolerationSelector from "../../../Common/TolerationSelector/TolerationSelector"; interface IAffinityProps { classes: any; @@ -47,16 +58,22 @@ interface IAffinityProps { nodeSelectorLabels: string; withPodAntiAffinity: boolean; keyValuePairs: LabelKeyPair[]; + tolerations: ITolerationModel[]; setModalErrorSnackMessage: typeof setModalErrorSnackMessage; updateAddField: typeof updateAddField; isPageValid: typeof isPageValid; setKeyValuePairs: typeof setKeyValuePairs; + setTolerationInfo: typeof setTolerationInfo; + addNewToleration: typeof addNewToleration; + removeToleration: typeof removeToleration; } const styles = (theme: Theme) => createStyles({ overlayAction: { marginLeft: 10, + display: "flex", + alignItems: "center", "& svg": { maxWidth: 15, maxHeight: 15, @@ -125,6 +142,10 @@ const Affinity = ({ keyValuePairs, setKeyValuePairs, isPageValid, + tolerations, + setTolerationInfo, + addNewToleration, + removeToleration, }: IAffinityProps) => { const [validationErrors, setValidationErrors] = useState({}); const [loading, setLoading] = useState(true); @@ -223,6 +244,12 @@ const Affinity = ({ setValidationErrors(commonVal); }, [isPageValid, podAffinity, nodeSelectorLabels]); + const updateToleration = (index: number, field: string, value: any) => { + const alterToleration = { ...tolerations[index], [field]: value }; + + setTolerationInfo(index, alterToleration); + }; + return (
@@ -415,6 +442,72 @@ const Affinity = ({ )} + + +

Tolerations

+ + {validationErrors["tolerations"]} + + + {tolerations && + tolerations.map((tol, i) => { + return ( + + { + updateToleration(i, "effect", value); + }} + tolerationKey={tol.key} + onTolerationKeyChange={(value) => { + updateToleration(i, "key", value); + }} + operator={tol.operator} + onOperatorChange={(value) => { + updateToleration(i, "operator", value); + }} + value={tol.value} + onValueChange={(value) => { + updateToleration(i, "value", value); + }} + tolerationSeconds={tol.tolerationSeconds?.seconds || 0} + onSecondsChange={(value) => { + updateToleration(i, "tolerationSeconds", { + seconds: value, + }); + }} + index={i} + /> +
+ + + +
+ +
+ removeToleration(i)} + disabled={tolerations.length <= 1} + > + + +
+
+ ); + })} +
+
+
); }; @@ -427,6 +520,7 @@ const mapState = (state: AppState) => { nodeSelectorLabels: createTenant.fields.affinity.nodeSelectorLabels, withPodAntiAffinity: createTenant.fields.affinity.withPodAntiAffinity, keyValuePairs: createTenant.nodeSelectorPairs, + tolerations: createTenant.tolerations, }; }; @@ -435,6 +529,9 @@ const connector = connect(mapState, { updateAddField, isPageValid, setKeyValuePairs, + setTolerationInfo, + addNewToleration, + removeToleration, }); export default withStyles(styles)(connector(Affinity)); diff --git a/portal-ui/src/screens/Console/Tenants/actions.ts b/portal-ui/src/screens/Console/Tenants/actions.ts index a7abe9b59..e3ed758b7 100644 --- a/portal-ui/src/screens/Console/Tenants/actions.ts +++ b/portal-ui/src/screens/Console/Tenants/actions.ts @@ -44,8 +44,11 @@ import { TENANT_DETAILS_SET_TAB, TENANT_DETAILS_SET_TENANT, ADD_TENANT_SET_KEY_PAIR_VALUE, - LabelKeyPair, + ADD_TENANT_SET_TOLERATION_VALUE, + ADD_TENANT_ADD_NEW_TOLERATION, + LabelKeyPair, ADD_TENANT_REMOVE_TOLERATION_ROW, } from "./types"; +import {ITolerationModel} from "../../../common/types"; // Basic actions export const setWizardPage = (page: number) => { @@ -295,3 +298,24 @@ export const setKeyValuePairs = (newArray: LabelKeyPair[]) => { newArray, }; }; + +export const setTolerationInfo = (index: number, tolerationValue: ITolerationModel) => { + return { + type: ADD_TENANT_SET_TOLERATION_VALUE, + index, + toleration: tolerationValue, + } +}; + +export const addNewToleration = () => { + return { + type: ADD_TENANT_ADD_NEW_TOLERATION, + } +}; + +export const removeToleration = (index: number) => { + return { + type: ADD_TENANT_REMOVE_TOLERATION_ROW, + index + } +}; diff --git a/portal-ui/src/screens/Console/Tenants/reducer.ts b/portal-ui/src/screens/Console/Tenants/reducer.ts index d1cfc00df..841295f26 100644 --- a/portal-ui/src/screens/Console/Tenants/reducer.ts +++ b/portal-ui/src/screens/Console/Tenants/reducer.ts @@ -23,6 +23,7 @@ import { ADD_TENANT_ADD_FILE_TO_CONSOLE_CA_KEYPAIR, ADD_TENANT_ADD_FILE_TO_MINIO_KEYPAIR, ADD_TENANT_ADD_MINIO_KEYPAIR, + ADD_TENANT_ADD_NEW_TOLERATION, ADD_TENANT_DELETE_CA_KEYPAIR, ADD_TENANT_DELETE_CONSOLE_CA_KEYPAIR, ADD_TENANT_DELETE_MINIO_KEYPAIR, @@ -31,13 +32,15 @@ import { ADD_TENANT_ENCRYPTION_SERVER_CERT, ADD_TENANT_ENCRYPTION_VAULT_CA, ADD_TENANT_ENCRYPTION_VAULT_CERT, + ADD_TENANT_REMOVE_TOLERATION_ROW, ADD_TENANT_RESET_FORM, ADD_TENANT_SET_CURRENT_PAGE, ADD_TENANT_SET_KEY_PAIR_VALUE, ADD_TENANT_SET_LIMIT_SIZE, ADD_TENANT_SET_PAGE_VALID, - ADD_TENANT_SET_STORAGE_TYPE, ADD_TENANT_SET_STORAGE_CLASSES_LIST, + ADD_TENANT_SET_STORAGE_TYPE, + ADD_TENANT_SET_TOLERATION_VALUE, ADD_TENANT_UPDATE_FIELD, ITenantState, TENANT_DETAILS_SET_CURRENT_TENANT, @@ -49,6 +52,7 @@ import { import { KeyPair } from "./ListTenants/utils"; import { getRandomString } from "./utils"; import { addTenantSetStorageTypeReducer } from "./reducers/add-tenant-reducer"; +import { ITolerationEffect, ITolerationOperator } from "../../../common/types"; const initialState: ITenantState = { createTenant: { @@ -334,6 +338,15 @@ const initialState: ITenantState = { }, }, nodeSelectorPairs: [{ key: "", value: "" }], + tolerations: [ + { + key: "", + tolerationSeconds: { seconds: 0 }, + value: "", + effect: ITolerationEffect.NoSchedule, + operator: ITolerationOperator.Equal, + }, + ], }, tenantDetails: { currentTenant: "", @@ -886,6 +899,15 @@ export function tenantsReducer( }, }, nodeSelectorPairs: [{ key: "", value: "" }], + tolerations: [ + { + key: "", + tolerationSeconds: { seconds: 0 }, + value: "", + effect: ITolerationEffect.NoSchedule, + operator: ITolerationOperator.Equal, + }, + ], }, }; case ADD_TENANT_SET_KEY_PAIR_VALUE: @@ -939,6 +961,51 @@ export function tenantsReducer( ...newTab, }, }; + case ADD_TENANT_SET_TOLERATION_VALUE: + const newSetTolerationValue = [...state.createTenant.tolerations]; + + newSetTolerationValue[action.index] = action.toleration; + + return { + ...state, + createTenant: { + ...state.createTenant, + tolerations: [...newSetTolerationValue], + }, + }; + case ADD_TENANT_ADD_NEW_TOLERATION: + const newTolerationArray = [ + ...state.createTenant.tolerations, + { + key: "", + tolerationSeconds: { seconds: 0 }, + value: "", + effect: ITolerationEffect.NoSchedule, + operator: ITolerationOperator.Equal, + }, + ]; + return { + ...state, + createTenant: { + ...state.createTenant, + tolerations: [...newTolerationArray], + }, + }; + case ADD_TENANT_REMOVE_TOLERATION_ROW: + const cleanTolerationArray = state.createTenant.tolerations.filter( + (_, index) => index !== action.index + ); + + console.log("action", action.index, cleanTolerationArray) + + return { + ...state, + createTenant: { + ...state.createTenant, + tolerations: [...cleanTolerationArray], + }, + }; + default: return state; } diff --git a/portal-ui/src/screens/Console/Tenants/types.ts b/portal-ui/src/screens/Console/Tenants/types.ts index ac0b6149a..c37e61492 100644 --- a/portal-ui/src/screens/Console/Tenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/types.ts @@ -20,6 +20,7 @@ import { IErasureCodeCalc, IGCPConfig, IGemaltoCredentials, + ITolerationModel, } from "../../../common/types"; import { IResourcesSize, ITenant } from "./ListTenants/types"; import { KeyPair, Opts } from "./ListTenants/utils"; @@ -69,6 +70,12 @@ export const ADD_TENANT_ENCRYPTION_GEMALTO_CA = // Affinity Node Selector KeyPairs export const ADD_TENANT_SET_KEY_PAIR_VALUE = "ADD_TENANT/SET_KEY_PAIR_VALUE"; +// Affinity Tolerations +export const ADD_TENANT_SET_TOLERATION_VALUE = + "ADD_TENANT/SET_TOLERATION_VALUE"; +export const ADD_TENANT_ADD_NEW_TOLERATION = "ADD_TENANT/ADD_NEW_TOLERATION"; +export const ADD_TENANT_REMOVE_TOLERATION_ROW = "ADD_TENANT/REMOVE_TOLERATION_ROW"; + // Tenant Details export const TENANT_DETAILS_SET_LOADING = "TENANT_DETAILS/SET_LOADING"; export const TENANT_DETAILS_SET_CURRENT_TENANT = @@ -130,6 +137,7 @@ export interface IKeysecureConfiguration { credentials: IGemaltoCredentials; tls: IGemaltoTLS; } + export interface IGemaltoConfiguration { keysecure: IKeysecureConfiguration; } @@ -155,6 +163,7 @@ export interface ICreateTenant { fields: IFieldStore; certificates: ICertificatesItems; nodeSelectorPairs: LabelKeyPair[]; + tolerations: ITolerationModel[]; } export interface ICertificatesItems { @@ -520,6 +529,21 @@ interface SetTenantTab { tab: string; } +interface SetTolerationValue { + type: typeof ADD_TENANT_SET_TOLERATION_VALUE; + index: number; + toleration: ITolerationModel; +} + +interface AddNewToleration { + type: typeof ADD_TENANT_ADD_NEW_TOLERATION; +} + +interface RemoveTolerationRow { + type: typeof ADD_TENANT_REMOVE_TOLERATION_ROW; + index: number; +} + export type FieldsToHandle = INameTenantFields; export type TenantsManagementTypes = @@ -549,4 +573,7 @@ export type TenantsManagementTypes = | SetLoadingTenant | SetTenantName | SetTenantDetails - | SetTenantTab; + | SetTenantTab + | SetTolerationValue + | AddNewToleration + | RemoveTolerationRow;