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;