github.com/minio/console@v1.4.1/web-app/src/screens/Console/Configurations/TiersConfiguration/AddTierConfiguration.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, { Fragment, useCallback, useEffect, useState } from "react"; 18 19 import { useNavigate, useParams } from "react-router-dom"; 20 import get from "lodash/get"; 21 import { 22 BackLink, 23 breakPoints, 24 Button, 25 FileSelector, 26 Grid, 27 InputBox, 28 PageLayout, 29 SectionTitle, 30 } from "mds"; 31 import { api } from "api"; 32 import { errorToHandler } from "api/errors"; 33 import { ApiError } from "api/consoleApi"; 34 import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary"; 35 import { 36 azureServiceName, 37 gcsServiceName, 38 minioServiceName, 39 s3ServiceName, 40 tierTypes, 41 } from "./utils"; 42 import { IAM_PAGES } from "../../../../common/SecureComponent/permissions"; 43 import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice"; 44 import { useAppDispatch } from "../../../../store"; 45 import RegionSelectWrapper from "./RegionSelectWrapper"; 46 import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper"; 47 import HelpMenu from "../../HelpMenu"; 48 49 const AddTierConfiguration = () => { 50 const dispatch = useAppDispatch(); 51 const navigate = useNavigate(); 52 const params = useParams(); 53 54 //Local States 55 const [saving, setSaving] = useState<boolean>(false); 56 57 // Form Items 58 const [name, setName] = useState<string>(""); 59 const [endpoint, setEndpoint] = useState<string>(""); 60 const [bucket, setBucket] = useState<string>(""); 61 const [prefix, setPrefix] = useState<string>(""); 62 const [region, setRegion] = useState<string>(""); 63 const [storageClass, setStorageClass] = useState<string>(""); 64 65 const [accessKey, setAccessKey] = useState<string>(""); 66 const [secretKey, setSecretKey] = useState<string>(""); 67 68 const [creds, setCreds] = useState<string>(""); 69 const [encodedCreds, setEncodedCreds] = useState<string>(""); 70 71 const [accountName, setAccountName] = useState<string>(""); 72 const [accountKey, setAccountKey] = useState<string>(""); 73 74 const [titleSelection, setTitleSelection] = useState<string>(""); 75 76 const type = get(params, "service", "s3"); 77 78 // Validations 79 const [isFormValid, setIsFormValid] = useState<boolean>(true); 80 const [nameInputError, setNameInputError] = useState<string>(""); 81 82 // Extra validation functions 83 84 const validName = useCallback(() => { 85 const patternAgainst = /^[A-Z0-9-_]+$/; // Only allow uppercase, numbers, dashes and underscores 86 if (patternAgainst.test(name)) { 87 setNameInputError(""); 88 return true; 89 } 90 91 setNameInputError( 92 "Please verify that string is uppercase only and contains valid characters (numbers, dashes & underscores).", 93 ); 94 return false; 95 }, [name]); 96 97 //Effects 98 99 useEffect(() => { 100 if (saving) { 101 let request = {}; 102 let fields = { 103 name, 104 endpoint, 105 bucket, 106 prefix, 107 region, 108 }; 109 110 let tierType = type; 111 112 switch (type) { 113 case "minio": 114 request = { 115 minio: { 116 ...fields, 117 accesskey: accessKey, 118 secretkey: secretKey, 119 }, 120 }; 121 break; 122 case "s3": 123 request = { 124 s3: { 125 ...fields, 126 accesskey: accessKey, 127 secretkey: secretKey, 128 storageclass: storageClass, 129 }, 130 }; 131 break; 132 case "gcs": 133 request = { 134 gcs: { 135 ...fields, 136 creds: encodedCreds, 137 }, 138 }; 139 break; 140 case "azure": 141 request = { 142 azure: { 143 ...fields, 144 accountname: accountName, 145 accountkey: accountKey, 146 }, 147 }; 148 } 149 150 let payload = { 151 type: tierType as 152 | "azure" 153 | "s3" 154 | "minio" 155 | "gcs" 156 | "unsupported" 157 | undefined, 158 ...request, 159 }; 160 161 api.admin 162 .addTier(payload) 163 .then(() => { 164 setSaving(false); 165 navigate(IAM_PAGES.TIERS); 166 }) 167 .catch(async (res) => { 168 const err = (await res.json()) as ApiError; 169 setSaving(false); 170 dispatch(setErrorSnackMessage(errorToHandler(err))); 171 }); 172 } 173 }, [ 174 accessKey, 175 accountKey, 176 accountName, 177 bucket, 178 encodedCreds, 179 endpoint, 180 name, 181 prefix, 182 region, 183 saving, 184 secretKey, 185 dispatch, 186 storageClass, 187 type, 188 navigate, 189 ]); 190 191 useEffect(() => { 192 let valid = true; 193 if (type === "") { 194 valid = false; 195 } 196 if (name === "" || !validName()) { 197 valid = false; 198 } 199 if (endpoint === "") { 200 valid = false; 201 } 202 if (bucket === "") { 203 valid = false; 204 } 205 if (region === "" && type !== "minio") { 206 valid = false; 207 } 208 209 if (type === "s3" || type === "minio") { 210 if (accessKey === "") { 211 valid = false; 212 } 213 if (secretKey === "") { 214 valid = false; 215 } 216 } 217 218 if (type === "gcs") { 219 if (encodedCreds === "") { 220 valid = false; 221 } 222 } 223 224 if (type === "azure") { 225 if (accountName === "") { 226 valid = false; 227 } 228 if (accountKey === "") { 229 valid = false; 230 } 231 } 232 233 setIsFormValid(valid); 234 }, [ 235 accessKey, 236 accountKey, 237 accountName, 238 bucket, 239 encodedCreds, 240 endpoint, 241 isFormValid, 242 name, 243 prefix, 244 region, 245 secretKey, 246 storageClass, 247 type, 248 validName, 249 ]); 250 251 useEffect(() => { 252 switch (type) { 253 case "gcs": 254 setEndpoint("https://storage.googleapis.com"); 255 setTitleSelection("Google Cloud"); 256 break; 257 case "s3": 258 setEndpoint("https://s3.amazonaws.com"); 259 setTitleSelection("Amazon S3"); 260 break; 261 case "azure": 262 setEndpoint("http://blob.core.windows.net"); 263 setTitleSelection("Azure"); 264 break; 265 case "minio": 266 setEndpoint(""); 267 setTitleSelection("MinIO"); 268 } 269 }, [type]); 270 271 //Fetch Actions 272 const submitForm = (event: React.FormEvent) => { 273 event.preventDefault(); 274 setSaving(true); 275 }; 276 277 // Input actions 278 const updateTierName = (e: React.ChangeEvent<HTMLInputElement>) => { 279 setName(e.target.value.toUpperCase()); 280 }; 281 282 const targetElement = tierTypes.find((item) => item.serviceName === type); 283 284 useEffect(() => { 285 dispatch(setHelpName("add-tier-configuration")); 286 // eslint-disable-next-line react-hooks/exhaustive-deps 287 }, []); 288 289 return ( 290 <Fragment> 291 <PageHeaderWrapper 292 label={ 293 <Fragment> 294 <BackLink 295 label={"Add Tier"} 296 onClick={() => navigate(IAM_PAGES.TIERS_ADD)} 297 /> 298 </Fragment> 299 } 300 actions={<HelpMenu />} 301 /> 302 303 <PageLayout> 304 <Grid 305 item 306 xs={12} 307 sx={{ 308 border: "1px solid #eaeaea", 309 padding: "25px", 310 }} 311 > 312 <form noValidate onSubmit={submitForm}> 313 {type !== "" && targetElement ? ( 314 <SectionTitle icon={targetElement.logo} sx={{ marginBottom: 20 }}> 315 {titleSelection ? titleSelection : ""} - Add Tier Configuration 316 </SectionTitle> 317 ) : null} 318 <Grid 319 item 320 xs={12} 321 sx={{ 322 display: "grid", 323 gridTemplateColumns: "1fr 1fr", 324 gridAutoFlow: "row", 325 gridRowGap: 20, 326 gridColumnGap: 50, 327 [`@media (max-width: ${breakPoints.sm}px)`]: { 328 gridTemplateColumns: "1fr", 329 gridAutoFlow: "dense", 330 }, 331 }} 332 > 333 {type !== "" && ( 334 <Fragment> 335 <InputBox 336 id="name" 337 name="name" 338 label="Name" 339 placeholder="Enter Name (Eg. REMOTE-TIER)" 340 value={name} 341 onChange={updateTierName} 342 error={nameInputError} 343 required 344 /> 345 <InputBox 346 id="endpoint" 347 name="endpoint" 348 label="Endpoint" 349 placeholder="Enter Endpoint" 350 value={endpoint} 351 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 352 setEndpoint(e.target.value); 353 }} 354 required 355 /> 356 {(type === s3ServiceName || type === minioServiceName) && ( 357 <Fragment> 358 <InputBox 359 id="accessKey" 360 name="accessKey" 361 label="Access Key" 362 placeholder="Enter Access Key" 363 value={accessKey} 364 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 365 setAccessKey(e.target.value); 366 }} 367 required 368 /> 369 <InputBox 370 id="secretKey" 371 name="secretKey" 372 label="Secret Key" 373 placeholder="Enter Secret Key" 374 value={secretKey} 375 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 376 setSecretKey(e.target.value); 377 }} 378 required 379 /> 380 </Fragment> 381 )} 382 {type === gcsServiceName && ( 383 <FileSelector 384 accept=".json" 385 id="creds" 386 label="Credentials" 387 name="creds" 388 returnEncodedData 389 onChange={(_, fileName, encodedValue) => { 390 if (encodedValue) { 391 setEncodedCreds(encodedValue); 392 setCreds(fileName); 393 } 394 }} 395 value={creds} 396 required 397 /> 398 )} 399 {type === azureServiceName && ( 400 <Fragment> 401 <InputBox 402 id="accountName" 403 name="accountName" 404 label="Account Name" 405 placeholder="Enter Account Name" 406 value={accountName} 407 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 408 setAccountName(e.target.value); 409 }} 410 required 411 /> 412 <InputBox 413 id="accountKey" 414 name="accountKey" 415 label="Account Key" 416 placeholder="Enter Account Key" 417 value={accountKey} 418 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 419 setAccountKey(e.target.value); 420 }} 421 required 422 /> 423 </Fragment> 424 )} 425 <InputBox 426 id="bucket" 427 name="bucket" 428 label="Bucket" 429 placeholder="Enter Bucket" 430 value={bucket} 431 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 432 setBucket(e.target.value); 433 }} 434 required 435 /> 436 <InputBox 437 id="prefix" 438 name="prefix" 439 label="Prefix" 440 placeholder="Enter Prefix" 441 value={prefix} 442 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 443 setPrefix(e.target.value); 444 }} 445 /> 446 <RegionSelectWrapper 447 onChange={(value) => { 448 setRegion(value); 449 }} 450 required={type !== "minio"} 451 label={"Region"} 452 id="region" 453 type={type as "azure" | "s3" | "minio" | "gcs"} 454 /> 455 {type === s3ServiceName && ( 456 <InputBox 457 id="storageClass" 458 name="storageClass" 459 label="Storage Class" 460 placeholder="Enter Storage Class" 461 value={storageClass} 462 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 463 setStorageClass(e.target.value); 464 }} 465 /> 466 )} 467 </Fragment> 468 )} 469 </Grid> 470 <Grid item xs={12} sx={modalStyleUtils.modalButtonBar}> 471 <Button 472 id={"save-tier-configuration"} 473 type="submit" 474 variant="callAction" 475 disabled={saving || !isFormValid} 476 label={"Save Tier Configuration"} 477 /> 478 </Grid> 479 </form> 480 </Grid> 481 </PageLayout> 482 </Fragment> 483 ); 484 }; 485 486 export default AddTierConfiguration;