github.com/minio/console@v1.4.1/web-app/src/screens/Console/ObjectBrowser/OBBucketList.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  
    19  import { useNavigate } from "react-router-dom";
    20  import {
    21    ActionLink,
    22    BucketsIcon,
    23    Button,
    24    DataTable,
    25    HelpBox,
    26    PageLayout,
    27    ProgressBar,
    28    RefreshIcon,
    29    Grid,
    30    HelpTip,
    31  } from "mds";
    32  import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
    33  import { SecureComponent } from "../../../common/SecureComponent";
    34  import {
    35    CONSOLE_UI_RESOURCE,
    36    IAM_PAGES,
    37    IAM_SCOPES,
    38    permissionTooltipHelper,
    39  } from "../../../common/SecureComponent/permissions";
    40  import SearchBox from "../Common/SearchBox";
    41  import hasPermission from "../../../common/SecureComponent/accessControl";
    42  import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
    43  import { useAppDispatch } from "../../../store";
    44  import { useSelector } from "react-redux";
    45  import { selFeatures } from "../consoleSlice";
    46  import AutoColorIcon from "../Common/Components/AutoColorIcon";
    47  import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
    48  import { niceBytesInt } from "../../../common/utils";
    49  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    50  import { Bucket } from "../../../api/consoleApi";
    51  import { api } from "../../../api";
    52  import { errorToHandler } from "../../../api/errors";
    53  import HelpMenu from "../HelpMenu";
    54  import { usageClarifyingContent } from "../Dashboard/BasicDashboard/ReportedUsage";
    55  
    56  const OBListBuckets = () => {
    57    const dispatch = useAppDispatch();
    58    const navigate = useNavigate();
    59  
    60    const [records, setRecords] = useState<Bucket[]>([]);
    61    const [loading, setLoading] = useState<boolean>(true);
    62    const [clickOverride, setClickOverride] = useState<boolean>(false);
    63    const [filterBuckets, setFilterBuckets] = useState<string>("");
    64  
    65    const features = useSelector(selFeatures);
    66    const obOnly = !!features?.includes("object-browser-only");
    67  
    68    useEffect(() => {
    69      if (loading) {
    70        const fetchRecords = () => {
    71          setLoading(true);
    72          api.buckets
    73            .listBuckets()
    74            .then((res) => {
    75              if (res.data) {
    76                setLoading(false);
    77                setRecords(res.data.buckets || []);
    78              }
    79            })
    80            .catch((err) => {
    81              setLoading(false);
    82              dispatch(setErrorSnackMessage(errorToHandler(err)));
    83            });
    84        };
    85        fetchRecords();
    86      }
    87    }, [loading, dispatch]);
    88  
    89    const filteredRecords = records.filter((b: Bucket) => {
    90      if (filterBuckets === "") {
    91        return true;
    92      } else {
    93        return b.name.indexOf(filterBuckets) >= 0;
    94      }
    95    });
    96  
    97    const hasBuckets = records.length > 0;
    98  
    99    const canListBuckets = hasPermission("*", [
   100      IAM_SCOPES.S3_LIST_BUCKET,
   101      IAM_SCOPES.S3_ALL_LIST_BUCKET,
   102    ]);
   103  
   104    const tableActions = [
   105      {
   106        type: "view",
   107        onClick: (bucket: Bucket) => {
   108          !clickOverride &&
   109            navigate(`${IAM_PAGES.OBJECT_BROWSER_VIEW}/${bucket.name}`);
   110        },
   111      },
   112    ];
   113  
   114    useEffect(() => {
   115      dispatch(setHelpName("object_browser"));
   116    }, [dispatch]);
   117  
   118    return (
   119      <Fragment>
   120        {!obOnly && (
   121          <PageHeaderWrapper label={"Object Browser"} actions={<HelpMenu />} />
   122        )}
   123  
   124        <PageLayout>
   125          <Grid item xs={12} sx={{ ...actionsTray.actionsTray, display: "flex" }}>
   126            {obOnly && (
   127              <Grid item xs>
   128                <AutoColorIcon marginRight={15} marginTop={10} />
   129              </Grid>
   130            )}
   131            {hasBuckets && (
   132              <SearchBox
   133                onChange={setFilterBuckets}
   134                placeholder="Filter Buckets"
   135                value={filterBuckets}
   136                sx={{
   137                  minWidth: 380,
   138                  "@media (max-width: 900px)": {
   139                    minWidth: 220,
   140                  },
   141                }}
   142              />
   143            )}
   144  
   145            <Grid
   146              item
   147              xs={12}
   148              sx={{
   149                display: "flex",
   150                alignItems: "center",
   151                justifyContent: "flex-end",
   152                gap: 8,
   153              }}
   154            >
   155              <TooltipWrapper tooltip={"Refresh"}>
   156                <Button
   157                  id={"refresh-buckets"}
   158                  onClick={() => {
   159                    setLoading(true);
   160                  }}
   161                  icon={<RefreshIcon />}
   162                  variant={"regular"}
   163                />
   164              </TooltipWrapper>
   165            </Grid>
   166          </Grid>
   167  
   168          {loading && <ProgressBar />}
   169          {!loading && (
   170            <Grid
   171              item
   172              xs={12}
   173              sx={{
   174                marginTop: 25,
   175                height: "calc(100vh - 211px)",
   176                "&.isEmbedded": {
   177                  height: "calc(100vh - 128px)",
   178                },
   179              }}
   180              className={obOnly ? "isEmbedded" : ""}
   181            >
   182              {filteredRecords.length !== 0 && (
   183                <DataTable
   184                  isLoading={loading}
   185                  records={filteredRecords}
   186                  entityName={"Buckets"}
   187                  idField={"name"}
   188                  columns={[
   189                    {
   190                      label: "Name",
   191                      elementKey: "name",
   192                      renderFunction: (label) => (
   193                        <div style={{ display: "flex" }}>
   194                          <BucketsIcon
   195                            style={{ width: 15, marginRight: 5, minWidth: 15 }}
   196                          />
   197                          <span
   198                            id={`browse-${label}`}
   199                            style={{
   200                              whiteSpace: "nowrap",
   201                              overflow: "hidden",
   202                              textOverflow: "ellipsis",
   203                              minWidth: 0,
   204                            }}
   205                          >
   206                            {label}
   207                          </span>
   208                        </div>
   209                      ),
   210                    },
   211                    {
   212                      label: "Objects",
   213                      elementKey: "objects",
   214                      renderFunction: (size: number | null) =>
   215                        size ? size.toLocaleString() : 0,
   216                    },
   217                    {
   218                      label: "Size",
   219                      elementKey: "size",
   220                      renderFunction: (size: number) => (
   221                        <div
   222                          onMouseEnter={() => setClickOverride(true)}
   223                          onMouseLeave={() => setClickOverride(false)}
   224                        >
   225                          <HelpTip
   226                            content={usageClarifyingContent}
   227                            placement="right"
   228                          >
   229                            {niceBytesInt(size || 0)}
   230                          </HelpTip>
   231                        </div>
   232                      ),
   233                    },
   234                    {
   235                      label: "Access",
   236                      elementKey: "rw_access",
   237                      renderFullObject: true,
   238                      renderFunction: (bucket: Bucket) => {
   239                        let access = [];
   240                        if (bucket.rw_access?.read) {
   241                          access.push("R");
   242                        }
   243                        if (bucket.rw_access?.write) {
   244                          access.push("W");
   245                        }
   246                        return <span>{access.join("/")}</span>;
   247                      },
   248                    },
   249                  ]}
   250                  itemActions={tableActions}
   251                />
   252              )}
   253              {filteredRecords.length === 0 && filterBuckets !== "" && (
   254                <Grid
   255                  container
   256                  sx={{
   257                    justifyContent: "center",
   258                    alignContent: "center",
   259                    alignItems: "center",
   260                  }}
   261                >
   262                  <Grid item xs={8}>
   263                    <HelpBox
   264                      iconComponent={<BucketsIcon />}
   265                      title={"No Results"}
   266                      help={
   267                        <Fragment>
   268                          No buckets match the filtering condition
   269                        </Fragment>
   270                      }
   271                    />
   272                  </Grid>
   273                </Grid>
   274              )}
   275              {!hasBuckets && (
   276                <Grid
   277                  container
   278                  sx={{
   279                    justifyContent: "center",
   280                    alignContent: "center",
   281                    alignItems: "center",
   282                  }}
   283                >
   284                  <Grid item xs={8}>
   285                    <HelpBox
   286                      iconComponent={<BucketsIcon />}
   287                      title={"Buckets"}
   288                      help={
   289                        <Fragment>
   290                          MinIO uses buckets to organize objects. A bucket is
   291                          similar to a folder or directory in a filesystem, where
   292                          each bucket can hold an arbitrary number of objects.
   293                          <br />
   294                          {canListBuckets ? (
   295                            ""
   296                          ) : (
   297                            <Fragment>
   298                              <br />
   299                              {permissionTooltipHelper(
   300                                [
   301                                  IAM_SCOPES.S3_LIST_BUCKET,
   302                                  IAM_SCOPES.S3_ALL_LIST_BUCKET,
   303                                ],
   304                                "view the buckets on this server",
   305                              )}
   306                              <br />
   307                            </Fragment>
   308                          )}
   309                          <SecureComponent
   310                            scopes={[IAM_SCOPES.S3_CREATE_BUCKET]}
   311                            resource={CONSOLE_UI_RESOURCE}
   312                          >
   313                            <br />
   314                            To get started,&nbsp;
   315                            <ActionLink
   316                              onClick={() => {
   317                                navigate(IAM_PAGES.ADD_BUCKETS);
   318                              }}
   319                            >
   320                              Create a Bucket.
   321                            </ActionLink>
   322                          </SecureComponent>
   323                        </Fragment>
   324                      }
   325                    />
   326                  </Grid>
   327                </Grid>
   328              )}
   329            </Grid>
   330          )}
   331        </PageLayout>
   332      </Fragment>
   333    );
   334  };
   335  
   336  export default OBListBuckets;