github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/ListBuckets/BulkLifecycleModal.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2022 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 {
    19    Box,
    20    CheckCircleIcon,
    21    FormLayout,
    22    Grid,
    23    InputBox,
    24    RadioGroup,
    25    ReadBox,
    26    Select,
    27    Switch,
    28    Tooltip,
    29    WarnIcon,
    30    Wizard,
    31  } from "mds";
    32  import get from "lodash/get";
    33  import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
    34  import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
    35  import { ITiersDropDown } from "../types";
    36  import { setModalErrorSnackMessage } from "../../../../systemSlice";
    37  import { useAppDispatch } from "../../../../store";
    38  import { api } from "api";
    39  import { MultiLifecycleResult, Tier } from "api/consoleApi";
    40  import { errorToHandler } from "api/errors";
    41  
    42  interface IBulkReplicationModal {
    43    open: boolean;
    44    closeModalAndRefresh: (clearSelection: boolean) => any;
    45    buckets: string[];
    46  }
    47  
    48  const AddBulkReplicationModal = ({
    49    open,
    50    closeModalAndRefresh,
    51    buckets,
    52  }: IBulkReplicationModal) => {
    53    const dispatch = useAppDispatch();
    54    const [addLoading, setAddLoading] = useState<boolean>(false);
    55    const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
    56    const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
    57    const [prefix, setPrefix] = useState("");
    58    const [tags, setTags] = useState<string>("");
    59    const [storageClass, setStorageClass] = useState("");
    60    const [NCTransitionSC, setNCTransitionSC] = useState("");
    61    const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
    62    const [expiredAllVersionsDM, setExpiredAllVersionsDM] =
    63      useState<boolean>(false);
    64    const [NCExpirationDays, setNCExpirationDays] = useState<string>("0");
    65    const [NCTransitionDays, setNCTransitionDays] = useState<string>("0");
    66    const [ilmType, setIlmType] = useState<"expiry" | "transition">("expiry");
    67    const [expiryDays, setExpiryDays] = useState<string>("0");
    68    const [transitionDays, setTransitionDays] = useState<string>("0");
    69    const [isFormValid, setIsFormValid] = useState<boolean>(false);
    70    const [results, setResults] = useState<MultiLifecycleResult | null>(null);
    71  
    72    useEffect(() => {
    73      if (loadingTiers) {
    74        api.admin
    75          .tiersList()
    76          .then((res) => {
    77            const tiersList: Tier[] | null = get(res.data, "items", []);
    78  
    79            if (tiersList !== null && tiersList.length >= 1) {
    80              const objList = tiersList.map((tier: Tier) => {
    81                const tierType = tier.type;
    82                const value = get(tier, `${tierType}.name`, "");
    83  
    84                return { label: value, value: value };
    85              });
    86  
    87              setTiersList(objList);
    88              if (objList.length > 0) {
    89                setStorageClass(objList[0].value);
    90              }
    91            }
    92            setLoadingTiers(false);
    93          })
    94          .catch((err) => {
    95            setLoadingTiers(false);
    96            dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
    97          });
    98      }
    99    }, [loadingTiers, dispatch]);
   100  
   101    useEffect(() => {
   102      let valid = true;
   103  
   104      if (ilmType !== "expiry") {
   105        if (storageClass === "") {
   106          valid = false;
   107        }
   108      }
   109      setIsFormValid(valid);
   110    }, [ilmType, expiryDays, transitionDays, storageClass]);
   111  
   112    const LogoToShow = ({ errString }: { errString: string }) => {
   113      switch (errString) {
   114        case "":
   115          return (
   116            <Box
   117              sx={{
   118                paddingTop: 5,
   119                color: "#42C91A",
   120              }}
   121            >
   122              <CheckCircleIcon />
   123            </Box>
   124          );
   125        case "n/a":
   126          return null;
   127        default:
   128          if (errString) {
   129            return (
   130              <Box
   131                sx={{
   132                  paddingTop: 5,
   133                  color: "#C72C48",
   134                }}
   135              >
   136                <Tooltip tooltip={errString} placement="top">
   137                  <WarnIcon />
   138                </Tooltip>
   139              </Box>
   140            );
   141          }
   142      }
   143      return null;
   144    };
   145  
   146    const createLifecycleRules = (to: any) => {
   147      let rules = {};
   148  
   149      if (ilmType === "expiry") {
   150        let expiry = {
   151          expiry_days: parseInt(expiryDays),
   152        };
   153  
   154        rules = {
   155          ...expiry,
   156          noncurrentversion_expiration_days: parseInt(NCExpirationDays),
   157        };
   158      } else {
   159        let transition = {
   160          transition_days: parseInt(transitionDays),
   161        };
   162  
   163        rules = {
   164          ...transition,
   165          noncurrentversion_transition_days: parseInt(NCTransitionDays),
   166          noncurrentversion_transition_storage_class: NCTransitionSC,
   167          storage_class: storageClass,
   168        };
   169      }
   170  
   171      const lifecycleInsert = {
   172        buckets,
   173        type: ilmType,
   174        prefix,
   175        tags,
   176        expired_object_delete_marker: expiredObjectDM,
   177        expired_object_delete_all: expiredAllVersionsDM,
   178        ...rules,
   179      };
   180  
   181      api.buckets
   182        .addMultiBucketLifecycle(lifecycleInsert)
   183        .then((res) => {
   184          setAddLoading(false);
   185          setResults(res.data);
   186          to("++");
   187        })
   188        .catch((err) => {
   189          setAddLoading(false);
   190          dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
   191        });
   192    };
   193  
   194    return (
   195      <ModalWrapper
   196        modalOpen={open}
   197        onClose={() => {
   198          closeModalAndRefresh(false);
   199        }}
   200        title="Set Lifecycle to multiple buckets"
   201      >
   202        <Wizard
   203          loadingStep={addLoading || loadingTiers}
   204          wizardSteps={[
   205            {
   206              label: "Lifecycle Configuration",
   207              componentRender: (
   208                <Fragment>
   209                  <FormLayout withBorders={false} containerPadding={false}>
   210                    <Grid item xs={12}>
   211                      <ReadBox
   212                        label="Local Buckets to replicate"
   213                        sx={{ maxWidth: "440px", width: "100%" }}
   214                      >
   215                        {buckets.join(", ")}
   216                      </ReadBox>
   217                    </Grid>
   218                    <h4>Remote Endpoint Configuration</h4>
   219                    <fieldset className={"inputItem"}>
   220                      <legend>Lifecycle Configuration</legend>
   221                      <RadioGroup
   222                        currentValue={ilmType}
   223                        id="quota_type"
   224                        name="quota_type"
   225                        label="ILM Rule"
   226                        onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
   227                          setIlmType(e.target.value as "expiry" | "transition");
   228                        }}
   229                        selectorOptions={[
   230                          { value: "expiry", label: "Expiry" },
   231                          { value: "transition", label: "Transition" },
   232                        ]}
   233                      />
   234                      {ilmType === "expiry" ? (
   235                        <Fragment>
   236                          <InputBox
   237                            type="number"
   238                            id="expiry_days"
   239                            name="expiry_days"
   240                            onChange={(
   241                              e: React.ChangeEvent<HTMLInputElement>,
   242                            ) => {
   243                              setExpiryDays(e.target.value);
   244                            }}
   245                            label="Expiry Days"
   246                            value={expiryDays}
   247                            min="0"
   248                          />
   249                          <InputBox
   250                            type="number"
   251                            id="noncurrentversion_expiration_days"
   252                            name="noncurrentversion_expiration_days"
   253                            onChange={(
   254                              e: React.ChangeEvent<HTMLInputElement>,
   255                            ) => {
   256                              setNCExpirationDays(e.target.value);
   257                            }}
   258                            label="Non-current Expiration Days"
   259                            value={NCExpirationDays}
   260                            min="0"
   261                          />
   262                        </Fragment>
   263                      ) : (
   264                        <Fragment>
   265                          <InputBox
   266                            type="number"
   267                            id="transition_days"
   268                            name="transition_days"
   269                            onChange={(
   270                              e: React.ChangeEvent<HTMLInputElement>,
   271                            ) => {
   272                              setTransitionDays(e.target.value);
   273                            }}
   274                            label="Transition Days"
   275                            value={transitionDays}
   276                            min="0"
   277                          />
   278                          <InputBox
   279                            type="number"
   280                            id="noncurrentversion_transition_days"
   281                            name="noncurrentversion_transition_days"
   282                            onChange={(
   283                              e: React.ChangeEvent<HTMLInputElement>,
   284                            ) => {
   285                              setNCTransitionDays(e.target.value);
   286                            }}
   287                            label="Non-current Transition Days"
   288                            value={NCTransitionDays}
   289                            min="0"
   290                          />
   291                          <InputBox
   292                            id="noncurrentversion_t_SC"
   293                            name="noncurrentversion_t_SC"
   294                            onChange={(
   295                              e: React.ChangeEvent<HTMLInputElement>,
   296                            ) => {
   297                              setNCTransitionSC(e.target.value);
   298                            }}
   299                            placeholder="Set Non-current Version Transition Storage Class"
   300                            label="Non-current Version Transition Storage Class"
   301                            value={NCTransitionSC}
   302                          />
   303                          <Select
   304                            label="Storage Class"
   305                            id="storage_class"
   306                            name="storage_class"
   307                            value={storageClass}
   308                            onChange={(value) => {
   309                              setStorageClass(value);
   310                            }}
   311                            options={tiersList}
   312                          />
   313                        </Fragment>
   314                      )}
   315                    </fieldset>
   316                    <fieldset className={"inputItem"}>
   317                      <legend>File Configuration</legend>
   318                      <InputBox
   319                        id="prefix"
   320                        name="prefix"
   321                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
   322                          setPrefix(e.target.value);
   323                        }}
   324                        label="Prefix"
   325                        value={prefix}
   326                      />
   327                      <QueryMultiSelector
   328                        name="tags"
   329                        label="Tags"
   330                        elements={tags}
   331                        onChange={(vl: string) => {
   332                          setTags(vl);
   333                        }}
   334                        keyPlaceholder="Tag Key"
   335                        valuePlaceholder="Tag Value"
   336                        withBorder
   337                      />
   338                      <Switch
   339                        value="expired_delete_marker"
   340                        id="expired_delete_marker"
   341                        name="expired_delete_marker"
   342                        checked={expiredObjectDM}
   343                        onChange={(
   344                          event: React.ChangeEvent<HTMLInputElement>,
   345                        ) => {
   346                          setExpiredObjectDM(event.target.checked);
   347                        }}
   348                        label={"Expired Object Delete Marker"}
   349                      />
   350                      <Switch
   351                        value="expired_delete_all"
   352                        id="expired_delete_all"
   353                        name="expired_delete_all"
   354                        checked={expiredAllVersionsDM}
   355                        onChange={(
   356                          event: React.ChangeEvent<HTMLInputElement>,
   357                        ) => {
   358                          setExpiredAllVersionsDM(event.target.checked);
   359                        }}
   360                        label={"Expired All Versions"}
   361                      />
   362                    </fieldset>
   363                  </FormLayout>
   364                </Fragment>
   365              ),
   366              buttons: [
   367                {
   368                  type: "custom",
   369                  label: "Create Rules",
   370                  enabled: !loadingTiers && !addLoading && isFormValid,
   371                  action: createLifecycleRules,
   372                },
   373              ],
   374            },
   375            {
   376              label: "Results",
   377              componentRender: (
   378                <Fragment>
   379                  <h3>Multi Bucket lifecycle Assignments Results</h3>
   380                  <Grid container>
   381                    <Grid item xs={12}>
   382                      <h4>Buckets Results</h4>
   383                      {results?.results?.map((resultItem) => {
   384                        return (
   385                          <Box
   386                            sx={{
   387                              display: "grid",
   388                              gridTemplateColumns: "45px auto",
   389                              alignItems: "center",
   390                              justifyContent: "stretch",
   391                            }}
   392                          >
   393                            {LogoToShow({ errString: resultItem.error || "" })}
   394                            <span>{resultItem.bucketName}</span>
   395                          </Box>
   396                        );
   397                      })}
   398                    </Grid>
   399                  </Grid>
   400                </Fragment>
   401              ),
   402              buttons: [
   403                {
   404                  type: "custom",
   405                  label: "Done",
   406                  enabled: !addLoading,
   407                  action: () => closeModalAndRefresh(true),
   408                },
   409              ],
   410            },
   411          ]}
   412          forModal
   413        />
   414      </ModalWrapper>
   415    );
   416  };
   417  
   418  export default AddBulkReplicationModal;