From ae34d886a92eabcb2e0898613b4232aaea928cb1 Mon Sep 17 00:00:00 2001
From: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
Date: Thu, 14 Apr 2022 20:19:45 +0000
Subject: [PATCH] Add site in a new page (#1845)
---
.../src/common/SecureComponent/permissions.ts | 5 +
.../SiteReplication/AddReplicationSites.tsx | 627 ++++++++++++++++++
.../AddReplicationSitesModal.tsx | 419 ------------
.../SiteReplication/ReplicationSites.tsx | 2 +-
.../SiteReplication/SiteReplication.tsx | 46 +-
portal-ui/src/screens/Console/Console.tsx | 9 +
6 files changed, 672 insertions(+), 436 deletions(-)
create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx
delete mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSitesModal.tsx
diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts
index 59f60484e..aabc35c3e 100644
--- a/portal-ui/src/common/SecureComponent/permissions.ts
+++ b/portal-ui/src/common/SecureComponent/permissions.ts
@@ -163,6 +163,7 @@ export const IAM_PAGES = {
TIERS_ADD_SERVICE: "/settings/tiers/add/:service",
SITE_REPLICATION: "/settings/site-replication",
SITE_REPLICATION_STATUS: "/settings/site-replication/status",
+ SITE_REPLICATION_ADD: "/settings/site-replication/add",
/* Operator */
TENANTS: "/tenants",
@@ -389,6 +390,10 @@ export const IAM_PAGES_PERMISSIONS = {
IAM_SCOPES.ADMIN_SERVER_INFO,
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
],
+ [IAM_PAGES.SITE_REPLICATION_ADD]: [
+ IAM_SCOPES.ADMIN_SERVER_INFO,
+ IAM_SCOPES.ADMIN_CONFIG_UPDATE,
+ ],
};
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";
diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx
new file mode 100644
index 000000000..7c418dc40
--- /dev/null
+++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSites.tsx
@@ -0,0 +1,627 @@
+// 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 .
+
+import React, { Fragment, useEffect, useState } from "react";
+import { AddIcon, ClustersIcon, RemoveIcon } from "../../../../icons";
+import { ReplicationSite } from "./SiteReplication";
+import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
+import Grid from "@mui/material/Grid";
+import { Box, Button, LinearProgress } from "@mui/material";
+import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton";
+import useApi from "../../Common/Hooks/useApi";
+import { connect } from "react-redux";
+import { setErrorSnackMessage, setSnackBarMessage } from "../../../../actions";
+import { ErrorResponseHandler } from "../../../../common/types";
+import PageHeader from "../../Common/PageHeader/PageHeader";
+import BackLink from "../../../../common/BackLink";
+import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
+import PageLayout from "../../Common/Layout/PageLayout";
+import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
+import HelpBox from "../../../../common/HelpBox";
+import history from "../../../../history";
+
+type SiteInputRow = {
+ name: string;
+ endpoint: string;
+};
+
+const isValidEndPoint = (ep: string) => {
+ let isValidEndPointUrl = false;
+
+ try {
+ new URL(ep);
+ isValidEndPointUrl = true;
+ } catch (err) {
+ isValidEndPointUrl = false;
+ }
+ if (isValidEndPointUrl || ep === "") {
+ return "";
+ } else {
+ return "Invalid Endpoint";
+ }
+};
+const AddReplicationSites = ({
+ setErrorSnackMessage,
+ setSnackBarMessage,
+}: {
+ existingSites: ReplicationSite[];
+ onClose: () => void;
+ setErrorSnackMessage: (err: ErrorResponseHandler) => void;
+ setSnackBarMessage: (msg: string) => void;
+}) => {
+ const [existingSites, setExistingSites] = useState([]);
+
+ const [accessKey, setAccessKey] = useState("");
+ const [secretKey, setSecretKey] = useState("");
+ const [siteConfig, setSiteConfig] = useState([]);
+
+ const setDefaultNewRows = () => {
+ const defaultNewSites = existingSites?.length
+ ? [{ endpoint: "", name: "" }]
+ : [
+ { endpoint: "", name: "" },
+ { endpoint: "", name: "" },
+ ];
+ setSiteConfig(defaultNewSites);
+ };
+
+ const [isSiteInfoLoading, invokeSiteInfoApi] = useApi(
+ (res: any) => {
+ const { sites: siteList, name: curSiteName } = res;
+ // current site name to be the fist one.
+ const foundIdx = siteList.findIndex((el: any) => el.name === curSiteName);
+ if (foundIdx !== -1) {
+ let curSite = siteList[foundIdx];
+ curSite = {
+ ...curSite,
+ isCurrent: true,
+ };
+ siteList.splice(foundIdx, 1, curSite);
+ }
+
+ siteList.sort((x: any, y: any) => {
+ return x.name === curSiteName ? -1 : y.name === curSiteName ? 1 : 0;
+ });
+ setExistingSites(siteList);
+ },
+ (err: any) => {
+ setExistingSites([]);
+ }
+ );
+
+ const getSites = () => {
+ invokeSiteInfoApi("GET", `api/v1/admin/site-replication`);
+ };
+
+ useEffect(() => {
+ getSites();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ setDefaultNewRows();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [existingSites]);
+
+ const isAllEndpointsValid =
+ siteConfig.reduce((acc: string[], cv, i) => {
+ const epValue = siteConfig[i].endpoint;
+ const isEpValid = isValidEndPoint(epValue);
+
+ if (isEpValid === "" && epValue !== "") {
+ acc.push(isEpValid);
+ }
+ return acc;
+ }, []).length === siteConfig.length;
+
+ const [isAdding, invokeSiteAddApi] = useApi(
+ (res: any) => {
+ if (res.success) {
+ setSnackBarMessage(res.status);
+ resetForm();
+ getSites();
+ history.push(IAM_PAGES.SITE_REPLICATION);
+ } else {
+ setErrorSnackMessage({
+ errorMessage: "Error",
+ detailedError: res.status,
+ });
+ }
+ },
+ (err: any) => {
+ setErrorSnackMessage(err);
+ }
+ );
+
+ const resetForm = () => {
+ setAccessKey("");
+ setSecretKey("");
+ setDefaultNewRows();
+ };
+
+ const addSiteReplication = () => {
+ const existingSitesToAdd = existingSites?.map((es, idx) => {
+ return {
+ accessKey: accessKey,
+ secretKey: secretKey,
+ name: es.name,
+ endpoint: es.endpoint,
+ };
+ });
+
+ const newSitesToAdd = siteConfig.reduce((acc: any, ns, idx) => {
+ if (ns.endpoint) {
+ acc.push({
+ accessKey: accessKey,
+ secretKey: secretKey,
+ name: ns.name || `dr-site-${idx}`,
+ endpoint: ns.endpoint,
+ });
+ }
+ return acc;
+ }, []);
+
+ invokeSiteAddApi("POST", `api/v1/admin/site-replication`, [
+ ...(existingSitesToAdd || []),
+ ...(newSitesToAdd || []),
+ ]);
+ };
+
+ return (
+
+
+ }
+ />
+
+
+
+ {isSiteInfoLoading || isAdding ? : null}
+
+
+
+
+
+
+
+
+
+
+
+ About Site Replication
+
+
+
+ The following changes are replicated to all other sites
+
+
+
+ Creation and deletion of buckets and objects
+
+
+
+
+ Creation and deletion of all IAM users, groups, policies
+ and their mappings to users or groups
+
+
+
+
Creation of STS credentials
+
+
+
+ Creation and deletion of service accounts (except those
+ owned by the root user)
+
+
+
+
+ Changes to Bucket features such as
+
+
Bucket Policies
+
Bucket Tags
+
Bucket Object-Lock configurations
+
Bucket Encryption configuration
+
+
+
+
+
+
+ The following Bucket features will NOT be replicated
+
+
Bucket notification configuration
+
Bucket lifecycle (ILM) configuration
+
+
+
+
+
+ }
+ />
+
+
+
+ );
+};
+
+const connector = connect(null, {
+ setErrorSnackMessage,
+ setSnackBarMessage,
+});
+export default connector(AddReplicationSites);
diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSitesModal.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSitesModal.tsx
deleted file mode 100644
index 36dabd513..000000000
--- a/portal-ui/src/screens/Console/Configurations/SiteReplication/AddReplicationSitesModal.tsx
+++ /dev/null
@@ -1,419 +0,0 @@
-// 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 .
-
-import React, { Fragment, useState } from "react";
-import { AddIcon, RecoverIcon, RemoveIcon } from "../../../../icons";
-import { ReplicationSite } from "./SiteReplication";
-import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
-import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
-import Grid from "@mui/material/Grid";
-import { Box, Button, LinearProgress } from "@mui/material";
-import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton";
-import useApi from "../../Common/Hooks/useApi";
-import { connect } from "react-redux";
-import { setErrorSnackMessage, setSnackBarMessage } from "../../../../actions";
-import { ErrorResponseHandler } from "../../../../common/types";
-
-type SiteInputRow = {
- name: string;
- endpoint: string;
-};
-
-const isValidEndPoint = (ep: string) => {
- let isValidEndPointUrl = false;
-
- try {
- new URL(ep);
- isValidEndPointUrl = true;
- } catch (err) {
- isValidEndPointUrl = false;
- }
- if (isValidEndPointUrl || ep === "") {
- return "";
- } else {
- return "Invalid Endpoint";
- }
-};
-const AddReplicationSitesModal = ({
- existingSites = [],
- onClose,
- setErrorSnackMessage,
- setSnackBarMessage,
-}: {
- existingSites: ReplicationSite[];
- onClose: () => void;
- setErrorSnackMessage: (err: ErrorResponseHandler) => void;
- setSnackBarMessage: (msg: string) => void;
-}) => {
- const defaultNewSites = existingSites?.length
- ? [{ endpoint: "", name: "" }]
- : [
- { endpoint: "", name: "" },
- { endpoint: "", name: "" },
- ];
-
- const [accessKey, setAccessKey] = useState("");
- const [secretKey, setSecretKey] = useState("");
- const [siteConfig, setSiteConfig] = useState(defaultNewSites);
-
- const isAllEndpointsValid =
- siteConfig.reduce((acc: string[], cv, i) => {
- const epValue = siteConfig[i].endpoint;
- const isEpValid = isValidEndPoint(epValue);
-
- if (isEpValid === "" && epValue !== "") {
- acc.push(isEpValid);
- }
- return acc;
- }, []).length === siteConfig.length;
-
- const [isAdding, invokeSiteAddApi] = useApi(
- (res: any) => {
- if (res.success) {
- setSnackBarMessage(res.status);
- onClose();
- } else {
- setErrorSnackMessage({
- errorMessage: "Error",
- detailedError: res.status,
- });
- }
- },
- (err: any) => {
- setErrorSnackMessage(err);
- }
- );
-
- const resetForm = () => {
- setAccessKey("");
- setSecretKey("");
- setSiteConfig(defaultNewSites);
- };
-
- const addSiteReplication = () => {
- const existingSitesToAdd = existingSites?.map((es, idx) => {
- return {
- accessKey: accessKey,
- secretKey: secretKey,
- name: es.name,
- endpoint: es.endpoint,
- };
- });
-
- const newSitesToAdd = siteConfig.reduce((acc: any, ns, idx) => {
- if (ns.endpoint) {
- acc.push({
- accessKey: accessKey,
- secretKey: secretKey,
- name: ns.name || `dr-site-${idx}`,
- endpoint: ns.endpoint,
- });
- }
- return acc;
- }, []);
-
- invokeSiteAddApi("POST", `api/v1/admin/site-replication`, [
- ...(existingSitesToAdd || []),
- ...(newSitesToAdd || []),
- ]);
- };
-
- return (
- }
- data-test-id={"add-site-replication-modal"}
- >
- {isAdding ? : null}
-
-
- );
-};
-
-const connector = connect(null, {
- setErrorSnackMessage,
- setSnackBarMessage,
-});
-export default connector(AddReplicationSitesModal);
diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx
index 23371709e..78d3fc9f1 100644
--- a/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx
+++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx
@@ -128,7 +128,7 @@ const ReplicationSites = ({
const key = `${siteInfo.name}`;
return (
-
+ {
const [sites, setSites] = useState([]);
- const [isAddOpen, setIsAddOpen] = useState(false);
-
const [deleteAll, setIsDeleteAll] = useState(false);
const [isSiteInfoLoading, invokeSiteInfoApi] = useApi(
(res: any) => {
@@ -148,7 +150,7 @@ const SiteReplication = ({
disabled={isRemoving}
icon={}
onClick={() => {
- setIsAddOpen(true);
+ history.push(IAM_PAGES.SITE_REPLICATION_ADD);
}}
/>
@@ -185,19 +187,9 @@ const SiteReplication = ({
) : null}
- {isAddOpen ? (
- {
- setIsAddOpen(false);
- getSites();
- }}
- />
- ) : null}
-
}
+ iconComponent={}
help={
This feature allows multiple independent MinIO sites (or clusters)
@@ -205,6 +197,28 @@ const SiteReplication = ({
configured as replicas. In this situation the set of replica sites
are referred to as peer sites or just sites.
+
+
+
+ Initially, only one of the sites added for replication may
+ have data. After site-replication is successfully
+ configured, this data is replicated to the other (initially
+ empty) sites. Subsequently, objects may be written to any of
+ the sites, and they will be replicated to all other sites.
+
+
+ All sites must have the same deployment credentials (i.e.
+ MINIO_ROOT_USER, MINIO_ROOT_PASSWORD).
+
+
+ All sites must be using the same external IDP(s) if any.
+
+
+ For SSE-S3 or SSE-KMS encryption via KMS, all sites must
+ have access to a central KMS deployment. server.
+