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

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2022 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    ActionLink,
    21    AddIcon,
    22    Box,
    23    Button,
    24    ClustersIcon,
    25    ConfirmDeleteIcon,
    26    Grid,
    27    HelpBox,
    28    Loader,
    29    PageLayout,
    30    RecoverIcon,
    31    SectionTitle,
    32    TrashIcon,
    33  } from "mds";
    34  import { ErrorResponseHandler } from "../../../../common/types";
    35  import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
    36  import {
    37    setErrorSnackMessage,
    38    setHelpName,
    39    setSnackBarMessage,
    40  } from "../../../../systemSlice";
    41  import { useAppDispatch } from "../../../../store";
    42  import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
    43  import useApi from "../../Common/Hooks/useApi";
    44  import ReplicationSites from "./ReplicationSites";
    45  import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
    46  import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
    47  import HelpMenu from "../../HelpMenu";
    48  
    49  export type ReplicationSite = {
    50    deploymentID: string;
    51    endpoint: string;
    52    name: string;
    53    isCurrent?: boolean;
    54  };
    55  
    56  const SiteReplication = () => {
    57    const dispatch = useAppDispatch();
    58    const navigate = useNavigate();
    59  
    60    const [sites, setSites] = useState([]);
    61  
    62    const [deleteAll, setIsDeleteAll] = useState(false);
    63    const [isSiteInfoLoading, invokeSiteInfoApi] = useApi(
    64      (res: any) => {
    65        const { sites: siteList, name: curSiteName } = res;
    66        // current site name to be the fist one.
    67        const foundIdx = siteList.findIndex((el: any) => el.name === curSiteName);
    68        if (foundIdx !== -1) {
    69          let curSite = siteList[foundIdx];
    70          curSite = {
    71            ...curSite,
    72            isCurrent: true,
    73          };
    74          siteList.splice(foundIdx, 1, curSite);
    75        }
    76  
    77        siteList.sort((x: any, y: any) => {
    78          return x.name === curSiteName ? -1 : y.name === curSiteName ? 1 : 0;
    79        });
    80        setSites(siteList);
    81      },
    82      (err: any) => {
    83        setSites([]);
    84      },
    85    );
    86  
    87    const getSites = () => {
    88      invokeSiteInfoApi("GET", `api/v1/admin/site-replication`);
    89    };
    90  
    91    const [isRemoving, invokeSiteRemoveApi] = useApi(
    92      (res: any) => {
    93        setIsDeleteAll(false);
    94        dispatch(setSnackBarMessage(`Successfully deleted.`));
    95        getSites();
    96      },
    97      (err: ErrorResponseHandler) => {
    98        dispatch(setErrorSnackMessage(err));
    99      },
   100    );
   101  
   102    const removeSites = (isAll: boolean = false, delSites: string[] = []) => {
   103      invokeSiteRemoveApi("DELETE", `api/v1/admin/site-replication`, {
   104        all: isAll,
   105        sites: delSites,
   106      });
   107    };
   108  
   109    useEffect(() => {
   110      getSites();
   111      // eslint-disable-next-line react-hooks/exhaustive-deps
   112    }, []);
   113  
   114    const hasSites = sites?.length;
   115  
   116    useEffect(() => {
   117      dispatch(setHelpName("site-replication"));
   118      // eslint-disable-next-line react-hooks/exhaustive-deps
   119    }, []);
   120  
   121    return (
   122      <Fragment>
   123        <PageHeaderWrapper label={"Site Replication"} actions={<HelpMenu />} />
   124        <PageLayout>
   125          <SectionTitle
   126            separator={!!hasSites}
   127            sx={{ marginBottom: 15 }}
   128            actions={
   129              <Box
   130                sx={{
   131                  display: "flex",
   132                  alignItems: "center",
   133                  justifyContent: "flex-end",
   134                  gap: 8,
   135                }}
   136              >
   137                {hasSites ? (
   138                  <Fragment>
   139                    <TooltipWrapper tooltip={"Delete All"}>
   140                      <Button
   141                        id={"delete-all"}
   142                        label={"Delete All"}
   143                        variant="secondary"
   144                        disabled={isRemoving}
   145                        icon={<TrashIcon />}
   146                        onClick={() => {
   147                          setIsDeleteAll(true);
   148                        }}
   149                      />
   150                    </TooltipWrapper>
   151                    <TooltipWrapper tooltip={"Replication Status"}>
   152                      <Button
   153                        id={"replication-status"}
   154                        label={"Replication Status"}
   155                        variant="regular"
   156                        icon={<RecoverIcon />}
   157                        onClick={(e) => {
   158                          e.preventDefault();
   159                          navigate(IAM_PAGES.SITE_REPLICATION_STATUS);
   160                        }}
   161                      />
   162                    </TooltipWrapper>
   163                  </Fragment>
   164                ) : null}
   165                <TooltipWrapper tooltip={"Add Replication Sites"}>
   166                  <Button
   167                    id={"add-replication-site"}
   168                    label={"Add Sites"}
   169                    variant="callAction"
   170                    disabled={isRemoving}
   171                    icon={<AddIcon />}
   172                    onClick={() => {
   173                      navigate(IAM_PAGES.SITE_REPLICATION_ADD);
   174                    }}
   175                  />
   176                </TooltipWrapper>
   177              </Box>
   178            }
   179          >
   180            {hasSites ? "List of Replicated Sites" : ""}
   181          </SectionTitle>
   182          {hasSites ? (
   183            <ReplicationSites
   184              sites={sites}
   185              onDeleteSite={removeSites}
   186              onRefresh={getSites}
   187            />
   188          ) : null}
   189          {isSiteInfoLoading ? (
   190            <Box
   191              sx={{
   192                display: "flex",
   193                justifyContent: "center",
   194                alignItems: "center",
   195                height: "calc( 100vh - 450px )",
   196              }}
   197            >
   198              <Loader style={{ width: 16, height: 16 }} />
   199            </Box>
   200          ) : null}
   201          {!hasSites && !isSiteInfoLoading ? (
   202            <Grid container>
   203              <Grid item xs={8}>
   204                <HelpBox
   205                  title={"Site Replication"}
   206                  iconComponent={<ClustersIcon />}
   207                  help={
   208                    <Fragment>
   209                      This feature allows multiple independent MinIO sites (or
   210                      clusters) that are using the same external IDentity Provider
   211                      (IDP) to be configured as replicas.
   212                      <br />
   213                      <br />
   214                      To get started,{" "}
   215                      <ActionLink
   216                        isLoading={false}
   217                        label={""}
   218                        onClick={() => {
   219                          navigate(IAM_PAGES.SITE_REPLICATION_ADD);
   220                        }}
   221                      >
   222                        Add a Replication Site
   223                      </ActionLink>
   224                      .
   225                      <br />
   226                      You can learn more at our{" "}
   227                      <a
   228                        href="https://min.io/docs/minio/linux/operations/install-deploy-manage/multi-site-replication.html?ref=con"
   229                        target="_blank"
   230                        rel="noopener"
   231                      >
   232                        documentation
   233                      </a>
   234                      .
   235                    </Fragment>
   236                  }
   237                />
   238              </Grid>
   239            </Grid>
   240          ) : null}
   241          {hasSites && !isSiteInfoLoading ? (
   242            <HelpBox
   243              title={"Site Replication"}
   244              iconComponent={<ClustersIcon />}
   245              help={
   246                <Fragment>
   247                  This feature allows multiple independent MinIO sites (or
   248                  clusters) that are using the same external IDentity Provider
   249                  (IDP) to be configured as replicas. In this situation the set of
   250                  replica sites are referred to as peer sites or just sites.
   251                  <br />
   252                  <br />
   253                  Initially, only one of the sites added for replication may have
   254                  data. After site-replication is successfully configured, this
   255                  data is replicated to the other (initially empty) sites.
   256                  Subsequently, objects may be written to any of the sites, and
   257                  they will be replicated to all other sites.
   258                  <br />
   259                  <br />
   260                  All sites must have the same deployment credentials (i.e.
   261                  MINIO_ROOT_USER, MINIO_ROOT_PASSWORD).
   262                  <br />
   263                  <br />
   264                  All sites must be using the same external IDP(s) if any.
   265                  <br />
   266                  <br />
   267                  For SSE-S3 or SSE-KMS encryption via KMS, all sites must have
   268                  access to a central KMS deployment server.
   269                  <br />
   270                  <br />
   271                  You can learn more at our{" "}
   272                  <a
   273                    href="https://github.com/minio/minio/tree/master/docs/site-replication?ref=con"
   274                    target="_blank"
   275                    rel="noopener"
   276                  >
   277                    documentation
   278                  </a>
   279                  .
   280                </Fragment>
   281              }
   282            />
   283          ) : null}
   284  
   285          {deleteAll ? (
   286            <ConfirmDialog
   287              title={`Delete All`}
   288              confirmText={"Delete"}
   289              isOpen={true}
   290              titleIcon={<ConfirmDeleteIcon />}
   291              isLoading={false}
   292              onConfirm={() => {
   293                const siteNames = sites.map((s: any) => s.name);
   294                removeSites(true, siteNames);
   295              }}
   296              onClose={() => {
   297                setIsDeleteAll(false);
   298              }}
   299              confirmationContent={
   300                <Fragment>
   301                  Are you sure you want to remove all the replication sites?.
   302                </Fragment>
   303              }
   304            />
   305          ) : null}
   306        </PageLayout>
   307      </Fragment>
   308    );
   309  };
   310  
   311  export default SiteReplication;