github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/EditReplicationModal.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 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, { useEffect, useState } from "react"; 18 import { 19 BucketReplicationIcon, 20 Button, 21 FormLayout, 22 InputBox, 23 ReadBox, 24 Switch, 25 Grid, 26 } from "mds"; 27 import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; 28 import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector"; 29 import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary"; 30 import { setModalErrorSnackMessage } from "../../../../systemSlice"; 31 import { useAppDispatch } from "../../../../store"; 32 import { api } from "api"; 33 import { errorToHandler } from "api/errors"; 34 35 interface IEditReplicationModal { 36 closeModalAndRefresh: (refresh: boolean) => void; 37 open: boolean; 38 bucketName: string; 39 ruleID: string; 40 } 41 42 const EditReplicationModal = ({ 43 closeModalAndRefresh, 44 open, 45 bucketName, 46 ruleID, 47 }: IEditReplicationModal) => { 48 const dispatch = useAppDispatch(); 49 const [editLoading, setEditLoading] = useState<boolean>(true); 50 const [saveEdit, setSaveEdit] = useState<boolean>(false); 51 const [priority, setPriority] = useState<string>("1"); 52 const [destination, setDestination] = useState<string>(""); 53 const [prefix, setPrefix] = useState<string>(""); 54 const [repDeleteMarker, setRepDeleteMarker] = useState<boolean>(false); 55 const [metadataSync, setMetadataSync] = useState<boolean>(false); 56 const [initialTags, setInitialTags] = useState<string>(""); 57 const [tags, setTags] = useState<string>(""); 58 const [targetStorageClass, setTargetStorageClass] = useState<string>(""); 59 const [repExisting, setRepExisting] = useState<boolean>(false); 60 const [repDelete, setRepDelete] = useState<boolean>(false); 61 const [ruleState, setRuleState] = useState<boolean>(false); 62 63 useEffect(() => { 64 if (editLoading) { 65 api.buckets 66 .getBucketReplicationRule(bucketName, ruleID) 67 .then((res) => { 68 setPriority(res.data.priority ? res.data.priority.toString() : ""); 69 const pref = res.data.prefix || ""; 70 const tag = res.data.tags || ""; 71 setPrefix(pref); 72 setInitialTags(tag); 73 setTags(tag); 74 setDestination(res.data.destination?.bucket || ""); 75 setRepDeleteMarker(res.data.delete_marker_replication || false); 76 setTargetStorageClass(res.data.storageClass || ""); 77 setRepExisting(!!res.data.existingObjects); 78 setRepDelete(!!res.data.deletes_replication); 79 setRuleState(res.data.status === "Enabled"); 80 setMetadataSync(!!res.data.metadata_replication); 81 82 setEditLoading(false); 83 }) 84 .catch((err) => { 85 dispatch(setModalErrorSnackMessage(errorToHandler(err.error))); 86 setEditLoading(false); 87 }); 88 } 89 }, [editLoading, dispatch, bucketName, ruleID]); 90 91 useEffect(() => { 92 if (saveEdit) { 93 const remoteBucketsInfo = { 94 arn: destination, 95 ruleState: ruleState, 96 prefix: prefix, 97 tags: tags, 98 replicateDeleteMarkers: repDeleteMarker, 99 replicateDeletes: repDelete, 100 replicateExistingObjects: repExisting, 101 replicateMetadata: metadataSync, 102 priority: parseInt(priority), 103 storageClass: targetStorageClass, 104 }; 105 106 api.buckets 107 .updateMultiBucketReplication(bucketName, ruleID, remoteBucketsInfo) 108 .then(() => { 109 setSaveEdit(false); 110 closeModalAndRefresh(true); 111 }) 112 .catch((err) => { 113 dispatch(setModalErrorSnackMessage(errorToHandler(err.error))); 114 setSaveEdit(false); 115 }); 116 } 117 }, [ 118 saveEdit, 119 bucketName, 120 ruleID, 121 destination, 122 prefix, 123 tags, 124 repDeleteMarker, 125 priority, 126 repDelete, 127 repExisting, 128 ruleState, 129 metadataSync, 130 targetStorageClass, 131 closeModalAndRefresh, 132 dispatch, 133 ]); 134 135 return ( 136 <ModalWrapper 137 modalOpen={open} 138 onClose={() => { 139 closeModalAndRefresh(false); 140 }} 141 title="Edit Bucket Replication" 142 titleIcon={<BucketReplicationIcon />} 143 > 144 <form 145 noValidate 146 autoComplete="off" 147 onSubmit={(e: React.FormEvent<HTMLFormElement>) => { 148 e.preventDefault(); 149 setSaveEdit(true); 150 }} 151 > 152 <FormLayout containerPadding={false} withBorders={false}> 153 <Switch 154 checked={ruleState} 155 id="ruleState" 156 name="ruleState" 157 label="Rule State" 158 onChange={(e) => { 159 setRuleState(e.target.checked); 160 }} 161 /> 162 <ReadBox label={"Destination"} sx={{ width: "100%" }}> 163 {destination} 164 </ReadBox> 165 166 <InputBox 167 id="priority" 168 name="priority" 169 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 170 if (e.target.validity.valid) { 171 setPriority(e.target.value); 172 } 173 }} 174 label="Priority" 175 value={priority} 176 pattern={"[0-9]*"} 177 /> 178 <InputBox 179 id="storageClass" 180 name="storageClass" 181 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 182 setTargetStorageClass(e.target.value); 183 }} 184 placeholder="STANDARD_IA,REDUCED_REDUNDANCY etc" 185 label="Storage Class" 186 value={targetStorageClass} 187 /> 188 <fieldset className={"inputItem"}> 189 <legend>Object Filters</legend> 190 <InputBox 191 id="prefix" 192 name="prefix" 193 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 194 setPrefix(e.target.value); 195 }} 196 placeholder="prefix" 197 label="Prefix" 198 value={prefix} 199 /> 200 <QueryMultiSelector 201 name="tags" 202 label="Tags" 203 elements={initialTags} 204 onChange={(vl: string) => { 205 setTags(vl); 206 }} 207 keyPlaceholder="Tag Key" 208 valuePlaceholder="Tag Value" 209 withBorder 210 /> 211 </fieldset> 212 <fieldset className={"inputItem"}> 213 <legend>Replication Options</legend> 214 215 <Switch 216 checked={repExisting} 217 id="repExisting" 218 name="repExisting" 219 label="Existing Objects" 220 onChange={(e) => { 221 setRepExisting(e.target.checked); 222 }} 223 description={"Replicate existing objects"} 224 /> 225 226 <Switch 227 checked={metadataSync} 228 id="metadatataSync" 229 name="metadatataSync" 230 label="Metadata Sync" 231 onChange={(e) => { 232 setMetadataSync(e.target.checked); 233 }} 234 description={"Metadata Sync"} 235 /> 236 237 <Switch 238 checked={repDeleteMarker} 239 id="deleteMarker" 240 name="deleteMarker" 241 label="Delete Marker" 242 onChange={(e) => { 243 setRepDeleteMarker(e.target.checked); 244 }} 245 description={"Replicate soft deletes"} 246 /> 247 <Switch 248 checked={repDelete} 249 id="repDelete" 250 name="repDelete" 251 label="Deletes" 252 onChange={(e) => { 253 setRepDelete(e.target.checked); 254 }} 255 description={"Replicate versioned deletes"} 256 /> 257 </fieldset> 258 <Grid item xs={12} sx={modalStyleUtils.modalButtonBar}> 259 <Button 260 id={"cancel-edit-replication"} 261 type="button" 262 variant="regular" 263 disabled={editLoading || saveEdit} 264 onClick={() => { 265 closeModalAndRefresh(false); 266 }} 267 label={"Cancel"} 268 /> 269 <Button 270 id={"save-replication"} 271 type="submit" 272 variant="callAction" 273 disabled={editLoading || saveEdit} 274 label={"Save"} 275 /> 276 </Grid> 277 </FormLayout> 278 </form> 279 </ModalWrapper> 280 ); 281 }; 282 283 export default EditReplicationModal;