Moved EditBucketReplication to screen (#3037)

This commit is contained in:
jinapurapu
2023-09-06 16:24:07 -07:00
committed by GitHub
parent 1dc21b9a21
commit 8dd94f5336
5 changed files with 430 additions and 7 deletions

View File

@@ -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
],

View File

@@ -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) => {

View File

@@ -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 <http://www.gnu.org/licenses/>.
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<boolean>(true);
const [saveEdit, setSaveEdit] = useState<boolean>(false);
const [priority, setPriority] = useState<string>("1");
const [destination, setDestination] = useState<string>("");
const [prefix, setPrefix] = useState<string>("");
const [repDeleteMarker, setRepDeleteMarker] = useState<boolean>(false);
const [metadataSync, setMetadataSync] = useState<boolean>(false);
const [initialTags, setInitialTags] = useState<string>("");
const [tags, setTags] = useState<string>("");
const [targetStorageClass, setTargetStorageClass] = useState<string>("");
const [repExisting, setRepExisting] = useState<boolean>(false);
const [repDelete, setRepDelete] = useState<boolean>(false);
const [ruleState, setRuleState] = useState<boolean>(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 (
<Fragment>
<PageHeaderWrapper
label={
<BackLink
label={"Edit Bucket Replication"}
onClick={() => navigate(backLink)}
/>
}
actions={<HelpMenu />}
/>
<PageLayout>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSaveEdit(true);
}}
>
<FormLayout
containerPadding={false}
withBorders={false}
helpBox={
<HelpBox
iconComponent={<BucketReplicationIcon />}
title="Bucket Replication Configuration"
help={
<Fragment>
<Box sx={{ paddingTop: "10px" }}>
For each write operation to the bucket, MinIO checks all
configured replication rules for the bucket and applies
the matching rule with highest configured priority.
</Box>
<Box sx={{ paddingTop: "10px" }}>
MinIO supports enabling replication of existing objects in
a bucket.
</Box>
<Box sx={{ paddingTop: "10px" }}>
MinIO does not enable existing object replication by
default. Objects created before replication was configured
or while replication is disabled are not synchronized to
the target deployment unless replication of existing
objects is enabled.
</Box>
<Box sx={{ paddingTop: "10px" }}>
MinIO supports replicating delete operations, where MinIO
synchronizes deleting specific object versions and new
delete markers. Delete operation replication uses the same
replication process as all other replication operations.
</Box>{" "}
</Fragment>
}
/>
}
>
<Switch
checked={ruleState}
id="ruleState"
name="ruleState"
label="Rule State"
onChange={(e) => {
setRuleState(e.target.checked);
}}
/>
<ReadBox label={"Destination"} sx={{ width: "100%" }}>
{destination}
</ReadBox>
<InputBox
id="priority"
name="priority"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.validity.valid) {
setPriority(e.target.value);
}
}}
label="Priority"
value={priority}
pattern={"[0-9]*"}
/>
<InputBox
id="storageClass"
name="storageClass"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTargetStorageClass(e.target.value);
}}
placeholder="STANDARD_IA,REDUCED_REDUNDANCY etc"
label="Storage Class"
value={targetStorageClass}
/>
<fieldset className={"inputItem"}>
<legend>Object Filters</legend>
<InputBox
id="prefix"
name="prefix"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
placeholder="prefix"
label="Prefix"
value={prefix}
/>
<QueryMultiSelector
name="tags"
label="Tags"
elements={initialTags}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
</fieldset>
<fieldset className={"inputItem"}>
<legend>Replication Options</legend>
<Switch
checked={repExisting}
id="repExisting"
name="repExisting"
label="Existing Objects"
onChange={(e) => {
setRepExisting(e.target.checked);
}}
description={"Replicate existing objects"}
/>
<Switch
checked={metadataSync}
id="metadatataSync"
name="metadatataSync"
label="Metadata Sync"
onChange={(e) => {
setMetadataSync(e.target.checked);
}}
description={"Metadata Sync"}
/>
<Switch
checked={repDeleteMarker}
id="deleteMarker"
name="deleteMarker"
label="Delete Marker"
onChange={(e) => {
setRepDeleteMarker(e.target.checked);
}}
description={"Replicate soft deletes"}
/>
<Switch
checked={repDelete}
id="repDelete"
name="repDelete"
label="Deletes"
onChange={(e) => {
setRepDelete(e.target.checked);
}}
description={"Replicate versioned deletes"}
/>
</fieldset>
<Grid
item
xs={12}
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "end",
gap: 10,
paddingTop: 10,
}}
>
<Button
id={"cancel-edit-replication"}
type="button"
variant="regular"
disabled={editLoading || saveEdit}
onClick={() => {
navigate(backLink);
}}
label={"Cancel"}
/>
<Button
id={"save-replication"}
type="submit"
variant="callAction"
disabled={editLoading || saveEdit}
label={"Save"}
/>
</Grid>
</FormLayout>
</form>
</PageLayout>
</Fragment>
);
};
export default EditBucketReplication;

View File

@@ -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,

View File

@@ -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."
}
]
}
}
}