github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjectsTable.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, { useState } from "react";
    18  import { listModeColumns, rewindModeColumns } from "./ListObjectsHelpers";
    19  import { useSelector } from "react-redux";
    20  import { AppState, useAppDispatch } from "../../../../../../store";
    21  import { selFeatures } from "../../../../consoleSlice";
    22  import { encodeURLString } from "../../../../../../common/utils";
    23  import {
    24    setLoadingVersions,
    25    setObjectDetailsView,
    26    setReloadObjectsList,
    27    setSelectedObjects,
    28    setSelectedObjectView,
    29  } from "../../../../ObjectBrowser/objectBrowserSlice";
    30  import { useNavigate, useParams } from "react-router-dom";
    31  import get from "lodash/get";
    32  import { sortListObjects } from "../utils";
    33  import { BucketObjectItem } from "./types";
    34  import {
    35    IAM_SCOPES,
    36    permissionTooltipHelper,
    37  } from "../../../../../../common/SecureComponent/permissions";
    38  import { hasPermission } from "../../../../../../common/SecureComponent";
    39  import { downloadObject } from "../../../../ObjectBrowser/utils";
    40  import { DataTable, ItemActions } from "mds";
    41  import { BucketObject } from "api/consoleApi";
    42  
    43  const ListObjectsTable = () => {
    44    const dispatch = useAppDispatch();
    45    const params = useParams();
    46    const navigate = useNavigate();
    47  
    48    const [sortDirection, setSortDirection] = useState<
    49      "ASC" | "DESC" | undefined
    50    >("ASC");
    51    const [currentSortField, setCurrentSortField] = useState<string>("name");
    52  
    53    const bucketName = params.bucketName || "";
    54  
    55    const detailsOpen = useSelector(
    56      (state: AppState) => state.objectBrowser.objectDetailsOpen,
    57    );
    58  
    59    const requestInProgress = useSelector(
    60      (state: AppState) => state.objectBrowser.requestInProgress,
    61    );
    62  
    63    const features = useSelector(selFeatures);
    64    const obOnly = !!features?.includes("object-browser-only");
    65  
    66    const rewindEnabled = useSelector(
    67      (state: AppState) => state.objectBrowser.rewind.rewindEnabled,
    68    );
    69    const records = useSelector((state: AppState) => state.objectBrowser.records);
    70    const searchObjects = useSelector(
    71      (state: AppState) => state.objectBrowser.searchObjects,
    72    );
    73    const selectedObjects = useSelector(
    74      (state: AppState) => state.objectBrowser.selectedObjects,
    75    );
    76    const connectionError = useSelector(
    77      (state: AppState) => state.objectBrowser.connectionError,
    78    );
    79    const anonymousMode = useSelector(
    80      (state: AppState) => state.system.anonymousMode,
    81    );
    82  
    83    const displayListObjects = hasPermission(bucketName, [
    84      IAM_SCOPES.S3_LIST_BUCKET,
    85      IAM_SCOPES.S3_ALL_LIST_BUCKET,
    86    ]);
    87  
    88    const filteredRecords = records.filter((b: BucketObjectItem) => {
    89      if (searchObjects === "") {
    90        return true;
    91      } else {
    92        const objectName = b.name.toLowerCase();
    93        if (objectName.indexOf(searchObjects.toLowerCase()) >= 0) {
    94          return true;
    95        } else {
    96          return false;
    97        }
    98      }
    99    });
   100  
   101    const plSelect = filteredRecords;
   102    const sortASC = plSelect.sort(sortListObjects(currentSortField));
   103  
   104    let payload: BucketObjectItem[] = [];
   105  
   106    if (sortDirection === "ASC") {
   107      payload = sortASC;
   108    } else {
   109      payload = sortASC.reverse();
   110    }
   111  
   112    const openPath = (object: BucketObject) => {
   113      const idElement = object.name || "";
   114      const newPath = `/browser/${bucketName}${
   115        idElement ? `/${encodeURLString(idElement)}` : ``
   116      }`;
   117  
   118      // for anonymous start download
   119      if (anonymousMode && !object.name?.endsWith("/")) {
   120        downloadObject(
   121          dispatch,
   122          bucketName,
   123          `${encodeURLString(idElement)}`,
   124          object,
   125        );
   126        return;
   127      }
   128      dispatch(setSelectedObjects([]));
   129  
   130      navigate(newPath);
   131  
   132      if (!anonymousMode) {
   133        dispatch(setObjectDetailsView(true));
   134        dispatch(setLoadingVersions(true));
   135      }
   136      dispatch(
   137        setSelectedObjectView(
   138          `${idElement ? `${encodeURLString(idElement)}` : ``}`,
   139        ),
   140      );
   141    };
   142    const tableActions: ItemActions[] = [
   143      {
   144        type: "view",
   145        tooltip: "View",
   146        onClick: openPath,
   147        sendOnlyId: false,
   148      },
   149    ];
   150  
   151    const sortChange = (sortData: any) => {
   152      const newSortDirection = get(sortData, "sortDirection", "DESC");
   153      setCurrentSortField(sortData.sortBy);
   154      setSortDirection(newSortDirection);
   155      dispatch(setReloadObjectsList(true));
   156    };
   157  
   158    const selectAllItems = () => {
   159      dispatch(setSelectedObjectView(null));
   160  
   161      if (selectedObjects.length === payload.length) {
   162        dispatch(setSelectedObjects([]));
   163        return;
   164      }
   165  
   166      const elements = payload.map((item) => item.name);
   167      dispatch(setSelectedObjects(elements));
   168    };
   169  
   170    const selectListObjects = (e: React.ChangeEvent<HTMLInputElement>) => {
   171      const targetD = e.target;
   172      const value = targetD.value;
   173      const checked = targetD.checked;
   174  
   175      let elements: string[] = [...selectedObjects]; // We clone the selectedBuckets array
   176  
   177      if (checked) {
   178        // If the user has checked this field we need to push this to selectedBucketsList
   179        elements.push(value);
   180      } else {
   181        // User has unchecked this field, we need to remove it from the list
   182        elements = elements.filter((element) => element !== value);
   183      }
   184      dispatch(setSelectedObjects(elements));
   185      dispatch(setSelectedObjectView(null));
   186  
   187      return elements;
   188    };
   189  
   190    let errorMessage =
   191      !displayListObjects && !anonymousMode
   192        ? permissionTooltipHelper(
   193            [IAM_SCOPES.S3_LIST_BUCKET, IAM_SCOPES.S3_ALL_LIST_BUCKET],
   194            "view Objects in this bucket",
   195          )
   196        : `This location is empty${
   197            !rewindEnabled ? ", please try uploading a new file" : ""
   198          }`;
   199  
   200    if (connectionError) {
   201      errorMessage =
   202        "Objects List unavailable. Please review your WebSockets configuration and try again";
   203    }
   204  
   205    let customPaperHeight = "calc(100vh - 290px)";
   206  
   207    if (obOnly) {
   208      customPaperHeight = "calc(100vh - 315px)";
   209    }
   210  
   211    return (
   212      <DataTable
   213        itemActions={tableActions}
   214        columns={rewindEnabled ? rewindModeColumns : listModeColumns}
   215        isLoading={requestInProgress}
   216        entityName="Objects"
   217        idField="name"
   218        records={payload}
   219        customPaperHeight={customPaperHeight}
   220        selectedItems={selectedObjects}
   221        onSelect={!anonymousMode ? selectListObjects : undefined}
   222        customEmptyMessage={errorMessage}
   223        sortEnabled={{
   224          currentSort: currentSortField,
   225          currentDirection: sortDirection,
   226          onSortClick: sortChange,
   227        }}
   228        onSelectAll={selectAllItems}
   229        rowStyle={({ index }) => {
   230          if (payload[index]?.delete_flag) {
   231            return "deleted";
   232          }
   233  
   234          return "";
   235        }}
   236        sx={{
   237          minHeight: detailsOpen ? "100%" : "initial",
   238        }}
   239        noBackground
   240      />
   241    );
   242  };
   243  export default ListObjectsTable;