From 8dd94f5336aae5645944e7838ebdf0047de9b720 Mon Sep 17 00:00:00 2001
From: jinapurapu <65002498+jinapurapu@users.noreply.github.com>
Date: Wed, 6 Sep 2023 16:24:07 -0700
Subject: [PATCH] Moved EditBucketReplication to screen (#3037)
---
.../src/common/SecureComponent/permissions.ts | 4 +
.../BucketDetails/BucketReplicationPanel.tsx | 8 +-
.../BucketDetails/EditBucketReplication.tsx | 335 ++++++++++++++++++
portal-ui/src/screens/Console/Console.tsx | 14 +
portal-ui/src/screens/Console/helpTopics.json | 76 +++-
5 files changed, 430 insertions(+), 7 deletions(-)
create mode 100644 portal-ui/src/screens/Console/Buckets/BucketDetails/EditBucketReplication.tsx
diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts
index 79aa9d7c9..8a68e37b6 100644
--- a/portal-ui/src/common/SecureComponent/permissions.ts
+++ b/portal-ui/src/common/SecureComponent/permissions.ts
@@ -140,6 +140,7 @@ export const IAM_PAGES = {
BUCKETS: "/buckets",
ADD_BUCKETS: "add-bucket",
BUCKETS_ADMIN_VIEW: ":bucketName/admin/*",
+ BUCKETS_EDIT_REPLICATION: "/buckets/edit-replication",
/* Object Browser */
OBJECT_BROWSER_VIEW: "/browser",
OBJECT_BROWSER_BUCKET_VIEW: "/browser/:bucketName",
@@ -293,6 +294,9 @@ export const IAM_PAGES_PERMISSIONS = {
[IAM_PAGES.ADD_BUCKETS]: [
IAM_SCOPES.S3_CREATE_BUCKET, // create bucket page
],
+ [IAM_PAGES.BUCKETS_EDIT_REPLICATION]: [
+ ...IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN], // edit bucket replication bucket page
+ ],
[IAM_PAGES.BUCKETS_ADMIN_VIEW]: [
...IAM_PERMISSIONS[IAM_ROLES.BUCKET_ADMIN], // bucket admin page
],
diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx
index f1b0ec6a0..e026d43a5 100644
--- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx
+++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx
@@ -16,7 +16,7 @@
import React, { Fragment, useEffect, useState } from "react";
import { useSelector } from "react-redux";
-import { useParams } from "react-router-dom";
+import { useNavigate, useParams } from "react-router-dom";
import {
AddIcon,
Box,
@@ -153,10 +153,12 @@ const BucketReplicationPanel = () => {
setDeleteSelectedRules(true);
setDeleteReplicationModal(true);
};
-
+ const navigate = useNavigate();
const editReplicationRule = (replication: BucketReplicationRule) => {
setSelectedRRule(replication.id);
- setEditReplicationModal(true);
+ navigate(
+ `/buckets/edit-replication?bucketName=${bucketName}&ruleID=${replication.id}`,
+ );
};
const ruleDestDisplay = (events: BucketReplicationDestination) => {
diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/EditBucketReplication.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/EditBucketReplication.tsx
new file mode 100644
index 000000000..42932b1f2
--- /dev/null
+++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/EditBucketReplication.tsx
@@ -0,0 +1,335 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2023 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 { useNavigate } from "react-router-dom";
+import {
+ BackLink,
+ Box,
+ BucketReplicationIcon,
+ Button,
+ FormLayout,
+ Grid,
+ HelpBox,
+ InputBox,
+ PageLayout,
+ ReadBox,
+ Switch,
+} from "mds";
+import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
+import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
+import { useAppDispatch } from "../../../../store";
+import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
+import HelpMenu from "../../HelpMenu";
+import { api } from "api";
+import { errorToHandler } from "api/errors";
+import QueryMultiSelector from "screens/Console/Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
+
+const EditBucketReplication = () => {
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+ let params = new URLSearchParams(document.location.search);
+
+ const bucketName = params.get("bucketName") || "";
+ const ruleID = params.get("ruleID") || "";
+
+ useEffect(() => {
+ dispatch(setHelpName("bucket-replication-edit"));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const backLink = IAM_PAGES.BUCKETS + `/${bucketName}/admin/replication`;
+
+ const [editLoading, setEditLoading] = useState(true);
+ const [saveEdit, setSaveEdit] = useState(false);
+ const [priority, setPriority] = useState("1");
+ const [destination, setDestination] = useState("");
+ const [prefix, setPrefix] = useState("");
+ const [repDeleteMarker, setRepDeleteMarker] = useState(false);
+ const [metadataSync, setMetadataSync] = useState(false);
+ const [initialTags, setInitialTags] = useState("");
+ const [tags, setTags] = useState("");
+ const [targetStorageClass, setTargetStorageClass] = useState("");
+ const [repExisting, setRepExisting] = useState(false);
+ const [repDelete, setRepDelete] = useState(false);
+ const [ruleState, setRuleState] = useState(false);
+
+ useEffect(() => {
+ if (editLoading && bucketName && ruleID) {
+ api.buckets
+
+ .getBucketReplicationRule(bucketName, ruleID)
+ .then((res) => {
+ setPriority(res.data.priority ? res.data.priority.toString() : "");
+ const pref = res.data.prefix || "";
+ const tag = res.data.tags || "";
+ setPrefix(pref);
+ setInitialTags(tag);
+ setTags(tag);
+ setDestination(res.data.destination?.bucket || "");
+ setRepDeleteMarker(res.data.delete_marker_replication || false);
+ setTargetStorageClass(res.data.storageClass || "");
+ setRepExisting(!!res.data.existingObjects);
+ setRepDelete(!!res.data.deletes_replication);
+ setRuleState(res.data.status === "Enabled");
+ setMetadataSync(!!res.data.metadata_replication);
+
+ setEditLoading(false);
+ })
+ .catch((err) => {
+ dispatch(setErrorSnackMessage(errorToHandler(err.error)));
+ setEditLoading(false);
+ });
+ }
+ }, [editLoading, dispatch, bucketName, ruleID]);
+
+ useEffect(() => {
+ if (saveEdit && bucketName && ruleID) {
+ const remoteBucketsInfo = {
+ arn: destination,
+ ruleState: ruleState,
+ prefix: prefix,
+ tags: tags,
+ replicateDeleteMarkers: repDeleteMarker,
+ replicateDeletes: repDelete,
+ replicateExistingObjects: repExisting,
+ replicateMetadata: metadataSync,
+ priority: parseInt(priority),
+ storageClass: targetStorageClass,
+ };
+
+ api.buckets
+ .updateMultiBucketReplication(bucketName, ruleID, remoteBucketsInfo)
+ .then(() => {
+ navigate(backLink);
+ })
+ .catch((err) => {
+ dispatch(setErrorSnackMessage(errorToHandler(err.error)));
+ setSaveEdit(false);
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ saveEdit,
+ bucketName,
+ ruleID,
+ destination,
+ prefix,
+ tags,
+ repDeleteMarker,
+ priority,
+ repDelete,
+ repExisting,
+ ruleState,
+ metadataSync,
+ targetStorageClass,
+ dispatch,
+ ]);
+
+ return (
+
+ navigate(backLink)}
+ />
+ }
+ actions={}
+ />
+
+
+
+
+ );
+};
+
+export default EditBucketReplication;
diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx
index 2665453b9..b42a41eb8 100644
--- a/portal-ui/src/screens/Console/Console.tsx
+++ b/portal-ui/src/screens/Console/Console.tsx
@@ -99,6 +99,10 @@ const ObjectManager = React.lazy(
const ObjectBrowser = React.lazy(() => import("./ObjectBrowser/ObjectBrowser"));
const Buckets = React.lazy(() => import("./Buckets/Buckets"));
+
+const EditBucketReplication = React.lazy(
+ () => import("./Buckets/BucketDetails/EditBucketReplication"),
+);
const Policies = React.lazy(() => import("./Policies/Policies"));
const AddPolicyScreen = React.lazy(() => import("./Policies/AddPolicyScreen"));
@@ -278,6 +282,16 @@ const Console = ({ classes }: IConsoleProps) => {
return hasPermission("*", IAM_PAGES_PERMISSIONS[IAM_PAGES.ADD_BUCKETS]);
},
},
+ {
+ component: EditBucketReplication,
+ path: IAM_PAGES.BUCKETS_EDIT_REPLICATION,
+ customPermissionFnc: () => {
+ return hasPermission(
+ "*",
+ IAM_PAGES_PERMISSIONS[IAM_PAGES.BUCKETS_EDIT_REPLICATION],
+ );
+ },
+ },
{
component: Buckets,
path: IAM_PAGES.BUCKETS_ADMIN_VIEW,
diff --git a/portal-ui/src/screens/Console/helpTopics.json b/portal-ui/src/screens/Console/helpTopics.json
index adde359a4..c11d1907d 100644
--- a/portal-ui/src/screens/Console/helpTopics.json
+++ b/portal-ui/src/screens/Console/helpTopics.json
@@ -451,19 +451,19 @@
"img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
"title": "How is failed replication handled?",
"url": "https://min.io/docs/minio/kubernetes/upstream/administration/bucket-replication.html#minio-replication-process",
- "body": "MinIO queues failed replication operations and retries those operations until replication succeeds."
+ "body": "MinIO queues failed replication operations and retries those operations until replication succeeds."
},
{
"img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
"title": "What are Replication Workers?",
"url": "https://min.io/docs/minio/windows/administration/bucket-replication.html#minio-replication-process",
- "body": "MinIO uses a replication queuing system with multiple concurrent replication workers operating on that queue."
+ "body": "MinIO uses a replication queuing system with multiple concurrent replication workers operating on that queue."
},
{
"img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
"title": "What are Settings and Configurations?",
"url": "https://min.io/docs/minio/windows/administration/console/managing-deployment.html#minio-console-settings",
- "body": "These configuration settings define runtime behavior of the MinIO server process, comparable to the mc admin config command"
+ "body": "These configuration settings define runtime behavior of the MinIO server process, comparable to the mc admin config command"
},
{
"img": "https://blog.min.io/content/images/size/w1000/2020/12/pay_banner-01-01-01-01-01.png",
@@ -508,7 +508,7 @@
"img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
"title": "Site Failure Recovery",
"url": "https://min.io/docs/minio/windows/operations/data-recovery/recover-after-site-failure.html#minio-restore-hardware-failure-site",
- "body": "MinIO can make the loss of an entire site, while significant, a relatively minor incident. Site recovery depends on the replication option you use for the site."
+ "body": "MinIO can make the loss of an entire site, while significant, a relatively minor incident. Site recovery depends on the replication option you use for the site."
},
{
"img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
@@ -3619,5 +3619,73 @@
"header": null,
"links": []
}
+ },
+ "bucket-replication-edit": {
+ "docs": {
+ "header": null,
+ "links": [
+ {
+ "img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
+ "title": "Bucket Replication Requirements",
+ "url": "https://min.io/docs/minio/kubernetes/upstream/administration/bucket-replication/bucket-replication-requirements.html",
+ "body": "Check here to ensure you meet the prerequisites before setting up any replication configurations."
+ },
+ {
+ "img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
+ "title": "Bucket Replication",
+ "url": "https://min.io/docs/minio/kubernetes/upstream/administration/bucket-replication.html#minio-bucket-replication-serverside",
+ "body": "MinIO server-side bucket replication is an automatic bucket-level configuration that synchronizes objects between a source and destination bucket."
+ },
+
+ {
+ "img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
+ "title": "Replication Internals",
+ "url": "https://min.io/docs/minio/windows/administration/bucket-replication.html#minio-replication-process",
+ "body": "Learn details of the MinIO replication process."
+ },
+
+ {
+ "img": "https://min.io/resources/img/logo/MINIO_wordmark.png",
+ "title": "SUBNET Registration and Support",
+ "url": "https://min.io/docs/minio/linux/administration/console/subnet-registration.html",
+ "body": "Learn how to register your MinIO deployment and access our SUBNET support system"
+ },
+
+ {
+ "img": "https://blog.min.io/content/images/size/w1000/2020/12/pay_banner-01-01-01-01-01.png",
+ "title": "Troubleshooting",
+ "url": "https://min.io/docs/minio/linux/operations/troubleshooting.html",
+ "body": "Need more help? Check out additional Troubleshooting options"
+ }
+ ]
+ },
+ "video": {
+ "header": null,
+ "links": [
+ {
+ "img": "https://i.ytimg.com/vi/89vnToCcoAw/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAxsMWCpBYqwqEy0xULLLdcIVKkeA",
+ "title": "Replication Lab I",
+ "url": "https://www.youtube.com/watch?v=89vnToCcoAw",
+ "body": "Demonstrates bucket replication concepts using MinIO Client, including active-passive and active-active replication."
+ },
+ {
+ "img": "https://i.ytimg.com/vi/G4wQZEsIxcU/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBTPmOjC6YVBAmuyHwPK1OMfTrXoA",
+ "title": "Bucket Replication Overview",
+ "url": "https://www.youtube.com/watch?v=G4wQZEsIxcU",
+ "body": "In this video, we will cover bucket level replication, both active-passive and active-active."
+ }
+ ]
+ },
+ "blog": {
+ "header": null,
+ "links": [
+ {
+ "img": "https://blog.min.io/content/images/size/w1000/2022/12/replication-bestpractices.jpg",
+ "title": "Replication Best Practices",
+ "url": "https://blog.min.io/minio-replication-best-practices/",
+ "body": "A detailed tutorial guiding you through setting up MinIO with a well-configured replication architecture."
+ }
+ ]
+ }
}
}