github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.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 { 20 AddIcon, 21 Button, 22 DataTable, 23 Grid, 24 HelpBox, 25 SectionTitle, 26 TiersIcon, 27 HelpTip, 28 } from "mds"; 29 import { useSelector } from "react-redux"; 30 import { api } from "api"; 31 import { ObjectBucketLifecycle } from "api/consoleApi"; 32 import { LifeCycleItem } from "../types"; 33 import { 34 hasPermission, 35 SecureComponent, 36 } from "../../../../common/SecureComponent"; 37 import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions"; 38 import { selBucketDetailsLoading } from "./bucketDetailsSlice"; 39 import { useParams } from "react-router-dom"; 40 import { setHelpName } from "../../../../systemSlice"; 41 import { useAppDispatch } from "../../../../store"; 42 import DeleteBucketLifecycleRule from "./DeleteBucketLifecycleRule"; 43 import EditLifecycleConfiguration from "./EditLifecycleConfiguration"; 44 import AddLifecycleModal from "./AddLifecycleModal"; 45 import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; 46 47 const BucketLifecyclePanel = () => { 48 const loadingBucket = useSelector(selBucketDetailsLoading); 49 const params = useParams(); 50 51 const [loadingLifecycle, setLoadingLifecycle] = useState<boolean>(true); 52 const [lifecycleRecords, setLifecycleRecords] = useState< 53 ObjectBucketLifecycle[] 54 >([]); 55 const [addLifecycleOpen, setAddLifecycleOpen] = useState<boolean>(false); 56 const [editLifecycleOpen, setEditLifecycleOpen] = useState<boolean>(false); 57 const [selectedLifecycleRule, setSelectedLifecycleRule] = 58 useState<LifeCycleItem | null>(null); 59 const [deleteLifecycleOpen, setDeleteLifecycleOpen] = 60 useState<boolean>(false); 61 const [selectedID, setSelectedID] = useState<string | null>(null); 62 const dispatch = useAppDispatch(); 63 64 const bucketName = params.bucketName || ""; 65 66 const displayLifeCycleRules = hasPermission(bucketName, [ 67 IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION, 68 IAM_SCOPES.S3_GET_ACTIONS, 69 ]); 70 71 useEffect(() => { 72 if (loadingBucket) { 73 setLoadingLifecycle(true); 74 } 75 }, [loadingBucket, setLoadingLifecycle]); 76 77 useEffect(() => { 78 dispatch(setHelpName("bucket_detail_lifecycle")); 79 // eslint-disable-next-line react-hooks/exhaustive-deps 80 }, []); 81 82 useEffect(() => { 83 if (loadingLifecycle) { 84 if (displayLifeCycleRules) { 85 api.buckets 86 .getBucketLifecycle(bucketName) 87 .then((res) => { 88 const records = get(res.data, "lifecycle", []); 89 setLifecycleRecords(records || []); 90 setLoadingLifecycle(false); 91 }) 92 .catch((err) => { 93 console.error(err.error); 94 setLifecycleRecords([]); 95 setLoadingLifecycle(false); 96 }); 97 } else { 98 setLoadingLifecycle(false); 99 } 100 } 101 }, [ 102 loadingLifecycle, 103 setLoadingLifecycle, 104 bucketName, 105 displayLifeCycleRules, 106 ]); 107 108 const closeEditLCAndRefresh = (refresh: boolean) => { 109 setEditLifecycleOpen(false); 110 setSelectedLifecycleRule(null); 111 if (refresh) { 112 setLoadingLifecycle(true); 113 } 114 }; 115 116 const closeAddLCAndRefresh = (refresh: boolean) => { 117 setAddLifecycleOpen(false); 118 if (refresh) { 119 setLoadingLifecycle(true); 120 } 121 }; 122 123 const closeDelLCRefresh = (refresh: boolean) => { 124 setDeleteLifecycleOpen(false); 125 setSelectedID(null); 126 127 if (refresh) { 128 setLoadingLifecycle(true); 129 } 130 }; 131 132 const renderStorageClass = (objectST: any) => { 133 let stClass = get(objectST, "transition.storage_class", ""); 134 stClass = get(objectST, "transition.noncurrent_storage_class", stClass); 135 136 return stClass; 137 }; 138 139 const lifecycleColumns = [ 140 { 141 label: "Type", 142 renderFullObject: true, 143 renderFunction: (el: LifeCycleItem) => { 144 if (!el) { 145 return <Fragment />; 146 } 147 if ( 148 el.expiration && 149 (el.expiration.days > 0 || 150 el.expiration.noncurrent_expiration_days || 151 (el.expiration.newer_noncurrent_expiration_versions && 152 el.expiration.newer_noncurrent_expiration_versions > 0)) 153 ) { 154 return <span>Expiry</span>; 155 } 156 if ( 157 el.transition && 158 (el.transition.days > 0 || el.transition.noncurrent_transition_days) 159 ) { 160 return <span>Transition</span>; 161 } 162 return <Fragment />; 163 }, 164 }, 165 { 166 label: "Version", 167 renderFullObject: true, 168 renderFunction: (el: LifeCycleItem) => { 169 if (!el) { 170 return <Fragment />; 171 } 172 if (el.expiration) { 173 if (el.expiration.days > 0) { 174 return <span>Current</span>; 175 } else if ( 176 el.expiration.noncurrent_expiration_days || 177 el.expiration.newer_noncurrent_expiration_versions 178 ) { 179 return <span>Non-Current</span>; 180 } 181 } 182 if (el.transition) { 183 if (el.transition.days > 0) { 184 return <span>Current</span>; 185 } else if (el.transition.noncurrent_transition_days) { 186 return <span>Non-Current</span>; 187 } 188 } 189 }, 190 }, 191 { 192 label: "Expire Delete Marker", 193 elementKey: "expire_delete_marker", 194 renderFunction: (el: LifeCycleItem) => { 195 if (!el) { 196 return <Fragment />; 197 } 198 if (el.expiration && el.expiration.delete_marker !== undefined) { 199 return <span>{el.expiration.delete_marker ? "true" : "false"}</span>; 200 } else { 201 return <Fragment />; 202 } 203 }, 204 renderFullObject: true, 205 }, 206 { 207 label: "Tier", 208 elementKey: "storage_class", 209 renderFunction: renderStorageClass, 210 renderFullObject: true, 211 }, 212 { 213 label: "Prefix", 214 elementKey: "prefix", 215 }, 216 { 217 label: "After", 218 renderFullObject: true, 219 renderFunction: (el: LifeCycleItem) => { 220 if (!el) { 221 return <Fragment />; 222 } 223 if (el.transition) { 224 if (el.transition.days > 0) { 225 return <span>{el.transition.days} days</span>; 226 } else if (el.transition.noncurrent_transition_days) { 227 return <span>{el.transition.noncurrent_transition_days} days</span>; 228 } 229 } 230 if (el.expiration) { 231 if (el.expiration.days > 0) { 232 return <span>{el.expiration.days} days</span>; 233 } else if (el.expiration.noncurrent_expiration_days) { 234 return <span>{el.expiration.noncurrent_expiration_days} days</span>; 235 } else { 236 return ( 237 <span> 238 {el.expiration.newer_noncurrent_expiration_versions} versions 239 </span> 240 ); 241 } 242 } 243 }, 244 }, 245 { 246 label: "Status", 247 elementKey: "status", 248 }, 249 ]; 250 251 const lifecycleActions = [ 252 { 253 type: "view", 254 255 onClick(valueToSend: any): any { 256 setSelectedLifecycleRule(valueToSend); 257 setEditLifecycleOpen(true); 258 }, 259 }, 260 { 261 type: "delete", 262 onClick(valueToDelete: string): any { 263 setSelectedID(valueToDelete); 264 setDeleteLifecycleOpen(true); 265 }, 266 sendOnlyId: true, 267 }, 268 ]; 269 270 return ( 271 <Fragment> 272 {editLifecycleOpen && selectedLifecycleRule && ( 273 <EditLifecycleConfiguration 274 open={editLifecycleOpen} 275 closeModalAndRefresh={closeEditLCAndRefresh} 276 selectedBucket={bucketName} 277 lifecycleRule={selectedLifecycleRule} 278 /> 279 )} 280 {addLifecycleOpen && ( 281 <AddLifecycleModal 282 open={addLifecycleOpen} 283 bucketName={bucketName} 284 closeModalAndRefresh={closeAddLCAndRefresh} 285 /> 286 )} 287 {deleteLifecycleOpen && selectedID && ( 288 <DeleteBucketLifecycleRule 289 id={selectedID} 290 bucket={bucketName} 291 deleteOpen={deleteLifecycleOpen} 292 onCloseAndRefresh={closeDelLCRefresh} 293 /> 294 )} 295 <SectionTitle 296 separator 297 sx={{ marginBottom: 15 }} 298 actions={ 299 <SecureComponent 300 scopes={[ 301 IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION, 302 IAM_SCOPES.S3_PUT_ACTIONS, 303 ]} 304 resource={bucketName} 305 matchAll 306 errorProps={{ disabled: true }} 307 > 308 <TooltipWrapper tooltip={"Add Lifecycle Rule"}> 309 <Button 310 id={"add-bucket-lifecycle-rule"} 311 onClick={() => { 312 setAddLifecycleOpen(true); 313 }} 314 label={"Add Lifecycle Rule"} 315 icon={<AddIcon />} 316 variant={"callAction"} 317 /> 318 </TooltipWrapper> 319 </SecureComponent> 320 } 321 > 322 <HelpTip 323 content={ 324 <Fragment> 325 MinIO derives it’s behavior and syntax from{" "} 326 <a 327 target="blank" 328 href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html" 329 > 330 S3 lifecycle 331 </a>{" "} 332 for compatibility in migrating workloads and lifecycle rules from 333 S3 to MinIO. 334 </Fragment> 335 } 336 placement="right" 337 > 338 Lifecycle Rules 339 </HelpTip> 340 </SectionTitle> 341 <Grid container> 342 <Grid item xs={12}> 343 <SecureComponent 344 scopes={[ 345 IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION, 346 IAM_SCOPES.S3_GET_ACTIONS, 347 ]} 348 resource={bucketName} 349 errorProps={{ disabled: true }} 350 > 351 <DataTable 352 itemActions={lifecycleActions} 353 columns={lifecycleColumns} 354 isLoading={loadingLifecycle} 355 records={lifecycleRecords} 356 entityName="Lifecycle" 357 customEmptyMessage="There are no Lifecycle rules yet" 358 idField="id" 359 customPaperHeight={"400px"} 360 /> 361 </SecureComponent> 362 </Grid> 363 {!loadingLifecycle && ( 364 <Grid item xs={12}> 365 <br /> 366 <HelpBox 367 title={"Lifecycle Rules"} 368 iconComponent={<TiersIcon />} 369 help={ 370 <Fragment> 371 MinIO Object Lifecycle Management allows creating rules for 372 time or date based automatic transition or expiry of objects. 373 For object transition, MinIO automatically moves the object to 374 a configured remote storage tier. 375 <br /> 376 <br /> 377 You can learn more at our{" "} 378 <a 379 href="https://min.io/docs/minio/linux/administration/object-management/object-lifecycle-management.html?ref=con" 380 target="_blank" 381 rel="noopener" 382 > 383 documentation 384 </a> 385 . 386 </Fragment> 387 } 388 /> 389 </Grid> 390 )} 391 </Grid> 392 </Fragment> 393 ); 394 }; 395 396 export default BucketLifecyclePanel;