github.com/minio/console@v1.4.1/web-app/src/screens/Console/Configurations/TiersConfiguration/ListTiersConfiguration.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, useEffect, useState } from "react"; 18 import get from "lodash/get"; 19 import { useSelector } from "react-redux"; 20 import { useNavigate } from "react-router-dom"; 21 import { 22 ActionLink, 23 AddIcon, 24 Box, 25 Button, 26 DataTable, 27 Grid, 28 HelpBox, 29 PageLayout, 30 ProgressBar, 31 RefreshIcon, 32 TierOfflineIcon, 33 TierOnlineIcon, 34 TiersIcon, 35 TiersNotAvailableIcon, 36 } from "mds"; 37 import { api } from "api"; 38 import { errorToHandler } from "api/errors"; 39 import { Tier } from "api/consoleApi"; 40 import { actionsTray } from "../../Common/FormComponents/common/styleLibrary"; 41 import { 42 CONSOLE_UI_RESOURCE, 43 IAM_PAGES, 44 IAM_SCOPES, 45 } from "../../../../common/SecureComponent/permissions"; 46 import { 47 hasPermission, 48 SecureComponent, 49 } from "../../../../common/SecureComponent"; 50 import { tierTypes } from "./utils"; 51 52 import { 53 selDistSet, 54 setErrorSnackMessage, 55 setHelpName, 56 } from "../../../../systemSlice"; 57 import { useAppDispatch } from "../../../../store"; 58 import SearchBox from "../../Common/SearchBox"; 59 import withSuspense from "../../Common/Components/withSuspense"; 60 import DistributedOnly from "../../Common/DistributedOnly/DistributedOnly"; 61 import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; 62 import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper"; 63 import HelpMenu from "../../HelpMenu"; 64 65 const UpdateTierCredentialsModal = withSuspense( 66 React.lazy(() => import("./UpdateTierCredentialsModal")), 67 ); 68 69 const ListTiersConfiguration = () => { 70 const dispatch = useAppDispatch(); 71 const navigate = useNavigate(); 72 73 const distributedSetup = useSelector(selDistSet); 74 const [records, setRecords] = useState<Tier[]>([]); 75 const [filter, setFilter] = useState<string>(""); 76 const [isLoading, setIsLoading] = useState<boolean>(true); 77 const [updateCredentialsOpen, setUpdateCredentialsOpen] = 78 useState<boolean>(false); 79 const [selectedTier, setSelectedTier] = useState<Tier>({ 80 type: "unsupported", 81 status: false, 82 }); 83 const hasSetTier = hasPermission(CONSOLE_UI_RESOURCE, [ 84 IAM_SCOPES.ADMIN_SET_TIER, 85 ]); 86 87 useEffect(() => { 88 if (isLoading) { 89 if (distributedSetup) { 90 const fetchRecords = () => { 91 api.admin 92 .tiersList() 93 .then((res) => { 94 setRecords(res.data.items || []); 95 setIsLoading(false); 96 }) 97 .catch((err) => { 98 dispatch(setErrorSnackMessage(errorToHandler(err.error))); 99 setIsLoading(false); 100 }); 101 }; 102 fetchRecords(); 103 } else { 104 setIsLoading(false); 105 } 106 } 107 }, [isLoading, dispatch, distributedSetup]); 108 109 const filteredRecords = records.filter((b: Tier) => { 110 if (filter === "") { 111 return true; 112 } 113 const getItemName = get(b, `${b.type}.name`, ""); 114 const getItemType = get(b, `type`, ""); 115 116 return getItemName.indexOf(filter) >= 0 || getItemType.indexOf(filter) >= 0; 117 }); 118 119 const addTier = () => { 120 navigate(IAM_PAGES.TIERS_ADD); 121 }; 122 123 const renderTierName = (item: Tier) => { 124 const name = get(item, `${item.type}.name`, ""); 125 126 if (name !== null) { 127 return <b>{name}</b>; 128 } 129 130 return ""; 131 }; 132 133 const renderTierType = (item: string) => { 134 const { logoXs } = 135 tierTypes.find((tierConf) => tierConf.serviceName === item) || {}; 136 if (item) { 137 return ( 138 <Box 139 sx={{ 140 display: "flex", 141 alignItems: "center", 142 "& .min-icon": { 143 width: "18px", 144 height: "22px", 145 }, 146 }} 147 > 148 {logoXs} 149 </Box> 150 ); 151 } 152 return ""; 153 }; 154 155 const renderTierStatus = (item: boolean) => { 156 if (item) { 157 return ( 158 <Grid 159 container 160 sx={{ 161 display: "flex", 162 alignItems: "center", 163 justifyItems: "start", 164 color: "#4CCB92", 165 fontSize: "8px", 166 flexDirection: "column", 167 }} 168 > 169 <TierOnlineIcon style={{ fill: "#4CCB92", width: 14, height: 14 }} /> 170 ONLINE 171 </Grid> 172 ); 173 } 174 return ( 175 <Grid 176 container 177 sx={{ 178 display: "flex", 179 flexDirection: "column", 180 alignItems: "center", 181 color: "#C83B51", 182 fontSize: "8px", 183 }} 184 > 185 <TierOfflineIcon style={{ fill: "#C83B51", width: 14, height: 14 }} /> 186 OFFLINE 187 </Grid> 188 ); 189 }; 190 191 const renderTierPrefix = (item: Tier) => { 192 const prefix = get(item, `${item.type}.prefix`, ""); 193 194 if (prefix !== null) { 195 return prefix; 196 } 197 198 return ""; 199 }; 200 201 const renderTierEndpoint = (item: Tier) => { 202 const endpoint = get(item, `${item.type}.endpoint`, ""); 203 204 if (endpoint !== null) { 205 return endpoint; 206 } 207 208 return ""; 209 }; 210 211 const renderTierBucket = (item: Tier) => { 212 const bucket = get(item, `${item.type}.bucket`, ""); 213 214 if (bucket !== null) { 215 return bucket; 216 } 217 218 return ""; 219 }; 220 221 const renderTierRegion = (item: Tier) => { 222 const region = get(item, `${item.type}.region`, ""); 223 224 if (region !== null) { 225 return region; 226 } 227 228 return ""; 229 }; 230 231 const renderTierUsage = (item: Tier) => { 232 const endpoint = get(item, `${item.type}.usage`, ""); 233 234 if (endpoint !== null) { 235 return endpoint; 236 } 237 238 return ""; 239 }; 240 241 const renderTierObjects = (item: Tier) => { 242 const endpoint = get(item, `${item.type}.objects`, ""); 243 244 if (endpoint !== null) { 245 return endpoint; 246 } 247 248 return ""; 249 }; 250 251 const renderTierVersions = (item: Tier) => { 252 const endpoint = get(item, `${item.type}.versions`, ""); 253 254 if (endpoint !== null) { 255 return endpoint; 256 } 257 258 return ""; 259 }; 260 261 const closeTierCredentials = () => { 262 setUpdateCredentialsOpen(false); 263 }; 264 265 useEffect(() => { 266 dispatch(setHelpName("list-tiers-configuration")); 267 // eslint-disable-next-line react-hooks/exhaustive-deps 268 }, []); 269 270 return ( 271 <Fragment> 272 {updateCredentialsOpen && ( 273 <UpdateTierCredentialsModal 274 open={updateCredentialsOpen} 275 tierData={selectedTier} 276 closeModalAndRefresh={closeTierCredentials} 277 /> 278 )} 279 <PageHeaderWrapper label="Tiers" actions={<HelpMenu />} /> 280 281 <PageLayout> 282 {!distributedSetup ? ( 283 <DistributedOnly 284 entity={"Tiers"} 285 iconComponent={<TiersNotAvailableIcon />} 286 /> 287 ) : ( 288 <Fragment> 289 <Grid item xs={12} sx={actionsTray.actionsTray}> 290 <SearchBox 291 placeholder="Filter" 292 onChange={setFilter} 293 value={filter} 294 sx={{ 295 marginRight: "auto", 296 maxWidth: 380, 297 }} 298 /> 299 300 <Box 301 sx={{ 302 display: "flex", 303 flexWrap: "nowrap", 304 gap: 5, 305 }} 306 > 307 <Button 308 id={"refresh-list"} 309 icon={<RefreshIcon />} 310 label={`Refresh List`} 311 onClick={() => { 312 setIsLoading(true); 313 }} 314 /> 315 <TooltipWrapper 316 tooltip={ 317 hasSetTier 318 ? "" 319 : "You require additional permissions in order to create a new Tier. Please ask your MinIO administrator to grant you " + 320 IAM_SCOPES.ADMIN_SET_TIER + 321 " permission in order to create a Tier." 322 } 323 > 324 <SecureComponent 325 scopes={[IAM_SCOPES.ADMIN_SET_TIER]} 326 resource={CONSOLE_UI_RESOURCE} 327 errorProps={{ disabled: true }} 328 > 329 <Button 330 id={"add-tier"} 331 icon={<AddIcon />} 332 label={`Create Tier`} 333 onClick={addTier} 334 variant="callAction" 335 /> 336 </SecureComponent> 337 </TooltipWrapper> 338 </Box> 339 </Grid> 340 {isLoading && <ProgressBar />} 341 {!isLoading && ( 342 <Fragment> 343 {records.length > 0 && ( 344 <Fragment> 345 <Grid item xs={12}> 346 <SecureComponent 347 scopes={[IAM_SCOPES.ADMIN_LIST_TIERS]} 348 resource={CONSOLE_UI_RESOURCE} 349 errorProps={{ disabled: true }} 350 > 351 <DataTable 352 itemActions={[ 353 { 354 type: "edit", 355 onClick: (tierData: Tier) => { 356 setSelectedTier(tierData); 357 setUpdateCredentialsOpen(true); 358 }, 359 }, 360 ]} 361 columns={[ 362 { 363 label: "Tier Name", 364 elementKey: "type", 365 renderFunction: renderTierName, 366 renderFullObject: true, 367 }, 368 { 369 label: "Status", 370 elementKey: "status", 371 renderFunction: renderTierStatus, 372 width: 50, 373 }, 374 { 375 label: "Type", 376 elementKey: "type", 377 renderFunction: renderTierType, 378 width: 50, 379 }, 380 { 381 label: "Endpoint", 382 elementKey: "type", 383 renderFunction: renderTierEndpoint, 384 renderFullObject: true, 385 }, 386 { 387 label: "Bucket", 388 elementKey: "type", 389 renderFunction: renderTierBucket, 390 renderFullObject: true, 391 }, 392 { 393 label: "Prefix", 394 elementKey: "type", 395 renderFunction: renderTierPrefix, 396 renderFullObject: true, 397 }, 398 { 399 label: "Region", 400 elementKey: "type", 401 renderFunction: renderTierRegion, 402 renderFullObject: true, 403 }, 404 { 405 label: "Usage", 406 elementKey: "type", 407 renderFunction: renderTierUsage, 408 renderFullObject: true, 409 }, 410 { 411 label: "Objects", 412 elementKey: "type", 413 renderFunction: renderTierObjects, 414 renderFullObject: true, 415 }, 416 { 417 label: "Versions", 418 elementKey: "type", 419 renderFunction: renderTierVersions, 420 renderFullObject: true, 421 }, 422 ]} 423 isLoading={isLoading} 424 records={filteredRecords} 425 entityName="Tiers" 426 idField="service_name" 427 customPaperHeight={"400px"} 428 /> 429 </SecureComponent> 430 </Grid> 431 <Grid 432 item 433 xs={12} 434 sx={{ 435 marginTop: "15px", 436 }} 437 > 438 <HelpBox 439 title={"Learn more about TIERS"} 440 iconComponent={<TiersIcon />} 441 help={ 442 <Fragment> 443 Tiers are used by the MinIO Object Lifecycle 444 Management which allows creating rules for time or 445 date based automatic transition or expiry of 446 objects. For object transition, MinIO automatically 447 moves the object to a configured remote storage 448 tier. 449 <br /> 450 <br /> 451 You can learn more at our{" "} 452 <a 453 href="https://min.io/docs/minio/linux/administration/object-management/object-lifecycle-management.html?ref=con" 454 target="_blank" 455 rel="noopener" 456 > 457 documentation 458 </a> 459 . 460 </Fragment> 461 } 462 /> 463 </Grid> 464 </Fragment> 465 )} 466 {records.length === 0 && ( 467 <HelpBox 468 title={"Tiers"} 469 iconComponent={<TiersIcon />} 470 help={ 471 <Fragment> 472 Tiers are used by the MinIO Object Lifecycle Management 473 which allows creating rules for time or date based 474 automatic transition or expiry of objects. For object 475 transition, MinIO automatically moves the object to a 476 configured remote storage tier. 477 <br /> 478 <br /> 479 {hasSetTier ? ( 480 <div> 481 To get started,{" "} 482 <ActionLink 483 isLoading={false} 484 label={""} 485 onClick={addTier} 486 > 487 Create Tier 488 </ActionLink> 489 . 490 </div> 491 ) : ( 492 "" 493 )} 494 </Fragment> 495 } 496 /> 497 )} 498 </Fragment> 499 )} 500 </Fragment> 501 )} 502 </PageLayout> 503 </Fragment> 504 ); 505 }; 506 507 export default ListTiersConfiguration;