Skip to content
54 changes: 54 additions & 0 deletions portal-ui/src/icons/HideTextIcon.tsx
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

import * as React from "react";
import { SVGProps } from "react";

const HideTextIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 15 15"
{...props}
>
<g id="Grupo_2449" data-name="Grupo 2449" transform="translate(-140 -181)">
<g id="OpenListIcon-full" transform="translate(144 250.612)">
<g
id="noun_chevron_2320228"
transform="translate(6.827 -63.612) rotate(90)"
>
<path
id="Trazado_6842"
data-name="Trazado 6842"
d="M.422,6.661a.433.433,0,0,1-.3-.117.37.37,0,0,1,0-.557L2.983,3.335.126.675a.37.37,0,0,1,0-.557.443.443,0,0,1,.6,0L3.889,3.052a.373.373,0,0,1,.126.274.344.344,0,0,1-.126.274L.727,6.533a.443.443,0,0,1-.306.127Z"
transform="translate(0 0)"
/>
</g>
<rect
id="Rectángulo_896"
data-name="Rectángulo 896"
width="0.462"
height="0.462"
transform="translate(0 -61.808)"
fill="none"
/>
</g>
</g>
</svg>
);

export default HideTextIcon;
52 changes: 52 additions & 0 deletions portal-ui/src/icons/ShowTextIcon.tsx
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

import * as React from "react";
import { SVGProps } from "react";

const ShowTextIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 15 15"
{...props}
>
<g id="OpenListIcon-full" transform="translate(4 4.984)">
<g
id="noun_chevron_2320228"
transform="translate(0.167 4.016) rotate(-90)"
>
<path
id="Trazado_6842"
data-name="Trazado 6842"
d="M.422,0a.433.433,0,0,0-.3.117.37.37,0,0,0,0,.557L2.983,3.325.126,5.986a.37.37,0,0,0,0,.557.443.443,0,0,0,.6,0L3.889,3.609a.373.373,0,0,0,.126-.274.344.344,0,0,0-.126-.274L.727.127A.443.443,0,0,0,.422,0Z"
transform="translate(0 0)"
/>
</g>
<rect
id="Rectángulo_896"
data-name="Rectángulo 896"
width="0.462"
height="0.462"
transform="translate(0 1.75)"
fill="none"
/>
</g>
</svg>
);

export default ShowTextIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
// 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/>.

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";
Expand All @@ -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";
Expand All @@ -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({
Expand Down Expand Up @@ -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<boolean[]>([]);
const errorList = validationResult.filter((v) => !v);
const hasErrors = errorList.length > 0;
const [records, setRecords] = useState<string[]>([]);
const versioningEnabled = useSelector(
(state: AppState) => state.addBucket.versioningEnabled
);
Expand Down Expand Up @@ -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());
};
Expand Down Expand Up @@ -212,85 +263,6 @@ const AddBucket = ({ classes }: IsetProps) => {
)}
<br />
<br />
<b>Bucket Naming Rules</b>
<Box
sx={{
display: "flex",
flexFlow: "column",
fontSize: "14px",
flex: "2",
"& .step-number": {
color: "#ffffff",
height: "25px",
width: "25px",
background: "#081C42",
marginRight: "10px",
textAlign: "center",
fontWeight: 600,
borderRadius: "50%",
},

"& .step-row": {
fontSize: "14px",
display: "flex",
marginTop: "15px",
marginBottom: "2px",

"&.step-text": {
fontWeight: 400,
},
"&:before": {
content: "' '",
height: "7px",
width: "7px",
backgroundColor: "#2781B0",
marginRight: "10px",
marginTop: "7px",
flexShrink: 0,
},
},
}}
>
<Box className="step-row">
<div className="step-text">
Bucket names must be between 3 (min) and 63 (max)
characters long.
</div>
</Box>
<Box className="step-row">
<div className="step-text">
Bucket names can consist only of lowercase letters,
numbers, dots (.), and hyphens (-).
</div>
</Box>
<Box className="step-row">
<div className="step-text">
Bucket names must not contain two adjacent periods.
</div>
</Box>
<Box className="step-row">
<div className="step-text">
Bucket names must not be formatted as an IP address (for
example, 192.168.5.4).
</div>
</Box>
<Box className="step-row">
<div className="step-text">
Bucket names must not start with the prefix xn--.
</div>
</Box>
<Box className="step-row">
<div className="step-text">
Bucket names must not end with the suffix -s3alias. This
suffix is reserved for access point alias names.
</div>
</Box>
<Box className="step-row">
<div className="step-text">
Bucket names must be unique within a partition.
</div>
</Box>
</Box>
</Fragment>
}
/>
Expand All @@ -306,7 +278,10 @@ const AddBucket = ({ classes }: IsetProps) => {
>
<Grid container marginTop={1} spacing={2}>
<Grid item xs={12}>
<AddBucketName />
<AddBucketName hasErrors={hasErrors} />
</Grid>
<Grid item xs={12}>
<BucketNamingRules errorList={validationResult} />
</Grid>
<Grid item xs={12}>
<SectionTitle>Features</SectionTitle>
Expand All @@ -330,8 +305,7 @@ const AddBucket = ({ classes }: IsetProps) => {
</Fragment>
)}
</Grid>

<Grid item xs={12}>
<Grid item xs={12} spacing={2}>
{siteReplicationInfo.enabled && (
<Fragment>
<br />
Expand Down Expand Up @@ -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
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ 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);
return (
<InputBoxWrapper
id="bucket-name"
name="bucket-name"
error={hasErrors ? "Invalid bucket Name" : ""}
autoFocus={true}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setName(event.target.value));
}}
label="Bucket Name"
value={bucketName}
required
/>
);
};
Expand Down
Loading