github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.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, useEffect, useState } from "react";
    18  import { useSelector } from "react-redux";
    19  import { useNavigate, useParams } from "react-router-dom";
    20  import {
    21    AddIcon,
    22    Box,
    23    BucketsIcon,
    24    Button,
    25    DataTable,
    26    Grid,
    27    HelpBox,
    28    SectionTitle,
    29    TrashIcon,
    30    HelpTip,
    31  } from "mds";
    32  import api from "../../../../common/api";
    33  import {
    34    BucketReplication,
    35    BucketReplicationDestination,
    36    BucketReplicationRule,
    37  } from "../types";
    38  import { ErrorResponseHandler } from "../../../../common/types";
    39  import {
    40    hasPermission,
    41    SecureComponent,
    42  } from "../../../../common/SecureComponent";
    43  import {
    44    IAM_PAGES,
    45    IAM_SCOPES,
    46  } from "../../../../common/SecureComponent/permissions";
    47  import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
    48  import { selBucketDetailsLoading } from "./bucketDetailsSlice";
    49  import { useAppDispatch } from "../../../../store";
    50  import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
    51  import withSuspense from "../../Common/Components/withSuspense";
    52  
    53  const EditReplicationModal = withSuspense(
    54    React.lazy(() => import("./EditReplicationModal")),
    55  );
    56  const AddReplicationModal = withSuspense(
    57    React.lazy(() => import("./AddReplicationModal")),
    58  );
    59  const DeleteReplicationRule = withSuspense(
    60    React.lazy(() => import("./DeleteReplicationRule")),
    61  );
    62  
    63  const BucketReplicationPanel = () => {
    64    const dispatch = useAppDispatch();
    65    const params = useParams();
    66  
    67    const loadingBucket = useSelector(selBucketDetailsLoading);
    68  
    69    const [loadingReplication, setLoadingReplication] = useState<boolean>(true);
    70    const [replicationRules, setReplicationRules] = useState<
    71      BucketReplicationRule[]
    72    >([]);
    73    const [deleteReplicationModal, setDeleteReplicationModal] =
    74      useState<boolean>(false);
    75    const [openSetReplication, setOpenSetReplication] = useState<boolean>(false);
    76    const [editReplicationModal, setEditReplicationModal] =
    77      useState<boolean>(false);
    78    const [selectedRRule, setSelectedRRule] = useState<string>("");
    79    const [selectedRepRules, setSelectedRepRules] = useState<string[]>([]);
    80    const [deleteSelectedRules, setDeleteSelectedRules] =
    81      useState<boolean>(false);
    82  
    83    const bucketName = params.bucketName || "";
    84  
    85    const displayReplicationRules = hasPermission(bucketName, [
    86      IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
    87      IAM_SCOPES.S3_GET_ACTIONS,
    88    ]);
    89    useEffect(() => {
    90      dispatch(setHelpName("bucket_detail_replication"));
    91      // eslint-disable-next-line react-hooks/exhaustive-deps
    92    }, []);
    93  
    94    useEffect(() => {
    95      if (loadingBucket) {
    96        setLoadingReplication(true);
    97      }
    98    }, [loadingBucket, setLoadingReplication]);
    99  
   100    useEffect(() => {
   101      if (loadingReplication) {
   102        if (displayReplicationRules) {
   103          api
   104            .invoke("GET", `/api/v1/buckets/${bucketName}/replication`)
   105            .then((res: BucketReplication) => {
   106              const r = res.rules ? res.rules : [];
   107  
   108              r.sort((a, b) => a.priority - b.priority);
   109  
   110              setReplicationRules(r);
   111              setLoadingReplication(false);
   112            })
   113            .catch((err: ErrorResponseHandler) => {
   114              dispatch(setErrorSnackMessage(err));
   115              setLoadingReplication(false);
   116            });
   117        } else {
   118          setLoadingReplication(false);
   119        }
   120      }
   121    }, [loadingReplication, dispatch, bucketName, displayReplicationRules]);
   122  
   123    const closeAddReplication = () => {
   124      setOpenReplicationOpen(false);
   125      setLoadingReplication(true);
   126    };
   127  
   128    const setOpenReplicationOpen = (open = false) => {
   129      setOpenSetReplication(open);
   130    };
   131  
   132    const closeReplicationModalDelete = (refresh: boolean) => {
   133      setDeleteReplicationModal(false);
   134  
   135      if (refresh) {
   136        setLoadingReplication(true);
   137      }
   138    };
   139  
   140    const closeEditReplication = (refresh: boolean) => {
   141      setEditReplicationModal(false);
   142  
   143      if (refresh) {
   144        setLoadingReplication(true);
   145      }
   146    };
   147  
   148    const confirmDeleteReplication = (replication: BucketReplicationRule) => {
   149      setSelectedRRule(replication.id);
   150      setDeleteSelectedRules(false);
   151      setDeleteReplicationModal(true);
   152    };
   153  
   154    const confirmDeleteSelectedReplicationRules = () => {
   155      setSelectedRRule("selectedRules");
   156      setDeleteSelectedRules(true);
   157      setDeleteReplicationModal(true);
   158    };
   159    const navigate = useNavigate();
   160    const editReplicationRule = (replication: BucketReplicationRule) => {
   161      setSelectedRRule(replication.id);
   162      navigate(
   163        `/buckets/edit-replication?bucketName=${bucketName}&ruleID=${replication.id}`,
   164      );
   165    };
   166  
   167    const ruleDestDisplay = (events: BucketReplicationDestination) => {
   168      return <Fragment>{events.bucket.replace("arn:aws:s3:::", "")}</Fragment>;
   169    };
   170  
   171    const tagDisplay = (events: BucketReplicationRule) => {
   172      return <Fragment>{events && events.tags !== "" ? "Yes" : "No"}</Fragment>;
   173    };
   174  
   175    const selectAllItems = () => {
   176      if (selectedRepRules.length === replicationRules.length) {
   177        setSelectedRepRules([]);
   178        return;
   179      }
   180      setSelectedRepRules(replicationRules.map((x) => x.id));
   181    };
   182  
   183    const selectRules = (e: React.ChangeEvent<HTMLInputElement>) => {
   184      const targetD = e.target;
   185      const value = targetD.value;
   186      const checked = targetD.checked;
   187  
   188      let elements: string[] = [...selectedRepRules]; // We clone the selectedSAs array
   189      if (checked) {
   190        // If the user has checked this field we need to push this to selectedSAs
   191        elements.push(value);
   192      } else {
   193        // User has unchecked this field, we need to remove it from the list
   194        elements = elements.filter((element) => element !== value);
   195      }
   196      setSelectedRepRules(elements);
   197      return elements;
   198    };
   199  
   200    const replicationTableActions: any = [
   201      {
   202        type: "delete",
   203        onClick: confirmDeleteReplication,
   204      },
   205      {
   206        type: "view",
   207        onClick: editReplicationRule,
   208        disableButtonFunction: !hasPermission(
   209          bucketName,
   210          [
   211            IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION,
   212            IAM_SCOPES.S3_PUT_ACTIONS,
   213          ],
   214          true,
   215        ),
   216      },
   217    ];
   218  
   219    return (
   220      <Fragment>
   221        {openSetReplication && (
   222          <AddReplicationModal
   223            closeModalAndRefresh={closeAddReplication}
   224            open={openSetReplication}
   225            bucketName={bucketName}
   226            setReplicationRules={replicationRules}
   227          />
   228        )}
   229  
   230        {deleteReplicationModal && (
   231          <DeleteReplicationRule
   232            deleteOpen={deleteReplicationModal}
   233            selectedBucket={bucketName}
   234            closeDeleteModalAndRefresh={closeReplicationModalDelete}
   235            ruleToDelete={selectedRRule}
   236            rulesToDelete={selectedRepRules}
   237            remainingRules={replicationRules.length}
   238            allSelected={
   239              replicationRules.length > 0 &&
   240              selectedRepRules.length === replicationRules.length
   241            }
   242            deleteSelectedRules={deleteSelectedRules}
   243          />
   244        )}
   245  
   246        {editReplicationModal && (
   247          <EditReplicationModal
   248            closeModalAndRefresh={closeEditReplication}
   249            open={editReplicationModal}
   250            bucketName={bucketName}
   251            ruleID={selectedRRule}
   252          />
   253        )}
   254        <SectionTitle
   255          separator
   256          sx={{ marginBottom: 15 }}
   257          actions={
   258            <Box style={{ display: "flex", gap: 10 }}>
   259              <SecureComponent
   260                scopes={[
   261                  IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION,
   262                  IAM_SCOPES.S3_PUT_ACTIONS,
   263                ]}
   264                resource={bucketName}
   265                matchAll
   266                errorProps={{ disabled: true }}
   267              >
   268                <TooltipWrapper tooltip={"Remove Selected Replication Rules"}>
   269                  <Button
   270                    id={"remove-bucket-replication-rule"}
   271                    onClick={() => {
   272                      confirmDeleteSelectedReplicationRules();
   273                    }}
   274                    label={"Remove Selected Rules"}
   275                    icon={<TrashIcon />}
   276                    color={"secondary"}
   277                    disabled={
   278                      selectedRepRules.length === 0 ||
   279                      replicationRules.length === 0
   280                    }
   281                    variant={"secondary"}
   282                  />
   283                </TooltipWrapper>
   284              </SecureComponent>
   285              <SecureComponent
   286                scopes={[
   287                  IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION,
   288                  IAM_SCOPES.S3_PUT_ACTIONS,
   289                ]}
   290                resource={bucketName}
   291                matchAll
   292                errorProps={{ disabled: true }}
   293              >
   294                <TooltipWrapper tooltip={"Add Replication Rule"}>
   295                  <Button
   296                    id={"add-bucket-replication-rule"}
   297                    onClick={() => {
   298                      navigate(
   299                        IAM_PAGES.BUCKETS_ADD_REPLICATION +
   300                          `?bucketName=${bucketName}&nextPriority=${
   301                            replicationRules.length + 1
   302                          }`,
   303                      );
   304                    }}
   305                    label={"Add Replication Rule"}
   306                    icon={<AddIcon />}
   307                    variant={"callAction"}
   308                  />
   309                </TooltipWrapper>
   310              </SecureComponent>
   311            </Box>
   312          }
   313        >
   314          <HelpTip
   315            content={
   316              <Fragment>
   317                MinIO{" "}
   318                <a
   319                  target="blank"
   320                  href="https://min.io/docs/minio/kubernetes/upstream/administration/bucket-replication.html"
   321                >
   322                  server-side bucket replication
   323                </a>{" "}
   324                is an automatic bucket-level configuration that synchronizes
   325                objects between a source and destination bucket.
   326              </Fragment>
   327            }
   328            placement="right"
   329          >
   330            Replication
   331          </HelpTip>
   332        </SectionTitle>
   333        <Grid container>
   334          <Grid item xs={12}>
   335            <SecureComponent
   336              scopes={[
   337                IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
   338                IAM_SCOPES.S3_GET_ACTIONS,
   339              ]}
   340              resource={bucketName}
   341              errorProps={{ disabled: true }}
   342            >
   343              <DataTable
   344                itemActions={replicationTableActions}
   345                columns={[
   346                  {
   347                    label: "Priority",
   348                    elementKey: "priority",
   349                    width: 55,
   350                    contentTextAlign: "center",
   351                  },
   352                  {
   353                    label: "Destination",
   354                    elementKey: "destination",
   355                    renderFunction: ruleDestDisplay,
   356                  },
   357                  {
   358                    label: "Prefix",
   359                    elementKey: "prefix",
   360                    width: 200,
   361                  },
   362                  {
   363                    label: "Tags",
   364                    elementKey: "tags",
   365                    renderFunction: tagDisplay,
   366                    width: 60,
   367                  },
   368                  { label: "Status", elementKey: "status", width: 100 },
   369                ]}
   370                isLoading={loadingReplication}
   371                records={replicationRules}
   372                entityName="Replication Rules"
   373                idField="id"
   374                customPaperHeight={"400px"}
   375                textSelectable
   376                selectedItems={selectedRepRules}
   377                onSelect={(e) => selectRules(e)}
   378                onSelectAll={selectAllItems}
   379              />
   380            </SecureComponent>
   381          </Grid>
   382          <Grid item xs={12}>
   383            <br />
   384            <HelpBox
   385              title={"Replication"}
   386              iconComponent={<BucketsIcon />}
   387              help={
   388                <Fragment>
   389                  MinIO supports server-side and client-side replication of
   390                  objects between source and destination buckets.
   391                  <br />
   392                  <br />
   393                  You can learn more at our{" "}
   394                  <a
   395                    href="https://min.io/docs/minio/linux/administration/bucket-replication.html?ref=con"
   396                    target="_blank"
   397                    rel="noopener"
   398                  >
   399                    documentation
   400                  </a>
   401                  .
   402                </Fragment>
   403              }
   404            />
   405          </Grid>
   406        </Grid>
   407      </Fragment>
   408    );
   409  };
   410  
   411  export default BucketReplicationPanel;