Implemented validation in create tenant form (#177)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -44,6 +44,9 @@ interface InputBoxProps {
|
||||
tooltip?: string;
|
||||
autoComplete?: string;
|
||||
index?: number;
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -52,6 +55,14 @@ const styles = (theme: Theme) =>
|
||||
...tooltipHelper,
|
||||
textBoxContainer: {
|
||||
flexGrow: 1,
|
||||
position: "relative",
|
||||
},
|
||||
errorState: {
|
||||
color: "#b53b4b",
|
||||
fontSize: 14,
|
||||
position: "absolute",
|
||||
top: 7,
|
||||
right: 7,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -66,6 +77,10 @@ const inputStyles = makeStyles((theme: Theme) =>
|
||||
color: "#393939",
|
||||
fontSize: 14,
|
||||
},
|
||||
error: {
|
||||
color: "#b53b4b",
|
||||
boxShadow: "inset 0px 0px 1px 1px #b53b4b",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -92,14 +107,31 @@ const InputBoxWrapper = ({
|
||||
multiline = false,
|
||||
tooltip = "",
|
||||
index = 0,
|
||||
error = "",
|
||||
required = false,
|
||||
placeholder = "",
|
||||
classes,
|
||||
}: InputBoxProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.fieldContainer}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.fieldContainer} ${
|
||||
error !== "" ? classes.errorInField : ""
|
||||
}`}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
<InputLabel
|
||||
htmlFor={id}
|
||||
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||
classes.inputLabel
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
{label}
|
||||
{required ? "*" : ""}
|
||||
</span>
|
||||
{tooltip !== "" && (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Tooltip title={tooltip} placement="top-start">
|
||||
@@ -112,7 +144,6 @@ const InputBoxWrapper = ({
|
||||
|
||||
<div className={classes.textBoxContainer}>
|
||||
<InputField
|
||||
className={classes.boxDesign}
|
||||
id={id}
|
||||
name={name}
|
||||
variant="outlined"
|
||||
@@ -124,6 +155,9 @@ const InputBoxWrapper = ({
|
||||
multiline={multiline}
|
||||
autoComplete={autoComplete}
|
||||
inputProps={{ "data-index": index }}
|
||||
error={error !== ""}
|
||||
helperText={error}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
@@ -33,6 +33,9 @@ export const fieldBasic = {
|
||||
alignItems: "center",
|
||||
},
|
||||
},
|
||||
fieldLabelError: {
|
||||
paddingBottom: 22,
|
||||
},
|
||||
fieldContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -28,6 +28,10 @@ import CheckboxWrapper from "../../Common/FormComponents/CheckboxWrapper/Checkbo
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { k8sfactorForDropdown } from "../../../../common/utils";
|
||||
import ZonesMultiSelector from "./ZonesMultiSelector";
|
||||
import {
|
||||
commonFormValidation,
|
||||
IValidation,
|
||||
} from "../../../../utils/validationFunctions";
|
||||
|
||||
interface IAddTenantProps {
|
||||
open: boolean;
|
||||
@@ -50,6 +54,7 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
sizeFactorContainer: {
|
||||
marginLeft: 8,
|
||||
alignSelf: "flex-start" as const,
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
@@ -81,11 +86,60 @@ const AddTenant = ({
|
||||
const [enableSSL, setEnableSSL] = useState<boolean>(false);
|
||||
const [sizeFactor, setSizeFactor] = useState<string>("Gi");
|
||||
const [storageClasses, setStorageClassesList] = useState<Opts[]>([]);
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
fetchStorageClassList();
|
||||
}, []);
|
||||
|
||||
const validationElements: IValidation[] = [
|
||||
{
|
||||
fieldKey: "tenant-name",
|
||||
required: true,
|
||||
pattern: /^[a-z0-9-]{3,63}$/,
|
||||
customPatternMessage:
|
||||
"Name only can contain lowercase letters, numbers and '-'. Min. Length: 3",
|
||||
value: tenantName,
|
||||
},
|
||||
{
|
||||
fieldKey: "image",
|
||||
required: false,
|
||||
value: imageName,
|
||||
},
|
||||
{
|
||||
fieldKey: "service_name",
|
||||
required: false,
|
||||
value: serviceName,
|
||||
},
|
||||
{
|
||||
fieldKey: "volumes_per_server",
|
||||
required: true,
|
||||
value: volumesPerServer.toString(10),
|
||||
},
|
||||
{
|
||||
fieldKey: "volume_size",
|
||||
required: true,
|
||||
value: volumeConfiguration.size,
|
||||
},
|
||||
{
|
||||
fieldKey: "access_key",
|
||||
required: false,
|
||||
value: accessKey,
|
||||
},
|
||||
{
|
||||
fieldKey: "secret_key",
|
||||
required: false,
|
||||
value: secretKey,
|
||||
},
|
||||
];
|
||||
|
||||
const clearValidationError = (fieldKey: string) => {
|
||||
const newValidationElement = { ...validationErrors };
|
||||
delete newValidationElement[fieldKey];
|
||||
|
||||
setValidationErrors(newValidationElement);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (addSending) {
|
||||
let cleanZones: IZone[] = [];
|
||||
@@ -95,31 +149,42 @@ const AddTenant = ({
|
||||
}
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("POST", `/api/v1/mkube/tenants`, {
|
||||
name: tenantName,
|
||||
service_name: tenantName,
|
||||
image: imageName,
|
||||
enable_ssl: enableSSL,
|
||||
enable_mcs: enableMCS,
|
||||
access_key: accessKey,
|
||||
secret_key: secretKey,
|
||||
volumes_per_server: volumesPerServer,
|
||||
volume_configuration: {
|
||||
size: `${volumeConfiguration.size}${sizeFactor}`,
|
||||
storage_class: volumeConfiguration.storage_class,
|
||||
},
|
||||
zones: cleanZones,
|
||||
})
|
||||
.then(() => {
|
||||
setAddSending(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddSending(false);
|
||||
setAddError(err);
|
||||
});
|
||||
const commonValidation = commonFormValidation(validationElements);
|
||||
|
||||
setValidationErrors(commonValidation);
|
||||
|
||||
console.log(commonValidation);
|
||||
|
||||
if (Object.keys(commonValidation).length === 0) {
|
||||
api
|
||||
.invoke("POST", `/api/v1/mkube/tenants`, {
|
||||
name: tenantName,
|
||||
service_name: tenantName,
|
||||
image: imageName,
|
||||
enable_ssl: enableSSL,
|
||||
enable_mcs: enableMCS,
|
||||
access_key: accessKey,
|
||||
secret_key: secretKey,
|
||||
volumes_per_server: volumesPerServer,
|
||||
volume_configuration: {
|
||||
size: `${volumeConfiguration.size}${sizeFactor}`,
|
||||
storage_class: volumeConfiguration.storage_class,
|
||||
},
|
||||
zones: cleanZones,
|
||||
})
|
||||
.then(() => {
|
||||
setAddSending(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddSending(false);
|
||||
setAddError(err);
|
||||
});
|
||||
} else {
|
||||
setAddSending(false);
|
||||
setAddError("Please fix the errors in the form and try again");
|
||||
}
|
||||
}
|
||||
}, [addSending]);
|
||||
|
||||
@@ -147,6 +212,11 @@ const AddTenant = ({
|
||||
value: s,
|
||||
}))
|
||||
);
|
||||
|
||||
const newStorage = { ...volumeConfiguration };
|
||||
newStorage.storage_class = res[0];
|
||||
|
||||
setVolumeConfiguration(newStorage);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
@@ -191,9 +261,12 @@ const AddTenant = ({
|
||||
name="tenant-name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setTenantName(e.target.value);
|
||||
clearValidationError("tenant-name");
|
||||
}}
|
||||
label="Tenant Name"
|
||||
value={tenantName}
|
||||
required
|
||||
error={validationErrors["tenant-name"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
@@ -202,9 +275,12 @@ const AddTenant = ({
|
||||
name="image"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setImageName(e.target.value);
|
||||
clearValidationError("image");
|
||||
}}
|
||||
label="MinIO Image"
|
||||
value={imageName}
|
||||
error={validationErrors["image"] || ""}
|
||||
placeholder="Eg. minio/minio:RELEASE.2020-05-08T02-40-49Z"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
@@ -213,9 +289,11 @@ const AddTenant = ({
|
||||
name="service_name"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setServiceName(e.target.value);
|
||||
clearValidationError("service_name");
|
||||
}}
|
||||
label="Service Name"
|
||||
value={serviceName}
|
||||
error={validationErrors["service_name"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
@@ -243,9 +321,12 @@ const AddTenant = ({
|
||||
type="number"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setVolumesPerServer(parseInt(e.target.value));
|
||||
clearValidationError("volumes_per_server");
|
||||
}}
|
||||
label="Volumes per Server"
|
||||
value={volumesPerServer.toString(10)}
|
||||
required
|
||||
error={validationErrors["volumes_per_server"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
@@ -256,9 +337,12 @@ const AddTenant = ({
|
||||
name="volume_size"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setVolumeConfig("size", e.target.value);
|
||||
clearValidationError("volume_size");
|
||||
}}
|
||||
label="Size"
|
||||
value={volumeConfiguration.size}
|
||||
required
|
||||
error={validationErrors["volume_size"] || ""}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.sizeFactorContainer}>
|
||||
@@ -304,9 +388,11 @@ const AddTenant = ({
|
||||
name="access_key"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAccessKey(e.target.value);
|
||||
clearValidationError("access_key");
|
||||
}}
|
||||
label="Access Key"
|
||||
value={accessKey}
|
||||
error={validationErrors["access_key"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
@@ -315,9 +401,11 @@ const AddTenant = ({
|
||||
name="secret_key"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSecretKey(e.target.value);
|
||||
clearValidationError("secret_key");
|
||||
}}
|
||||
label="Secret Key"
|
||||
value={secretKey}
|
||||
error={validationErrors["secret_key"] || ""}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
|
||||
44
portal-ui/src/utils/validationFunctions.ts
Normal file
44
portal-ui/src/utils/validationFunctions.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
export interface IValidation {
|
||||
fieldKey: string;
|
||||
required: boolean;
|
||||
pattern?: RegExp;
|
||||
customPatternMessage?: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const commonFormValidation = (fieldsValidate: IValidation[]) => {
|
||||
let returnErrors: any = {};
|
||||
|
||||
fieldsValidate.forEach((field) => {
|
||||
if (field.required && field.value.trim() === "") {
|
||||
returnErrors[field.fieldKey] = "Field cannot be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.pattern && field.customPatternMessage) {
|
||||
const rgx = new RegExp(field.pattern, "g");
|
||||
|
||||
if (!field.value.match(rgx)) {
|
||||
returnErrors[field.fieldKey] = field.customPatternMessage;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return returnErrors;
|
||||
};
|
||||
Reference in New Issue
Block a user