Moved EditBucketReplication to screen (#3037)
This commit is contained in:
@@ -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
|
||||
],
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user