github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/EditBucketReplication.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2023 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, useEffect, useState } from "react";
    18  import { useNavigate } from "react-router-dom";
    19  import {
    20    BackLink,
    21    Box,
    22    BucketReplicationIcon,
    23    Button,
    24    FormLayout,
    25    Grid,
    26    HelpBox,
    27    InputBox,
    28    PageLayout,
    29    ReadBox,
    30    Switch,
    31  } from "mds";
    32  import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
    33  import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
    34  import { useAppDispatch } from "../../../../store";
    35  import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
    36  import HelpMenu from "../../HelpMenu";
    37  import { api } from "api";
    38  import { errorToHandler } from "api/errors";
    39  import QueryMultiSelector from "screens/Console/Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
    40  
    41  const EditBucketReplication = () => {
    42    const dispatch = useAppDispatch();
    43    const navigate = useNavigate();
    44    let params = new URLSearchParams(document.location.search);
    45  
    46    const bucketName = params.get("bucketName") || "";
    47    const ruleID = params.get("ruleID") || "";
    48  
    49    useEffect(() => {
    50      dispatch(setHelpName("bucket-replication-edit"));
    51      // eslint-disable-next-line react-hooks/exhaustive-deps
    52    }, []);
    53  
    54    const backLink = IAM_PAGES.BUCKETS + `/${bucketName}/admin/replication`;
    55  
    56    const [editLoading, setEditLoading] = useState<boolean>(true);
    57    const [saveEdit, setSaveEdit] = useState<boolean>(false);
    58    const [priority, setPriority] = useState<string>("1");
    59    const [destination, setDestination] = useState<string>("");
    60    const [prefix, setPrefix] = useState<string>("");
    61    const [repDeleteMarker, setRepDeleteMarker] = useState<boolean>(false);
    62    const [metadataSync, setMetadataSync] = useState<boolean>(false);
    63    const [initialTags, setInitialTags] = useState<string>("");
    64    const [tags, setTags] = useState<string>("");
    65    const [targetStorageClass, setTargetStorageClass] = useState<string>("");
    66    const [repExisting, setRepExisting] = useState<boolean>(false);
    67    const [repDelete, setRepDelete] = useState<boolean>(false);
    68    const [ruleState, setRuleState] = useState<boolean>(false);
    69  
    70    useEffect(() => {
    71      if (editLoading && bucketName && ruleID) {
    72        api.buckets
    73  
    74          .getBucketReplicationRule(bucketName, ruleID)
    75          .then((res) => {
    76            setPriority(res.data.priority ? res.data.priority.toString() : "");
    77            const pref = res.data.prefix || "";
    78            const tag = res.data.tags || "";
    79            setPrefix(pref);
    80            setInitialTags(tag);
    81            setTags(tag);
    82            setDestination(res.data.destination?.bucket || "");
    83            setRepDeleteMarker(res.data.delete_marker_replication || false);
    84            setTargetStorageClass(res.data.storageClass || "");
    85            setRepExisting(!!res.data.existingObjects);
    86            setRepDelete(!!res.data.deletes_replication);
    87            setRuleState(res.data.status === "Enabled");
    88            setMetadataSync(!!res.data.metadata_replication);
    89  
    90            setEditLoading(false);
    91          })
    92          .catch((err) => {
    93            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
    94            setEditLoading(false);
    95          });
    96      }
    97    }, [editLoading, dispatch, bucketName, ruleID]);
    98  
    99    useEffect(() => {
   100      if (saveEdit && bucketName && ruleID) {
   101        const remoteBucketsInfo = {
   102          arn: destination,
   103          ruleState: ruleState,
   104          prefix: prefix,
   105          tags: tags,
   106          replicateDeleteMarkers: repDeleteMarker,
   107          replicateDeletes: repDelete,
   108          replicateExistingObjects: repExisting,
   109          replicateMetadata: metadataSync,
   110          priority: parseInt(priority),
   111          storageClass: targetStorageClass,
   112        };
   113  
   114        api.buckets
   115          .updateMultiBucketReplication(bucketName, ruleID, remoteBucketsInfo)
   116          .then(() => {
   117            navigate(backLink);
   118          })
   119          .catch((err) => {
   120            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   121            setSaveEdit(false);
   122          });
   123      }
   124      // eslint-disable-next-line react-hooks/exhaustive-deps
   125    }, [
   126      saveEdit,
   127      bucketName,
   128      ruleID,
   129      destination,
   130      prefix,
   131      tags,
   132      repDeleteMarker,
   133      priority,
   134      repDelete,
   135      repExisting,
   136      ruleState,
   137      metadataSync,
   138      targetStorageClass,
   139      dispatch,
   140    ]);
   141  
   142    return (
   143      <Fragment>
   144        <PageHeaderWrapper
   145          label={
   146            <BackLink
   147              label={"Edit Bucket Replication"}
   148              onClick={() => navigate(backLink)}
   149            />
   150          }
   151          actions={<HelpMenu />}
   152        />
   153        <PageLayout>
   154          <form
   155            noValidate
   156            autoComplete="off"
   157            onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
   158              e.preventDefault();
   159              setSaveEdit(true);
   160            }}
   161          >
   162            <FormLayout
   163              containerPadding={false}
   164              withBorders={false}
   165              helpBox={
   166                <HelpBox
   167                  iconComponent={<BucketReplicationIcon />}
   168                  title="Bucket Replication Configuration"
   169                  help={
   170                    <Fragment>
   171                      <Box sx={{ paddingTop: "10px" }}>
   172                        For each write operation to the bucket, MinIO checks all
   173                        configured replication rules for the bucket and applies
   174                        the matching rule with highest configured priority.
   175                      </Box>
   176                      <Box sx={{ paddingTop: "10px" }}>
   177                        MinIO supports enabling replication of existing objects in
   178                        a bucket.
   179                      </Box>
   180                      <Box sx={{ paddingTop: "10px" }}>
   181                        MinIO does not enable existing object replication by
   182                        default. Objects created before replication was configured
   183                        or while replication is disabled are not synchronized to
   184                        the target deployment unless replication of existing
   185                        objects is enabled.
   186                      </Box>
   187                      <Box sx={{ paddingTop: "10px" }}>
   188                        MinIO supports replicating delete operations, where MinIO
   189                        synchronizes deleting specific object versions and new
   190                        delete markers. Delete operation replication uses the same
   191                        replication process as all other replication operations.
   192                      </Box>{" "}
   193                    </Fragment>
   194                  }
   195                />
   196              }
   197            >
   198              <Switch
   199                checked={ruleState}
   200                id="ruleState"
   201                name="ruleState"
   202                label="Rule State"
   203                onChange={(e) => {
   204                  setRuleState(e.target.checked);
   205                }}
   206              />
   207              <ReadBox label={"Destination"} sx={{ width: "100%" }}>
   208                {destination}
   209              </ReadBox>
   210              <InputBox
   211                id="priority"
   212                name="priority"
   213                onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   214                  if (e.target.validity.valid) {
   215                    setPriority(e.target.value);
   216                  }
   217                }}
   218                label="Priority"
   219                value={priority}
   220                pattern={"[0-9]*"}
   221              />
   222              <InputBox
   223                id="storageClass"
   224                name="storageClass"
   225                onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   226                  setTargetStorageClass(e.target.value);
   227                }}
   228                placeholder="STANDARD_IA,REDUCED_REDUNDANCY etc"
   229                label="Storage Class"
   230                value={targetStorageClass}
   231              />
   232              <fieldset className={"inputItem"}>
   233                <legend>Object Filters</legend>
   234                <InputBox
   235                  id="prefix"
   236                  name="prefix"
   237                  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   238                    setPrefix(e.target.value);
   239                  }}
   240                  placeholder="prefix"
   241                  label="Prefix"
   242                  value={prefix}
   243                />
   244                <QueryMultiSelector
   245                  name="tags"
   246                  label="Tags"
   247                  elements={initialTags}
   248                  onChange={(vl: string) => {
   249                    setTags(vl);
   250                  }}
   251                  keyPlaceholder="Tag Key"
   252                  valuePlaceholder="Tag Value"
   253                  withBorder
   254                />
   255              </fieldset>
   256              <fieldset className={"inputItem"}>
   257                <legend>Replication Options</legend>
   258                <Switch
   259                  checked={repExisting}
   260                  id="repExisting"
   261                  name="repExisting"
   262                  label="Existing Objects"
   263                  onChange={(e) => {
   264                    setRepExisting(e.target.checked);
   265                  }}
   266                  description={"Replicate existing objects"}
   267                />
   268                <Switch
   269                  checked={metadataSync}
   270                  id="metadatataSync"
   271                  name="metadatataSync"
   272                  label="Metadata Sync"
   273                  onChange={(e) => {
   274                    setMetadataSync(e.target.checked);
   275                  }}
   276                  description={"Metadata Sync"}
   277                />
   278                <Switch
   279                  checked={repDeleteMarker}
   280                  id="deleteMarker"
   281                  name="deleteMarker"
   282                  label="Delete Marker"
   283                  onChange={(e) => {
   284                    setRepDeleteMarker(e.target.checked);
   285                  }}
   286                  description={"Replicate soft deletes"}
   287                />
   288                <Switch
   289                  checked={repDelete}
   290                  id="repDelete"
   291                  name="repDelete"
   292                  label="Deletes"
   293                  onChange={(e) => {
   294                    setRepDelete(e.target.checked);
   295                  }}
   296                  description={"Replicate versioned deletes"}
   297                />
   298              </fieldset>
   299              <Grid
   300                item
   301                xs={12}
   302                sx={{
   303                  display: "flex",
   304                  flexDirection: "row",
   305                  justifyContent: "end",
   306                  gap: 10,
   307                  paddingTop: 10,
   308                }}
   309              >
   310                <Button
   311                  id={"cancel-edit-replication"}
   312                  type="button"
   313                  variant="regular"
   314                  disabled={editLoading || saveEdit}
   315                  onClick={() => {
   316                    navigate(backLink);
   317                  }}
   318                  label={"Cancel"}
   319                />
   320                <Button
   321                  id={"save-replication"}
   322                  type="submit"
   323                  variant="callAction"
   324                  disabled={editLoading || saveEdit}
   325                  label={"Save"}
   326                />
   327              </Grid>
   328            </FormLayout>
   329          </form>
   330        </PageLayout>
   331      </Fragment>
   332    );
   333  };
   334  
   335  export default EditBucketReplication;