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;