github.com/minio/console@v1.4.1/web-app/src/screens/Console/Buckets/BucketDetails/BrowserHandler.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, useCallback, useEffect } from "react"; 18 import { useSelector } from "react-redux"; 19 import { useLocation, useParams } from "react-router-dom"; 20 import { api } from "api"; 21 import { AppState, useAppDispatch } from "../../../../store"; 22 import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions"; 23 import { decodeURLString, encodeURLString } from "../../../../common/utils"; 24 import { 25 resetMessages, 26 setIsVersioned, 27 setLoadingLocking, 28 setLoadingObjectInfo, 29 setLoadingVersioning, 30 setLoadingVersions, 31 setLockingEnabled, 32 setObjectDetailsView, 33 setRequestInProgress, 34 setSelectedObjectView, 35 setVersionsModeEnabled, 36 } from "../../ObjectBrowser/objectBrowserSlice"; 37 import ListObjects from "../ListBuckets/Objects/ListObjects/ListObjects"; 38 import hasPermission from "../../../../common/SecureComponent/accessControl"; 39 import OBHeader from "../../ObjectBrowser/OBHeader"; 40 41 const BrowserHandler = () => { 42 const dispatch = useAppDispatch(); 43 const params = useParams(); 44 const location = useLocation(); 45 46 const loadingVersioning = useSelector( 47 (state: AppState) => state.objectBrowser.loadingVersioning, 48 ); 49 50 const rewindEnabled = useSelector( 51 (state: AppState) => state.objectBrowser.rewind.rewindEnabled, 52 ); 53 const rewindDate = useSelector( 54 (state: AppState) => state.objectBrowser.rewind.dateToRewind, 55 ); 56 const showDeleted = useSelector( 57 (state: AppState) => state.objectBrowser.showDeleted, 58 ); 59 const requestInProgress = useSelector( 60 (state: AppState) => state.objectBrowser.requestInProgress, 61 ); 62 const loadingLocking = useSelector( 63 (state: AppState) => state.objectBrowser.loadingLocking, 64 ); 65 const reloadObjectsList = useSelector( 66 (state: AppState) => state.objectBrowser.reloadObjectsList, 67 ); 68 const simplePath = useSelector( 69 (state: AppState) => state.objectBrowser.simplePath, 70 ); 71 const anonymousMode = useSelector( 72 (state: AppState) => state.system.anonymousMode, 73 ); 74 const selectedBucket = useSelector( 75 (state: AppState) => state.objectBrowser.selectedBucket, 76 ); 77 const records = useSelector((state: AppState) => state.objectBrowser.records); 78 79 const bucketName = params.bucketName || ""; 80 const pathSegment = location.pathname.split(`/browser/${bucketName}/`); 81 const internalPaths = pathSegment.length === 2 ? pathSegment[1] : ""; 82 83 const initWSRequest = useCallback( 84 (path: string) => { 85 let currDate = new Date(); 86 87 let date = currDate.toISOString(); 88 89 if (rewindDate !== null && rewindEnabled) { 90 date = rewindDate; 91 } 92 93 const payloadData = { 94 bucketName, 95 path, 96 rewindMode: rewindEnabled || showDeleted, 97 date: date, 98 }; 99 100 dispatch({ type: "socket/OBRequest", payload: payloadData }); 101 }, 102 [bucketName, showDeleted, rewindDate, rewindEnabled, dispatch], 103 ); 104 105 // Common path load 106 const pathLoad = useCallback( 107 (forceLoad: boolean = false) => { 108 const decodedInternalPaths = decodeURLString(internalPaths); 109 110 // We exit Versions mode in case of path change 111 dispatch(setVersionsModeEnabled({ status: false })); 112 113 let searchPath = decodedInternalPaths; 114 115 if (!decodedInternalPaths.endsWith("/") && decodedInternalPaths !== "") { 116 searchPath = `${decodedInternalPaths 117 .split("/") 118 .slice(0, -1) 119 .join("/")}/`; 120 } 121 122 if (searchPath === "/") { 123 searchPath = ""; 124 } 125 126 // If the path is different of the actual path or reload objects list is requested, then we initialize a new request to load a new record set. 127 if ( 128 searchPath !== simplePath || 129 bucketName !== selectedBucket || 130 forceLoad 131 ) { 132 dispatch(setRequestInProgress(true)); 133 initWSRequest(searchPath); 134 } 135 }, 136 [ 137 internalPaths, 138 dispatch, 139 simplePath, 140 selectedBucket, 141 bucketName, 142 initWSRequest, 143 ], 144 ); 145 146 useEffect(() => { 147 return () => { 148 dispatch({ type: "socket/OBCancelLast" }); 149 }; 150 }, [dispatch]); 151 152 // Object Details handler 153 useEffect(() => { 154 const decodedIPaths = decodeURLString(internalPaths); 155 156 dispatch(setLoadingVersioning(true)); 157 158 if (decodedIPaths.endsWith("/") || decodedIPaths === "") { 159 dispatch(setObjectDetailsView(false)); 160 dispatch(setSelectedObjectView(null)); 161 dispatch(setLoadingLocking(true)); 162 } else { 163 dispatch(setLoadingObjectInfo(true)); 164 dispatch(setObjectDetailsView(true)); 165 dispatch(setLoadingVersions(true)); 166 dispatch( 167 setSelectedObjectView( 168 `${decodedIPaths ? `${encodeURLString(decodedIPaths)}` : ``}`, 169 ), 170 ); 171 } 172 }, [bucketName, internalPaths, rewindDate, rewindEnabled, dispatch]); 173 174 // Navigation Listing Request 175 useEffect(() => { 176 pathLoad(false); 177 }, [pathLoad]); 178 179 // Reload Handler 180 useEffect(() => { 181 if (reloadObjectsList && records.length === 0 && !requestInProgress) { 182 pathLoad(true); 183 } 184 }, [reloadObjectsList, records, requestInProgress, pathLoad]); 185 186 const displayListObjects = 187 hasPermission(bucketName, [ 188 IAM_SCOPES.S3_LIST_BUCKET, 189 IAM_SCOPES.S3_ALL_LIST_BUCKET, 190 ]) || anonymousMode; 191 192 useEffect(() => { 193 if (loadingVersioning && !anonymousMode) { 194 if (displayListObjects) { 195 api.buckets 196 .getBucketVersioning(bucketName) 197 .then((res) => { 198 dispatch(setIsVersioned(res.data)); 199 dispatch(setLoadingVersioning(false)); 200 }) 201 .catch((err) => { 202 console.error( 203 "Error Getting Object Versioning Status: ", 204 err.error.detailedMessage, 205 ); 206 dispatch(setLoadingVersioning(false)); 207 }); 208 } else { 209 dispatch(setLoadingVersioning(false)); 210 dispatch(resetMessages()); 211 } 212 } 213 }, [ 214 bucketName, 215 loadingVersioning, 216 dispatch, 217 displayListObjects, 218 anonymousMode, 219 ]); 220 221 useEffect(() => { 222 if (loadingLocking) { 223 if (displayListObjects) { 224 api.buckets 225 .getBucketObjectLockingStatus(bucketName) 226 .then((res) => { 227 dispatch(setLockingEnabled(res.data.object_locking_enabled)); 228 dispatch(setLoadingLocking(false)); 229 }) 230 .catch((err) => { 231 console.error( 232 "Error Getting Object Locking Status: ", 233 err.error.detailedMessage, 234 ); 235 dispatch(setLoadingLocking(false)); 236 }); 237 } else { 238 dispatch(resetMessages()); 239 dispatch(setLoadingLocking(false)); 240 } 241 } 242 }, [bucketName, loadingLocking, dispatch, displayListObjects]); 243 244 return ( 245 <Fragment> 246 {!anonymousMode && <OBHeader bucketName={bucketName} />} 247 <ListObjects /> 248 </Fragment> 249 ); 250 }; 251 252 export default BrowserHandler;