github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/EditBucketReplication.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2023 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 BackLink, 21 Box, 22 BucketReplicationIcon, 23 Button, 24 FormLayout, 25 Grid, 26 HelpBox, 27 InputBox, 28 PageLayout, 29 ReadBox, 30 Switch, 31 } from "mds"; 32 import { IAM_PAGES } from "../../../../common/SecureComponent/permissions"; 33 import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice"; 34 import { useAppDispatch } from "../../../../store"; 35 import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper"; 36 import HelpMenu from "../../HelpMenu"; 37 import { api } from "api"; 38 import { errorToHandler } from "api/errors"; 39 import QueryMultiSelector from "screens/Console/Common/FormComponents/QueryMultiSelector/QueryMultiSelector"; 40 41 const EditBucketReplication = () => { 42 const dispatch = useAppDispatch(); 43 const navigate = useNavigate(); 44 let params = new URLSearchParams(document.location.search); 45 46 const bucketName = params.get("bucketName") || ""; 47 const ruleID = params.get("ruleID") || ""; 48 49 useEffect(() => { 50 dispatch(setHelpName("bucket-replication-edit")); 51 // eslint-disable-next-line react-hooks/exhaustive-deps 52 }, []); 53 54 const backLink = IAM_PAGES.BUCKETS + `/${bucketName}/admin/replication`; 55 56 const [editLoading, setEditLoading] = useState<boolean>(true); 57 const [saveEdit, setSaveEdit] = useState<boolean>(false); 58 const [priority, setPriority] = useState<string>("1"); 59 const [destination, setDestination] = useState<string>(""); 60 const [prefix, setPrefix] = useState<string>(""); 61 const [repDeleteMarker, setRepDeleteMarker] = useState<boolean>(false); 62 const [metadataSync, setMetadataSync] = useState<boolean>(false); 63 const [initialTags, setInitialTags] = useState<string>(""); 64 const [tags, setTags] = useState<string>(""); 65 const [targetStorageClass, setTargetStorageClass] = useState<string>(""); 66 const [repExisting, setRepExisting] = useState<boolean>(false); 67 const [repDelete, setRepDelete] = useState<boolean>(false); 68 const [ruleState, setRuleState] = useState<boolean>(false); 69 70 useEffect(() => { 71 if (editLoading && bucketName && ruleID) { 72 api.buckets 73 74 .getBucketReplicationRule(bucketName, ruleID) 75 .then((res) => { 76 setPriority(res.data.priority ? res.data.priority.toString() : ""); 77 const pref = res.data.prefix || ""; 78 const tag = res.data.tags || ""; 79 setPrefix(pref); 80 setInitialTags(tag); 81 setTags(tag); 82 setDestination(res.data.destination?.bucket || ""); 83 setRepDeleteMarker(res.data.delete_marker_replication || false); 84 setTargetStorageClass(res.data.storageClass || ""); 85 setRepExisting(!!res.data.existingObjects); 86 setRepDelete(!!res.data.deletes_replication); 87 setRuleState(res.data.status === "Enabled"); 88 setMetadataSync(!!res.data.metadata_replication); 89 90 setEditLoading(false); 91 }) 92 .catch((err) => { 93 dispatch(setErrorSnackMessage(errorToHandler(err.error))); 94 setEditLoading(false); 95 }); 96 } 97 }, [editLoading, dispatch, bucketName, ruleID]); 98 99 useEffect(() => { 100 if (saveEdit && bucketName && ruleID) { 101 const remoteBucketsInfo = { 102 arn: destination, 103 ruleState: ruleState, 104 prefix: prefix, 105 tags: tags, 106 replicateDeleteMarkers: repDeleteMarker, 107 replicateDeletes: repDelete, 108 replicateExistingObjects: repExisting, 109 replicateMetadata: metadataSync, 110 priority: parseInt(priority), 111 storageClass: targetStorageClass, 112 }; 113 114 api.buckets 115 .updateMultiBucketReplication(bucketName, ruleID, remoteBucketsInfo) 116 .then(() => { 117 navigate(backLink); 118 }) 119 .catch((err) => { 120 dispatch(setErrorSnackMessage(errorToHandler(err.error))); 121 setSaveEdit(false); 122 }); 123 } 124 // eslint-disable-next-line react-hooks/exhaustive-deps 125 }, [ 126 saveEdit, 127 bucketName, 128 ruleID, 129 destination, 130 prefix, 131 tags, 132 repDeleteMarker, 133 priority, 134 repDelete, 135 repExisting, 136 ruleState, 137 metadataSync, 138 targetStorageClass, 139 dispatch, 140 ]); 141 142 return ( 143 <Fragment> 144 <PageHeaderWrapper 145 label={ 146 <BackLink 147 label={"Edit Bucket Replication"} 148 onClick={() => navigate(backLink)} 149 /> 150 } 151 actions={<HelpMenu />} 152 /> 153 <PageLayout> 154 <form 155 noValidate 156 autoComplete="off" 157 onSubmit={(e: React.FormEvent<HTMLFormElement>) => { 158 e.preventDefault(); 159 setSaveEdit(true); 160 }} 161 > 162 <FormLayout 163 containerPadding={false} 164 withBorders={false} 165 helpBox={ 166 <HelpBox 167 iconComponent={<BucketReplicationIcon />} 168 title="Bucket Replication Configuration" 169 help={ 170 <Fragment> 171 <Box sx={{ paddingTop: "10px" }}> 172 For each write operation to the bucket, MinIO checks all 173 configured replication rules for the bucket and applies 174 the matching rule with highest configured priority. 175 </Box> 176 <Box sx={{ paddingTop: "10px" }}> 177 MinIO supports enabling replication of existing objects in 178 a bucket. 179 </Box> 180 <Box sx={{ paddingTop: "10px" }}> 181 MinIO does not enable existing object replication by 182 default. Objects created before replication was configured 183 or while replication is disabled are not synchronized to 184 the target deployment unless replication of existing 185 objects is enabled. 186 </Box> 187 <Box sx={{ paddingTop: "10px" }}> 188 MinIO supports replicating delete operations, where MinIO 189 synchronizes deleting specific object versions and new 190 delete markers. Delete operation replication uses the same 191 replication process as all other replication operations. 192 </Box>{" "} 193 </Fragment> 194 } 195 /> 196 } 197 > 198 <Switch 199 checked={ruleState} 200 id="ruleState" 201 name="ruleState" 202 label="Rule State" 203 onChange={(e) => { 204 setRuleState(e.target.checked); 205 }} 206 /> 207 <ReadBox label={"Destination"} sx={{ width: "100%" }}> 208 {destination} 209 </ReadBox> 210 <InputBox 211 id="priority" 212 name="priority" 213 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 214 if (e.target.validity.valid) { 215 setPriority(e.target.value); 216 } 217 }} 218 label="Priority" 219 value={priority} 220 pattern={"[0-9]*"} 221 /> 222 <InputBox 223 id="storageClass" 224 name="storageClass" 225 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 226 setTargetStorageClass(e.target.value); 227 }} 228 placeholder="STANDARD_IA,REDUCED_REDUNDANCY etc" 229 label="Storage Class" 230 value={targetStorageClass} 231 /> 232 <fieldset className={"inputItem"}> 233 <legend>Object Filters</legend> 234 <InputBox 235 id="prefix" 236 name="prefix" 237 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 238 setPrefix(e.target.value); 239 }} 240 placeholder="prefix" 241 label="Prefix" 242 value={prefix} 243 /> 244 <QueryMultiSelector 245 name="tags" 246 label="Tags" 247 elements={initialTags} 248 onChange={(vl: string) => { 249 setTags(vl); 250 }} 251 keyPlaceholder="Tag Key" 252 valuePlaceholder="Tag Value" 253 withBorder 254 /> 255 </fieldset> 256 <fieldset className={"inputItem"}> 257 <legend>Replication Options</legend> 258 <Switch 259 checked={repExisting} 260 id="repExisting" 261 name="repExisting" 262 label="Existing Objects" 263 onChange={(e) => { 264 setRepExisting(e.target.checked); 265 }} 266 description={"Replicate existing objects"} 267 /> 268 <Switch 269 checked={metadataSync} 270 id="metadatataSync" 271 name="metadatataSync" 272 label="Metadata Sync" 273 onChange={(e) => { 274 setMetadataSync(e.target.checked); 275 }} 276 description={"Metadata Sync"} 277 /> 278 <Switch 279 checked={repDeleteMarker} 280 id="deleteMarker" 281 name="deleteMarker" 282 label="Delete Marker" 283 onChange={(e) => { 284 setRepDeleteMarker(e.target.checked); 285 }} 286 description={"Replicate soft deletes"} 287 /> 288 <Switch 289 checked={repDelete} 290 id="repDelete" 291 name="repDelete" 292 label="Deletes" 293 onChange={(e) => { 294 setRepDelete(e.target.checked); 295 }} 296 description={"Replicate versioned deletes"} 297 /> 298 </fieldset> 299 <Grid 300 item 301 xs={12} 302 sx={{ 303 display: "flex", 304 flexDirection: "row", 305 justifyContent: "end", 306 gap: 10, 307 paddingTop: 10, 308 }} 309 > 310 <Button 311 id={"cancel-edit-replication"} 312 type="button" 313 variant="regular" 314 disabled={editLoading || saveEdit} 315 onClick={() => { 316 navigate(backLink); 317 }} 318 label={"Cancel"} 319 /> 320 <Button 321 id={"save-replication"} 322 type="submit" 323 variant="callAction" 324 disabled={editLoading || saveEdit} 325 label={"Save"} 326 /> 327 </Grid> 328 </FormLayout> 329 </form> 330 </PageLayout> 331 </Fragment> 332 ); 333 }; 334 335 export default EditBucketReplication;