github.com/minio/console@v1.4.1/web-app/src/screens/Console/Configurations/TiersConfiguration/AddTierConfiguration.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React, { Fragment, useCallback, useEffect, useState } from "react";
    18  
    19  import { useNavigate, useParams } from "react-router-dom";
    20  import get from "lodash/get";
    21  import {
    22    BackLink,
    23    breakPoints,
    24    Button,
    25    FileSelector,
    26    Grid,
    27    InputBox,
    28    PageLayout,
    29    SectionTitle,
    30  } from "mds";
    31  import { api } from "api";
    32  import { errorToHandler } from "api/errors";
    33  import { ApiError } from "api/consoleApi";
    34  import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
    35  import {
    36    azureServiceName,
    37    gcsServiceName,
    38    minioServiceName,
    39    s3ServiceName,
    40    tierTypes,
    41  } from "./utils";
    42  import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
    43  import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
    44  import { useAppDispatch } from "../../../../store";
    45  import RegionSelectWrapper from "./RegionSelectWrapper";
    46  import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
    47  import HelpMenu from "../../HelpMenu";
    48  
    49  const AddTierConfiguration = () => {
    50    const dispatch = useAppDispatch();
    51    const navigate = useNavigate();
    52    const params = useParams();
    53  
    54    //Local States
    55    const [saving, setSaving] = useState<boolean>(false);
    56  
    57    // Form Items
    58    const [name, setName] = useState<string>("");
    59    const [endpoint, setEndpoint] = useState<string>("");
    60    const [bucket, setBucket] = useState<string>("");
    61    const [prefix, setPrefix] = useState<string>("");
    62    const [region, setRegion] = useState<string>("");
    63    const [storageClass, setStorageClass] = useState<string>("");
    64  
    65    const [accessKey, setAccessKey] = useState<string>("");
    66    const [secretKey, setSecretKey] = useState<string>("");
    67  
    68    const [creds, setCreds] = useState<string>("");
    69    const [encodedCreds, setEncodedCreds] = useState<string>("");
    70  
    71    const [accountName, setAccountName] = useState<string>("");
    72    const [accountKey, setAccountKey] = useState<string>("");
    73  
    74    const [titleSelection, setTitleSelection] = useState<string>("");
    75  
    76    const type = get(params, "service", "s3");
    77  
    78    // Validations
    79    const [isFormValid, setIsFormValid] = useState<boolean>(true);
    80    const [nameInputError, setNameInputError] = useState<string>("");
    81  
    82    // Extra validation functions
    83  
    84    const validName = useCallback(() => {
    85      const patternAgainst = /^[A-Z0-9-_]+$/; // Only allow uppercase, numbers, dashes and underscores
    86      if (patternAgainst.test(name)) {
    87        setNameInputError("");
    88        return true;
    89      }
    90  
    91      setNameInputError(
    92        "Please verify that string is uppercase only and contains valid characters (numbers, dashes & underscores).",
    93      );
    94      return false;
    95    }, [name]);
    96  
    97    //Effects
    98  
    99    useEffect(() => {
   100      if (saving) {
   101        let request = {};
   102        let fields = {
   103          name,
   104          endpoint,
   105          bucket,
   106          prefix,
   107          region,
   108        };
   109  
   110        let tierType = type;
   111  
   112        switch (type) {
   113          case "minio":
   114            request = {
   115              minio: {
   116                ...fields,
   117                accesskey: accessKey,
   118                secretkey: secretKey,
   119              },
   120            };
   121            break;
   122          case "s3":
   123            request = {
   124              s3: {
   125                ...fields,
   126                accesskey: accessKey,
   127                secretkey: secretKey,
   128                storageclass: storageClass,
   129              },
   130            };
   131            break;
   132          case "gcs":
   133            request = {
   134              gcs: {
   135                ...fields,
   136                creds: encodedCreds,
   137              },
   138            };
   139            break;
   140          case "azure":
   141            request = {
   142              azure: {
   143                ...fields,
   144                accountname: accountName,
   145                accountkey: accountKey,
   146              },
   147            };
   148        }
   149  
   150        let payload = {
   151          type: tierType as
   152            | "azure"
   153            | "s3"
   154            | "minio"
   155            | "gcs"
   156            | "unsupported"
   157            | undefined,
   158          ...request,
   159        };
   160  
   161        api.admin
   162          .addTier(payload)
   163          .then(() => {
   164            setSaving(false);
   165            navigate(IAM_PAGES.TIERS);
   166          })
   167          .catch(async (res) => {
   168            const err = (await res.json()) as ApiError;
   169            setSaving(false);
   170            dispatch(setErrorSnackMessage(errorToHandler(err)));
   171          });
   172      }
   173    }, [
   174      accessKey,
   175      accountKey,
   176      accountName,
   177      bucket,
   178      encodedCreds,
   179      endpoint,
   180      name,
   181      prefix,
   182      region,
   183      saving,
   184      secretKey,
   185      dispatch,
   186      storageClass,
   187      type,
   188      navigate,
   189    ]);
   190  
   191    useEffect(() => {
   192      let valid = true;
   193      if (type === "") {
   194        valid = false;
   195      }
   196      if (name === "" || !validName()) {
   197        valid = false;
   198      }
   199      if (endpoint === "") {
   200        valid = false;
   201      }
   202      if (bucket === "") {
   203        valid = false;
   204      }
   205      if (region === "" && type !== "minio") {
   206        valid = false;
   207      }
   208  
   209      if (type === "s3" || type === "minio") {
   210        if (accessKey === "") {
   211          valid = false;
   212        }
   213        if (secretKey === "") {
   214          valid = false;
   215        }
   216      }
   217  
   218      if (type === "gcs") {
   219        if (encodedCreds === "") {
   220          valid = false;
   221        }
   222      }
   223  
   224      if (type === "azure") {
   225        if (accountName === "") {
   226          valid = false;
   227        }
   228        if (accountKey === "") {
   229          valid = false;
   230        }
   231      }
   232  
   233      setIsFormValid(valid);
   234    }, [
   235      accessKey,
   236      accountKey,
   237      accountName,
   238      bucket,
   239      encodedCreds,
   240      endpoint,
   241      isFormValid,
   242      name,
   243      prefix,
   244      region,
   245      secretKey,
   246      storageClass,
   247      type,
   248      validName,
   249    ]);
   250  
   251    useEffect(() => {
   252      switch (type) {
   253        case "gcs":
   254          setEndpoint("https://storage.googleapis.com");
   255          setTitleSelection("Google Cloud");
   256          break;
   257        case "s3":
   258          setEndpoint("https://s3.amazonaws.com");
   259          setTitleSelection("Amazon S3");
   260          break;
   261        case "azure":
   262          setEndpoint("http://blob.core.windows.net");
   263          setTitleSelection("Azure");
   264          break;
   265        case "minio":
   266          setEndpoint("");
   267          setTitleSelection("MinIO");
   268      }
   269    }, [type]);
   270  
   271    //Fetch Actions
   272    const submitForm = (event: React.FormEvent) => {
   273      event.preventDefault();
   274      setSaving(true);
   275    };
   276  
   277    // Input actions
   278    const updateTierName = (e: React.ChangeEvent<HTMLInputElement>) => {
   279      setName(e.target.value.toUpperCase());
   280    };
   281  
   282    const targetElement = tierTypes.find((item) => item.serviceName === type);
   283  
   284    useEffect(() => {
   285      dispatch(setHelpName("add-tier-configuration"));
   286      // eslint-disable-next-line react-hooks/exhaustive-deps
   287    }, []);
   288  
   289    return (
   290      <Fragment>
   291        <PageHeaderWrapper
   292          label={
   293            <Fragment>
   294              <BackLink
   295                label={"Add Tier"}
   296                onClick={() => navigate(IAM_PAGES.TIERS_ADD)}
   297              />
   298            </Fragment>
   299          }
   300          actions={<HelpMenu />}
   301        />
   302  
   303        <PageLayout>
   304          <Grid
   305            item
   306            xs={12}
   307            sx={{
   308              border: "1px solid #eaeaea",
   309              padding: "25px",
   310            }}
   311          >
   312            <form noValidate onSubmit={submitForm}>
   313              {type !== "" && targetElement ? (
   314                <SectionTitle icon={targetElement.logo} sx={{ marginBottom: 20 }}>
   315                  {titleSelection ? titleSelection : ""} - Add Tier Configuration
   316                </SectionTitle>
   317              ) : null}
   318              <Grid
   319                item
   320                xs={12}
   321                sx={{
   322                  display: "grid",
   323                  gridTemplateColumns: "1fr 1fr",
   324                  gridAutoFlow: "row",
   325                  gridRowGap: 20,
   326                  gridColumnGap: 50,
   327                  [`@media (max-width: ${breakPoints.sm}px)`]: {
   328                    gridTemplateColumns: "1fr",
   329                    gridAutoFlow: "dense",
   330                  },
   331                }}
   332              >
   333                {type !== "" && (
   334                  <Fragment>
   335                    <InputBox
   336                      id="name"
   337                      name="name"
   338                      label="Name"
   339                      placeholder="Enter Name (Eg. REMOTE-TIER)"
   340                      value={name}
   341                      onChange={updateTierName}
   342                      error={nameInputError}
   343                      required
   344                    />
   345                    <InputBox
   346                      id="endpoint"
   347                      name="endpoint"
   348                      label="Endpoint"
   349                      placeholder="Enter Endpoint"
   350                      value={endpoint}
   351                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   352                        setEndpoint(e.target.value);
   353                      }}
   354                      required
   355                    />
   356                    {(type === s3ServiceName || type === minioServiceName) && (
   357                      <Fragment>
   358                        <InputBox
   359                          id="accessKey"
   360                          name="accessKey"
   361                          label="Access Key"
   362                          placeholder="Enter Access Key"
   363                          value={accessKey}
   364                          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   365                            setAccessKey(e.target.value);
   366                          }}
   367                          required
   368                        />
   369                        <InputBox
   370                          id="secretKey"
   371                          name="secretKey"
   372                          label="Secret Key"
   373                          placeholder="Enter Secret Key"
   374                          value={secretKey}
   375                          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   376                            setSecretKey(e.target.value);
   377                          }}
   378                          required
   379                        />
   380                      </Fragment>
   381                    )}
   382                    {type === gcsServiceName && (
   383                      <FileSelector
   384                        accept=".json"
   385                        id="creds"
   386                        label="Credentials"
   387                        name="creds"
   388                        returnEncodedData
   389                        onChange={(_, fileName, encodedValue) => {
   390                          if (encodedValue) {
   391                            setEncodedCreds(encodedValue);
   392                            setCreds(fileName);
   393                          }
   394                        }}
   395                        value={creds}
   396                        required
   397                      />
   398                    )}
   399                    {type === azureServiceName && (
   400                      <Fragment>
   401                        <InputBox
   402                          id="accountName"
   403                          name="accountName"
   404                          label="Account Name"
   405                          placeholder="Enter Account Name"
   406                          value={accountName}
   407                          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   408                            setAccountName(e.target.value);
   409                          }}
   410                          required
   411                        />
   412                        <InputBox
   413                          id="accountKey"
   414                          name="accountKey"
   415                          label="Account Key"
   416                          placeholder="Enter Account Key"
   417                          value={accountKey}
   418                          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   419                            setAccountKey(e.target.value);
   420                          }}
   421                          required
   422                        />
   423                      </Fragment>
   424                    )}
   425                    <InputBox
   426                      id="bucket"
   427                      name="bucket"
   428                      label="Bucket"
   429                      placeholder="Enter Bucket"
   430                      value={bucket}
   431                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   432                        setBucket(e.target.value);
   433                      }}
   434                      required
   435                    />
   436                    <InputBox
   437                      id="prefix"
   438                      name="prefix"
   439                      label="Prefix"
   440                      placeholder="Enter Prefix"
   441                      value={prefix}
   442                      onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   443                        setPrefix(e.target.value);
   444                      }}
   445                    />
   446                    <RegionSelectWrapper
   447                      onChange={(value) => {
   448                        setRegion(value);
   449                      }}
   450                      required={type !== "minio"}
   451                      label={"Region"}
   452                      id="region"
   453                      type={type as "azure" | "s3" | "minio" | "gcs"}
   454                    />
   455                    {type === s3ServiceName && (
   456                      <InputBox
   457                        id="storageClass"
   458                        name="storageClass"
   459                        label="Storage Class"
   460                        placeholder="Enter Storage Class"
   461                        value={storageClass}
   462                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   463                          setStorageClass(e.target.value);
   464                        }}
   465                      />
   466                    )}
   467                  </Fragment>
   468                )}
   469              </Grid>
   470              <Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
   471                <Button
   472                  id={"save-tier-configuration"}
   473                  type="submit"
   474                  variant="callAction"
   475                  disabled={saving || !isFormValid}
   476                  label={"Save Tier Configuration"}
   477                />
   478              </Grid>
   479            </form>
   480          </Grid>
   481        </PageLayout>
   482      </Fragment>
   483    );
   484  };
   485  
   486  export default AddTierConfiguration;