From 3a3a4b2feabc35a167418418ba8268a7f53e4313 Mon Sep 17 00:00:00 2001 From: jinapurapu <65002498+jinapurapu@users.noreply.github.com> Date: Tue, 30 Aug 2022 11:02:10 -0700 Subject: [PATCH] Interactive Bucket Naming Rules component (#2262) --- portal-ui/src/icons/HideTextIcon.tsx | 54 +++++++ portal-ui/src/icons/ShowTextIcon.tsx | 52 ++++++ .../ListBuckets/AddBucket/AddBucket.tsx | 146 +++++++---------- .../ListBuckets/AddBucket/AddBucketName.tsx | 4 +- .../AddBucket/BucketNamingRules.tsx | 150 ++++++++++++++++++ .../ListBuckets/AddBucket/InvalidRule.tsx | 56 +++++++ .../Buckets/ListBuckets/AddBucket/NARule.tsx | 53 +++++++ .../ListBuckets/AddBucket/ValidRule.tsx | 53 +++++++ 8 files changed, 481 insertions(+), 87 deletions(-) create mode 100644 portal-ui/src/icons/HideTextIcon.tsx create mode 100644 portal-ui/src/icons/ShowTextIcon.tsx create mode 100644 portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/BucketNamingRules.tsx create mode 100644 portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/InvalidRule.tsx create mode 100644 portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/NARule.tsx create mode 100644 portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/ValidRule.tsx diff --git a/portal-ui/src/icons/HideTextIcon.tsx b/portal-ui/src/icons/HideTextIcon.tsx new file mode 100644 index 000000000..f1c930842 --- /dev/null +++ b/portal-ui/src/icons/HideTextIcon.tsx @@ -0,0 +1,54 @@ +// 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 * as React from "react"; +import { SVGProps } from "react"; + +const HideTextIcon = (props: SVGProps) => ( + + + + + + + + + + +); + +export default HideTextIcon; diff --git a/portal-ui/src/icons/ShowTextIcon.tsx b/portal-ui/src/icons/ShowTextIcon.tsx new file mode 100644 index 000000000..06822c0de --- /dev/null +++ b/portal-ui/src/icons/ShowTextIcon.tsx @@ -0,0 +1,52 @@ +// 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 * as React from "react"; +import { SVGProps } from "react"; + +const ShowTextIcon = (props: SVGProps) => ( + + + + + + + + +); + +export default ShowTextIcon; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucket.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucket.tsx index 698215595..f5a6c4bfc 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucket.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucket.tsx @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { Fragment, useEffect } from "react"; +import React, { Fragment, useEffect, useState } from "react"; import Grid from "@mui/material/Grid"; -import { Box, Button, LinearProgress } from "@mui/material"; +import { Button, LinearProgress } from "@mui/material"; import { Theme } from "@mui/material/styles"; import { useNavigate } from "react-router-dom"; import createStyles from "@mui/styles/createStyles"; @@ -31,7 +31,10 @@ import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/ import PageHeader from "../../../Common/PageHeader/PageHeader"; import BackLink from "../../../../../common/BackLink"; import { BucketsIcon, InfoIcon } from "../../../../../icons"; - +import { setErrorSnackMessage } from "../../../../../systemSlice"; +import { ErrorResponseHandler } from "../../../../../common/types"; +import { BucketList } from "../../types"; +import api from "../../../../../common/api"; import PageLayout from "../../../Common/Layout/PageLayout"; import InputUnitMenu from "../../../Common/FormComponents/InputUnitMenu/InputUnitMenu"; import FormLayout from "../../../Common/FormLayout"; @@ -54,6 +57,7 @@ import { addBucketAsync } from "./addBucketThunks"; import AddBucketName from "./AddBucketName"; import { IAM_SCOPES } from "../../../../../common/SecureComponent/permissions"; import { hasPermission } from "../../../../../common/SecureComponent"; +import BucketNamingRules from "./BucketNamingRules"; const styles = (theme: Theme) => createStyles({ @@ -108,6 +112,15 @@ const AddBucket = ({ classes }: IsetProps) => { const dispatch = useAppDispatch(); const navigate = useNavigate(); + const validBucketCharacters = new RegExp(`^[a-z0-9.-]*$`); + const ipAddressFormat = new RegExp( + "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(.|$)){4}$" + ); + const bucketName = useSelector((state: AppState) => state.addBucket.name); + const [validationResult, setValidationResult] = useState([]); + const errorList = validationResult.filter((v) => !v); + const hasErrors = errorList.length > 0; + const [records, setRecords] = useState([]); const versioningEnabled = useSelector( (state: AppState) => state.addBucket.versioningEnabled ); @@ -149,6 +162,44 @@ const AddBucket = ({ classes }: IsetProps) => { IAM_SCOPES.S3_PUT_BUCKET_OBJECT_LOCK_CONFIGURATION, ]); + useEffect(() => { + const bucketNameErrors = [ + !(bucketName.length < 3 || bucketName.length > 63), + validBucketCharacters.test(bucketName), + !( + bucketName.includes(".-") || + bucketName.includes("-.") || + bucketName.includes("..") + ), + !ipAddressFormat.test(bucketName), + !bucketName.startsWith("xn--"), + !bucketName.endsWith("-s3alias"), + !records.includes(bucketName), + ]; + setValidationResult(bucketNameErrors); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bucketName]); + + useEffect(() => { + const fetchRecords = () => { + api + .invoke("GET", `/api/v1/buckets`) + .then((res: BucketList) => { + var bucketList: string[] = []; + if (res.buckets != null && res.buckets.length > 0) { + res.buckets.forEach((bucket) => { + bucketList.push(bucket.name); + }); + } + setRecords(bucketList); + }) + .catch((err: ErrorResponseHandler) => { + dispatch(setErrorSnackMessage(err)); + }); + }; + fetchRecords(); + }, [dispatch]); + const resForm = () => { dispatch(resetForm()); }; @@ -212,85 +263,6 @@ const AddBucket = ({ classes }: IsetProps) => { )}

- Bucket Naming Rules - - -
- Bucket names must be between 3 (min) and 63 (max) - characters long. -
-
- -
- Bucket names can consist only of lowercase letters, - numbers, dots (.), and hyphens (-). -
-
- -
- Bucket names must not contain two adjacent periods. -
-
- -
- Bucket names must not be formatted as an IP address (for - example, 192.168.5.4). -
-
- -
- Bucket names must not start with the prefix xn--. -
-
- -
- Bucket names must not end with the suffix -s3alias. This - suffix is reserved for access point alias names. -
-
- -
- Bucket names must be unique within a partition. -
-
-
} /> @@ -306,7 +278,10 @@ const AddBucket = ({ classes }: IsetProps) => { > - + + + + Features @@ -330,8 +305,7 @@ const AddBucket = ({ classes }: IsetProps) => { )} - - + {siteReplicationInfo.enabled && (
@@ -498,7 +472,7 @@ const AddBucket = ({ classes }: IsetProps) => { type="submit" variant="contained" color="primary" - disabled={addLoading || invalidFields.length > 0} + disabled={addLoading || invalidFields.length > 0 || hasErrors} > Create Bucket diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucketName.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucketName.tsx index 969f72f3c..c39a8396d 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucketName.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/AddBucketName.tsx @@ -20,7 +20,7 @@ import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/Inpu import { useSelector } from "react-redux"; import { AppState, useAppDispatch } from "../../../../../store"; -const AddBucketName = () => { +const AddBucketName = ({ hasErrors }: { hasErrors: boolean }) => { const dispatch = useAppDispatch(); const bucketName = useSelector((state: AppState) => state.addBucket.name); @@ -28,12 +28,14 @@ const AddBucketName = () => { ) => { dispatch(setName(event.target.value)); }} label="Bucket Name" value={bucketName} + required /> ); }; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/BucketNamingRules.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/BucketNamingRules.tsx new file mode 100644 index 000000000..856a3c11d --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/BucketNamingRules.tsx @@ -0,0 +1,150 @@ +// 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, { Fragment, useState } from "react"; +import Grid from "@mui/material/Grid"; +import { Button, LinearProgress } from "@mui/material"; +import { AppState } from "../../../../../store"; +import { useSelector } from "react-redux"; +import ShowTextIcon from "../../../../../icons/ShowTextIcon"; +import HideTextIcon from "../../../../../icons/HideTextIcon"; + +import ValidRule from "./ValidRule"; +import InvalidRule from "./InvalidRule"; +import NARule from "./NARule"; + +const BucketNamingRules = ({ errorList }: { errorList: boolean[] }) => { + const lengthRuleText = + "Bucket names must be between 3 (min) and 63 (max) characters long."; + const characterRuleText = + "Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-)."; + const periodRuleText = + "Bucket names must not contain two adjacent periods, or a period adjacent to a hyphen."; + const ipRuleText = + "Bucket names must not be formatted as an IP address (for example, 192.168.5.4)."; + const prefixRuleText = "Bucket names must not start with the prefix xn--."; + const suffixRuleText = + "Bucket names must not end with the suffix -s3alias. This suffix is reserved for access point alias names."; + const uniqueRuleText = "Bucket names must be unique within a partition."; + + const bucketName = useSelector((state: AppState) => state.addBucket.name); + + const [showNamingRules, setShowNamingRules] = useState(false); + + const addLoading = useSelector((state: AppState) => state.addBucket.loading); + + const [ + lengthRule, + validCharacters, + noAdjacentPeriods, + notIPFormat, + noPrefix, + noSuffix, + uniqueName, + ] = errorList; + + return ( + + + {showNamingRules ? ( + + {" "} + Hide Bucket Naming Rules{" "} + + ) : ( + + View Bucket Naming Rules + + )} + + {showNamingRules && ( + + + {bucketName.length === 0 ? ( + + ) : lengthRule ? ( + + ) : ( + + )} + {bucketName.length === 0 ? ( + + ) : validCharacters ? ( + + ) : ( + + )} + {bucketName.length === 0 ? ( + + ) : noAdjacentPeriods ? ( + + ) : ( + + )} + {bucketName.length === 0 ? ( + + ) : notIPFormat ? ( + + ) : ( + + )} + + + {bucketName.length === 0 ? ( + + ) : noPrefix ? ( + + ) : ( + + )} + + {bucketName.length === 0 ? ( + + ) : noSuffix ? ( + + ) : ( + + )} + + {bucketName.length === 0 ? ( + + ) : uniqueName ? ( + + ) : ( + + )} + + + )} + + {addLoading && ( + + + + )} + + ); +}; + +export default BucketNamingRules; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/InvalidRule.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/InvalidRule.tsx new file mode 100644 index 000000000..04222d571 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/InvalidRule.tsx @@ -0,0 +1,56 @@ +// 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, { Fragment } from "react"; +import Grid from "@mui/material/Grid"; +import { ConfirmDeleteIcon } from "../../../../../icons"; + +interface IInvalidRule { + ruleText: string; +} + +const InvalidRule = ({ ruleText }: IInvalidRule) => { + return ( + + + + + + + {ruleText} + + + + ); +}; + +export default InvalidRule; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/NARule.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/NARule.tsx new file mode 100644 index 000000000..dfafb0579 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/NARule.tsx @@ -0,0 +1,53 @@ +// 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, { Fragment } from "react"; +import Grid from "@mui/material/Grid"; +import { CircleIcon } from "../../../../../icons"; + +interface INARule { + ruleText: string; +} + +const NARule = ({ ruleText }: INARule) => { + return ( + + + + + + + {ruleText} + + + + ); +}; + +export default NARule; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/ValidRule.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/ValidRule.tsx new file mode 100644 index 000000000..fc0cd1453 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket/ValidRule.tsx @@ -0,0 +1,53 @@ +// 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, { Fragment } from "react"; +import Grid from "@mui/material/Grid"; +import { ConfirmModalIcon } from "../../../../../icons"; + +interface IValidRule { + ruleText: string; +} + +const ValidRule = ({ ruleText }: IValidRule) => { + return ( + + + + + + + {ruleText} + + + + ); +}; + +export default ValidRule;