github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.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  
    19  import get from "lodash/get";
    20  import { useSelector } from "react-redux";
    21  import { useParams } from "react-router-dom";
    22  import { api } from "api";
    23  import {
    24    BucketEncryptionInfo,
    25    BucketQuota,
    26    BucketVersioningResponse,
    27    GetBucketRetentionConfig,
    28  } from "api/consoleApi";
    29  import { errorToHandler } from "api/errors";
    30  import {
    31    Box,
    32    DisabledIcon,
    33    EnabledIcon,
    34    Grid,
    35    SectionTitle,
    36    ValuePair,
    37  } from "mds";
    38  import { twoColCssGridLayoutConfig } from "../../Common/FormComponents/common/styleLibrary";
    39  import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
    40  import {
    41    hasPermission,
    42    SecureComponent,
    43  } from "../../../../common/SecureComponent";
    44  import {
    45    selDistSet,
    46    setErrorSnackMessage,
    47    setHelpName,
    48  } from "../../../../systemSlice";
    49  import {
    50    selBucketDetailsInfo,
    51    selBucketDetailsLoading,
    52    setBucketDetailsLoad,
    53  } from "./bucketDetailsSlice";
    54  import { useAppDispatch } from "../../../../store";
    55  import VersioningInfo from "../VersioningInfo";
    56  import withSuspense from "../../Common/Components/withSuspense";
    57  import LabelWithIcon from "./SummaryItems/LabelWithIcon";
    58  import EditablePropertyItem from "./SummaryItems/EditablePropertyItem";
    59  import ReportedUsage from "./SummaryItems/ReportedUsage";
    60  import BucketQuotaSize from "./SummaryItems/BucketQuotaSize";
    61  
    62  const SetAccessPolicy = withSuspense(
    63    React.lazy(() => import("./SetAccessPolicy")),
    64  );
    65  const SetRetentionConfig = withSuspense(
    66    React.lazy(() => import("./SetRetentionConfig")),
    67  );
    68  const EnableBucketEncryption = withSuspense(
    69    React.lazy(() => import("./EnableBucketEncryption")),
    70  );
    71  const EnableVersioningModal = withSuspense(
    72    React.lazy(() => import("./EnableVersioningModal")),
    73  );
    74  const BucketTags = withSuspense(
    75    React.lazy(() => import("./SummaryItems/BucketTags")),
    76  );
    77  const EnableQuota = withSuspense(React.lazy(() => import("./EnableQuota")));
    78  
    79  const BucketSummary = () => {
    80    const dispatch = useAppDispatch();
    81    const params = useParams();
    82  
    83    const loadingBucket = useSelector(selBucketDetailsLoading);
    84    const bucketInfo = useSelector(selBucketDetailsInfo);
    85    const distributedSetup = useSelector(selDistSet);
    86  
    87    const [encryptionCfg, setEncryptionCfg] =
    88      useState<BucketEncryptionInfo | null>(null);
    89    const [bucketSize, setBucketSize] = useState<number | "0">("0");
    90    const [hasObjectLocking, setHasObjectLocking] = useState<boolean | undefined>(
    91      false,
    92    );
    93    const [accessPolicyScreenOpen, setAccessPolicyScreenOpen] =
    94      useState<boolean>(false);
    95    const [replicationRules, setReplicationRules] = useState<boolean>(false);
    96    const [loadingObjectLocking, setLoadingLocking] = useState<boolean>(true);
    97    const [loadingSize, setLoadingSize] = useState<boolean>(true);
    98    const [bucketLoading, setBucketLoading] = useState<boolean>(true);
    99    const [loadingEncryption, setLoadingEncryption] = useState<boolean>(true);
   100    const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
   101    const [loadingQuota, setLoadingQuota] = useState<boolean>(true);
   102    const [loadingReplication, setLoadingReplication] = useState<boolean>(true);
   103    const [loadingRetention, setLoadingRetention] = useState<boolean>(true);
   104    const [versioningInfo, setVersioningInfo] =
   105      useState<BucketVersioningResponse>();
   106    const [quotaEnabled, setQuotaEnabled] = useState<boolean>(false);
   107    const [quota, setQuota] = useState<BucketQuota | null>(null);
   108    const [encryptionEnabled, setEncryptionEnabled] = useState<boolean>(false);
   109    const [retentionEnabled, setRetentionEnabled] = useState<boolean>(false);
   110    const [retentionConfig, setRetentionConfig] =
   111      useState<GetBucketRetentionConfig | null>(null);
   112    const [retentionConfigOpen, setRetentionConfigOpen] =
   113      useState<boolean>(false);
   114    const [enableEncryptionScreenOpen, setEnableEncryptionScreenOpen] =
   115      useState<boolean>(false);
   116    const [enableQuotaScreenOpen, setEnableQuotaScreenOpen] =
   117      useState<boolean>(false);
   118    const [enableVersioningOpen, setEnableVersioningOpen] =
   119      useState<boolean>(false);
   120    useEffect(() => {
   121      dispatch(setHelpName("bucket_detail_summary"));
   122      // eslint-disable-next-line react-hooks/exhaustive-deps
   123    }, []);
   124  
   125    const bucketName = params.bucketName || "";
   126  
   127    let accessPolicy = "PRIVATE";
   128    let policyDefinition = "";
   129  
   130    if (bucketInfo !== null && bucketInfo.access && bucketInfo.definition) {
   131      accessPolicy = bucketInfo.access;
   132      policyDefinition = bucketInfo.definition;
   133    }
   134  
   135    const displayGetBucketObjectLockConfiguration = hasPermission(bucketName, [
   136      IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
   137      IAM_SCOPES.S3_GET_ACTIONS,
   138    ]);
   139  
   140    const displayGetBucketEncryptionConfiguration = hasPermission(bucketName, [
   141      IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
   142      IAM_SCOPES.S3_GET_ACTIONS,
   143    ]);
   144  
   145    const displayGetBucketQuota = hasPermission(bucketName, [
   146      IAM_SCOPES.ADMIN_GET_BUCKET_QUOTA,
   147    ]);
   148  
   149    useEffect(() => {
   150      if (loadingBucket) {
   151        setBucketLoading(true);
   152      } else {
   153        setBucketLoading(false);
   154      }
   155    }, [loadingBucket, setBucketLoading]);
   156  
   157    useEffect(() => {
   158      if (loadingEncryption) {
   159        if (displayGetBucketEncryptionConfiguration) {
   160          api.buckets
   161            .getBucketEncryptionInfo(bucketName)
   162            .then((res) => {
   163              if (res.data.algorithm) {
   164                setEncryptionEnabled(true);
   165                setEncryptionCfg(res.data);
   166              }
   167              setLoadingEncryption(false);
   168            })
   169            .catch((err) => {
   170              err = errorToHandler(err.error);
   171              if (
   172                err.errorMessage ===
   173                "The server side encryption configuration was not found"
   174              ) {
   175                setEncryptionEnabled(false);
   176                setEncryptionCfg(null);
   177              }
   178              setLoadingEncryption(false);
   179            });
   180        } else {
   181          setEncryptionEnabled(false);
   182          setEncryptionCfg(null);
   183          setLoadingEncryption(false);
   184        }
   185      }
   186    }, [loadingEncryption, bucketName, displayGetBucketEncryptionConfiguration]);
   187  
   188    useEffect(() => {
   189      if (loadingVersioning && distributedSetup) {
   190        api.buckets
   191          .getBucketVersioning(bucketName)
   192          .then((res) => {
   193            setVersioningInfo(res.data);
   194            setLoadingVersioning(false);
   195          })
   196          .catch((err) => {
   197            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   198            setLoadingVersioning(false);
   199          });
   200      }
   201    }, [loadingVersioning, dispatch, bucketName, distributedSetup]);
   202  
   203    useEffect(() => {
   204      if (loadingQuota && distributedSetup) {
   205        if (displayGetBucketQuota) {
   206          api.buckets
   207            .getBucketQuota(bucketName)
   208            .then((res) => {
   209              setQuota(res.data);
   210              if (res.data.quota) {
   211                setQuotaEnabled(true);
   212              } else {
   213                setQuotaEnabled(false);
   214              }
   215              setLoadingQuota(false);
   216            })
   217            .catch((err) => {
   218              dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   219              setQuotaEnabled(false);
   220              setLoadingQuota(false);
   221            });
   222        } else {
   223          setQuotaEnabled(false);
   224          setLoadingQuota(false);
   225        }
   226      }
   227    }, [
   228      loadingQuota,
   229      setLoadingVersioning,
   230      dispatch,
   231      bucketName,
   232      distributedSetup,
   233      displayGetBucketQuota,
   234    ]);
   235  
   236    useEffect(() => {
   237      if (loadingVersioning && distributedSetup) {
   238        if (displayGetBucketObjectLockConfiguration) {
   239          api.buckets
   240            .getBucketObjectLockingStatus(bucketName)
   241            .then((res) => {
   242              setHasObjectLocking(res.data.object_locking_enabled);
   243              setLoadingLocking(false);
   244            })
   245            .catch((err) => {
   246              dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   247              setLoadingLocking(false);
   248            });
   249        } else {
   250          setLoadingLocking(false);
   251        }
   252      }
   253    }, [
   254      loadingObjectLocking,
   255      dispatch,
   256      bucketName,
   257      loadingVersioning,
   258      distributedSetup,
   259      displayGetBucketObjectLockConfiguration,
   260    ]);
   261  
   262    useEffect(() => {
   263      if (loadingSize) {
   264        api.buckets
   265          .listBuckets()
   266          .then((res) => {
   267            const resBuckets = get(res.data, "buckets", []);
   268  
   269            const bucketInfo = resBuckets.find(
   270              (bucket) => bucket.name === bucketName,
   271            );
   272  
   273            const size = get(bucketInfo, "size", "0");
   274  
   275            setLoadingSize(false);
   276            setBucketSize(size);
   277          })
   278          .catch((err) => {
   279            setLoadingSize(false);
   280            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   281          });
   282      }
   283    }, [loadingSize, dispatch, bucketName]);
   284  
   285    useEffect(() => {
   286      if (loadingReplication && distributedSetup) {
   287        api.buckets
   288          .getBucketReplication(bucketName)
   289          .then((res) => {
   290            const r = res.data.rules ? res.data.rules : [];
   291            setReplicationRules(r.length > 0);
   292            setLoadingReplication(false);
   293          })
   294          .catch((err) => {
   295            dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   296            setLoadingReplication(false);
   297          });
   298      }
   299    }, [loadingReplication, dispatch, bucketName, distributedSetup]);
   300  
   301    useEffect(() => {
   302      if (loadingRetention && hasObjectLocking) {
   303        api.buckets
   304          .getBucketRetentionConfig(bucketName)
   305          .then((res) => {
   306            setLoadingRetention(false);
   307            setRetentionEnabled(true);
   308            setRetentionConfig(res.data);
   309          })
   310          .catch((err) => {
   311            setRetentionEnabled(false);
   312            setLoadingRetention(false);
   313            setRetentionConfig(null);
   314          });
   315      }
   316    }, [loadingRetention, hasObjectLocking, bucketName]);
   317  
   318    const loadAllBucketData = () => {
   319      dispatch(setBucketDetailsLoad(true));
   320      setBucketLoading(true);
   321      setLoadingSize(true);
   322      setLoadingVersioning(true);
   323      setLoadingEncryption(true);
   324      setLoadingRetention(true);
   325    };
   326  
   327    const setBucketVersioning = () => {
   328      setEnableVersioningOpen(true);
   329    };
   330    const setBucketQuota = () => {
   331      setEnableQuotaScreenOpen(true);
   332    };
   333  
   334    const closeEnableBucketEncryption = () => {
   335      setEnableEncryptionScreenOpen(false);
   336      setLoadingEncryption(true);
   337    };
   338    const closeEnableBucketQuota = () => {
   339      setEnableQuotaScreenOpen(false);
   340      setLoadingQuota(true);
   341    };
   342  
   343    const closeSetAccessPolicy = () => {
   344      setAccessPolicyScreenOpen(false);
   345      loadAllBucketData();
   346    };
   347  
   348    const closeRetentionConfig = () => {
   349      setRetentionConfigOpen(false);
   350      loadAllBucketData();
   351    };
   352  
   353    const closeEnableVersioning = (refresh: boolean) => {
   354      setEnableVersioningOpen(false);
   355      if (refresh) {
   356        loadAllBucketData();
   357      }
   358    };
   359  
   360    let versioningStatus = versioningInfo?.status;
   361    let versioningText = "Unversioned (Default)";
   362    if (versioningStatus === "Enabled") {
   363      versioningText = "Versioned";
   364    } else if (versioningStatus === "Suspended") {
   365      versioningText = "Suspended";
   366    }
   367  
   368    return (
   369      <Fragment>
   370        {enableEncryptionScreenOpen && (
   371          <EnableBucketEncryption
   372            open={enableEncryptionScreenOpen}
   373            selectedBucket={bucketName}
   374            encryptionEnabled={encryptionEnabled}
   375            encryptionCfg={encryptionCfg}
   376            closeModalAndRefresh={closeEnableBucketEncryption}
   377          />
   378        )}
   379        {enableQuotaScreenOpen && (
   380          <EnableQuota
   381            open={enableQuotaScreenOpen}
   382            selectedBucket={bucketName}
   383            enabled={quotaEnabled}
   384            cfg={quota}
   385            closeModalAndRefresh={closeEnableBucketQuota}
   386          />
   387        )}
   388        {accessPolicyScreenOpen && (
   389          <SetAccessPolicy
   390            bucketName={bucketName}
   391            open={accessPolicyScreenOpen}
   392            actualPolicy={accessPolicy}
   393            actualDefinition={policyDefinition}
   394            closeModalAndRefresh={closeSetAccessPolicy}
   395          />
   396        )}
   397        {retentionConfigOpen && (
   398          <SetRetentionConfig
   399            bucketName={bucketName}
   400            open={retentionConfigOpen}
   401            closeModalAndRefresh={closeRetentionConfig}
   402          />
   403        )}
   404        {enableVersioningOpen && (
   405          <EnableVersioningModal
   406            closeVersioningModalAndRefresh={closeEnableVersioning}
   407            modalOpen={enableVersioningOpen}
   408            selectedBucket={bucketName}
   409            versioningInfo={versioningInfo}
   410            objectLockingEnabled={!!hasObjectLocking}
   411          />
   412        )}
   413  
   414        <SectionTitle separator sx={{ marginBottom: 15 }}>
   415          Summary
   416        </SectionTitle>
   417        <Grid container>
   418          <SecureComponent
   419            scopes={[IAM_SCOPES.S3_GET_BUCKET_POLICY, IAM_SCOPES.S3_GET_ACTIONS]}
   420            resource={bucketName}
   421          >
   422            <Grid item xs={12}>
   423              <Box sx={twoColCssGridLayoutConfig}>
   424                <Box sx={twoColCssGridLayoutConfig}>
   425                  <SecureComponent
   426                    scopes={[
   427                      IAM_SCOPES.S3_GET_BUCKET_POLICY,
   428                      IAM_SCOPES.S3_GET_ACTIONS,
   429                    ]}
   430                    resource={bucketName}
   431                  >
   432                    <EditablePropertyItem
   433                      iamScopes={[
   434                        IAM_SCOPES.S3_PUT_BUCKET_POLICY,
   435                        IAM_SCOPES.S3_PUT_ACTIONS,
   436                      ]}
   437                      resourceName={bucketName}
   438                      property={"Access Policy:"}
   439                      value={accessPolicy.toLowerCase()}
   440                      onEdit={() => {
   441                        setAccessPolicyScreenOpen(true);
   442                      }}
   443                      isLoading={bucketLoading}
   444                      helpTip={
   445                        <Fragment>
   446                          <strong>Private</strong> policy limits access to
   447                          credentialled accounts with appropriate permissions
   448                          <br />
   449                          <strong>Public</strong> policy anyone will be able to
   450                          upload, download and delete files from this Bucket once
   451                          logged in
   452                          <br />
   453                          <strong>Custom</strong> policy can be written to define
   454                          which accounts are authorized to access this Bucket
   455                          <br />
   456                          <br />
   457                          To allow Bucket access without credentials, use the{" "}
   458                          <a href={`/buckets/${bucketName}/admin/prefix`}>
   459                            Anonymous
   460                          </a>{" "}
   461                          setting
   462                        </Fragment>
   463                      }
   464                    />
   465                  </SecureComponent>
   466  
   467                  <SecureComponent
   468                    scopes={[
   469                      IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
   470                      IAM_SCOPES.S3_GET_ACTIONS,
   471                    ]}
   472                    resource={bucketName}
   473                  >
   474                    <EditablePropertyItem
   475                      iamScopes={[
   476                        IAM_SCOPES.S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION,
   477                        IAM_SCOPES.S3_PUT_ACTIONS,
   478                      ]}
   479                      resourceName={bucketName}
   480                      property={"Encryption:"}
   481                      value={encryptionEnabled ? "Enabled" : "Disabled"}
   482                      onEdit={() => {
   483                        setEnableEncryptionScreenOpen(true);
   484                      }}
   485                      isLoading={loadingEncryption}
   486                      helpTip={
   487                        <Fragment>
   488                          MinIO supports enabling automatic{" "}
   489                          <a
   490                            href="https://min.io/docs/minio/kubernetes/upstream/administration/server-side-encryption/server-side-encryption-sse-kms.html"
   491                            target="blank"
   492                          >
   493                            SSE-KMS
   494                          </a>{" "}
   495                          and{" "}
   496                          <a
   497                            href="https://min.io/docs/minio/kubernetes/upstream/administration/server-side-encryption/server-side-encryption-sse-s3.html"
   498                            target="blank"
   499                          >
   500                            SSE-S3
   501                          </a>{" "}
   502                          encryption of all objects written to a bucket using a
   503                          specific External Key (EK) stored on the external KMS.
   504                        </Fragment>
   505                      }
   506                    />
   507                  </SecureComponent>
   508  
   509                  <SecureComponent
   510                    scopes={[
   511                      IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
   512                      IAM_SCOPES.S3_GET_ACTIONS,
   513                    ]}
   514                    resource={bucketName}
   515                  >
   516                    <ValuePair
   517                      label={"Replication:"}
   518                      value={
   519                        <LabelWithIcon
   520                          icon={
   521                            replicationRules ? <EnabledIcon /> : <DisabledIcon />
   522                          }
   523                          label={
   524                            <label className={"muted"}>
   525                              {replicationRules ? "Enabled" : "Disabled"}
   526                            </label>
   527                          }
   528                        />
   529                      }
   530                    />
   531                  </SecureComponent>
   532  
   533                  <SecureComponent
   534                    scopes={[
   535                      IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
   536                      IAM_SCOPES.S3_GET_ACTIONS,
   537                    ]}
   538                    resource={bucketName}
   539                  >
   540                    <ValuePair
   541                      label={"Object Locking:"}
   542                      value={
   543                        <LabelWithIcon
   544                          icon={
   545                            hasObjectLocking ? <EnabledIcon /> : <DisabledIcon />
   546                          }
   547                          label={
   548                            <label className={"muted"}>
   549                              {hasObjectLocking ? "Enabled" : "Disabled"}
   550                            </label>
   551                          }
   552                        />
   553                      }
   554                    />
   555                  </SecureComponent>
   556                  <Box>
   557                    <ValuePair
   558                      label={"Tags:"}
   559                      value={<BucketTags bucketName={bucketName} />}
   560                    />
   561                  </Box>
   562                  <EditablePropertyItem
   563                    iamScopes={[IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA]}
   564                    resourceName={bucketName}
   565                    property={"Quota:"}
   566                    value={quotaEnabled ? "Enabled" : "Disabled"}
   567                    onEdit={setBucketQuota}
   568                    isLoading={loadingQuota}
   569                    helpTip={
   570                      <Fragment>
   571                        Setting a{" "}
   572                        <a
   573                          href="https://min.io/docs/minio/linux/reference/minio-mc/mc-quota-set.html"
   574                          target="blank"
   575                        >
   576                          quota
   577                        </a>{" "}
   578                        assigns a hard limit to a bucket beyond which MinIO does
   579                        not allow writes.
   580                      </Fragment>
   581                    }
   582                  />
   583                </Box>
   584                <Box
   585                  sx={{
   586                    display: "grid",
   587                    gridTemplateColumns: "1fr",
   588                    alignItems: "flex-start",
   589                  }}
   590                >
   591                  <ReportedUsage bucketSize={`${bucketSize}`} />
   592                  {quotaEnabled && quota ? (
   593                    <BucketQuotaSize quota={quota} />
   594                  ) : null}
   595                </Box>
   596              </Box>
   597            </Grid>
   598          </SecureComponent>
   599  
   600          {distributedSetup && (
   601            <SecureComponent
   602              scopes={[
   603                IAM_SCOPES.S3_GET_BUCKET_VERSIONING,
   604                IAM_SCOPES.S3_GET_ACTIONS,
   605              ]}
   606              resource={bucketName}
   607            >
   608              <Grid item xs={12} sx={{ marginTop: 5 }}>
   609                <SectionTitle separator sx={{ marginBottom: 15 }}>
   610                  Versioning
   611                </SectionTitle>
   612  
   613                <Box sx={twoColCssGridLayoutConfig}>
   614                  <Box sx={twoColCssGridLayoutConfig}>
   615                    <EditablePropertyItem
   616                      iamScopes={[
   617                        IAM_SCOPES.S3_PUT_BUCKET_VERSIONING,
   618                        IAM_SCOPES.S3_PUT_ACTIONS,
   619                      ]}
   620                      resourceName={bucketName}
   621                      property={"Current Status:"}
   622                      value={
   623                        <Box
   624                          sx={{
   625                            display: "flex",
   626                            flexDirection: "column",
   627                            textDecorationStyle: "initial",
   628                            placeItems: "flex-start",
   629                            justifyItems: "flex-start",
   630                            gap: 3,
   631                          }}
   632                        >
   633                          <div> {versioningText}</div>
   634                        </Box>
   635                      }
   636                      onEdit={setBucketVersioning}
   637                      isLoading={loadingVersioning}
   638                      disabled={hasObjectLocking}
   639                    />
   640  
   641                    {versioningInfo?.status === "Enabled" ? (
   642                      <VersioningInfo versioningState={versioningInfo} />
   643                    ) : null}
   644                  </Box>
   645                </Box>
   646              </Grid>
   647            </SecureComponent>
   648          )}
   649  
   650          {hasObjectLocking && (
   651            <SecureComponent
   652              scopes={[
   653                IAM_SCOPES.S3_GET_OBJECT_RETENTION,
   654                IAM_SCOPES.S3_GET_ACTIONS,
   655              ]}
   656              resource={bucketName}
   657            >
   658              <Grid item xs={12} sx={{ marginTop: 5 }}>
   659                <SectionTitle separator sx={{ marginBottom: 15 }}>
   660                  Retention
   661                </SectionTitle>
   662  
   663                <Box sx={twoColCssGridLayoutConfig}>
   664                  <Box sx={twoColCssGridLayoutConfig}>
   665                    <EditablePropertyItem
   666                      iamScopes={[IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA]}
   667                      resourceName={bucketName}
   668                      property={"Retention:"}
   669                      value={retentionEnabled ? "Enabled" : "Disabled"}
   670                      onEdit={() => {
   671                        setRetentionConfigOpen(true);
   672                      }}
   673                      isLoading={loadingRetention}
   674                      helpTip={
   675                        <Fragment>
   676                          MinIO{" "}
   677                          <a
   678                            target="blank"
   679                            href="https://min.io/docs/minio/macos/administration/object-management.html#object-retention"
   680                          >
   681                            Object Locking
   682                          </a>{" "}
   683                          enforces Write-Once Read-Many (WORM) immutability to
   684                          protect versioned objects from deletion.
   685                        </Fragment>
   686                      }
   687                    />
   688  
   689                    <ValuePair
   690                      label={"Mode:"}
   691                      value={
   692                        <label
   693                          className={"muted"}
   694                          style={{ textTransform: "capitalize" }}
   695                        >
   696                          {retentionConfig && retentionConfig.mode
   697                            ? retentionConfig.mode
   698                            : "-"}
   699                        </label>
   700                      }
   701                    />
   702                    <ValuePair
   703                      label={"Validity:"}
   704                      value={
   705                        <label
   706                          className={"muted"}
   707                          style={{ textTransform: "capitalize" }}
   708                        >
   709                          {retentionConfig && retentionConfig.validity}{" "}
   710                          {retentionConfig &&
   711                            (retentionConfig.validity === 1
   712                              ? retentionConfig.unit?.slice(0, -1)
   713                              : retentionConfig.unit)}
   714                        </label>
   715                      }
   716                    />
   717                  </Box>
   718                </Box>
   719              </Grid>
   720            </SecureComponent>
   721          )}
   722        </Grid>
   723      </Fragment>
   724    );
   725  };
   726  
   727  export default BucketSummary;