From 605f4d4a62a9bf9a10dc545986ffd0a9cc00a9b9 Mon Sep 17 00:00:00 2001 From: Alex <33497058+bexsoft@users.noreply.github.com> Date: Thu, 9 Sep 2021 12:39:03 -0500 Subject: [PATCH] Object browser migrated into bucket details (#1017) Signed-off-by: Benjamin Perez --- pkg/acl/endpoints.go | 58 +-- pkg/acl/endpoints_test.go | 12 +- .../Buckets/BucketDetails/BrowserHandler.tsx | 107 ++++++ .../Buckets/BucketDetails/BucketDetails.tsx | 48 ++- .../BucketDetails/BucketEventsPanel.tsx | 2 +- .../BucketDetails/BucketLifecyclePanel.tsx | 2 +- .../BucketDetails/BucketReplicationPanel.tsx | 2 +- .../src/screens/Console/Buckets/Buckets.tsx | 16 +- .../Buckets/ListBuckets/BucketListItem.tsx | 155 ++++++++ .../Buckets/ListBuckets/ListBuckets.tsx | 81 +++-- .../Objects/ListObjects/CreateFolderModal.tsx | 23 +- .../Objects/ListObjects/ListObjects.tsx | 227 +++++------- .../Objects/ListObjects/ObjectRouting.tsx | 68 ---- .../Objects/ObjectDetails/ObjectDetails.tsx | 88 ++--- .../src/screens/Console/Buckets/types.tsx | 1 + portal-ui/src/screens/Console/Console.tsx | 14 - .../BasicDashboard/DriveInfoCard.tsx | 4 +- .../Console/DirectCSI/FormatErrorsResult.tsx | 1 - portal-ui/src/screens/Console/Menu/Menu.tsx | 8 - .../Console/ObjectBrowser/BrowseBuckets.tsx | 338 ------------------ .../ObjectBrowser/BrowserBreadcrumbs.tsx | 73 ++-- .../Console/ObjectBrowser/ObjectBrowser.tsx | 90 ----- .../screens/Console/ObjectBrowser/actions.ts | 133 +------ .../screens/Console/ObjectBrowser/reducers.ts | 124 +------ 24 files changed, 593 insertions(+), 1082 deletions(-) create mode 100644 portal-ui/src/screens/Console/Buckets/BucketDetails/BrowserHandler.tsx create mode 100644 portal-ui/src/screens/Console/Buckets/ListBuckets/BucketListItem.tsx delete mode 100644 portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx delete mode 100644 portal-ui/src/screens/Console/ObjectBrowser/BrowseBuckets.tsx delete mode 100644 portal-ui/src/screens/Console/ObjectBrowser/ObjectBrowser.tsx diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go index e6ac14cee..727fcf818 100644 --- a/pkg/acl/endpoints.go +++ b/pkg/acl/endpoints.go @@ -32,7 +32,11 @@ var ( metrics = "/metrics" profiling = "/profiling" buckets = "/buckets" - bucketsDetail = "/buckets/*" + bucketsGeneral = "/buckets/*" + bucketsAdmin = "/buckets/:bucketName/admin/*" + bucketsAdminMain = "/buckets/:bucketName/admin" + bucketsBrowser = "/buckets/:bucketName/browse/*" + bucketsBrowserMain = "/buckets/:bucketName/browse" serviceAccounts = "/account" changePassword = "/account/change-password" tenants = "/tenants" @@ -50,9 +54,6 @@ var ( storageDrives = "/storage/drives" remoteBuckets = "/remote-buckets" replication = "/replication" - objectBrowser = "/object-browser/:bucket/*" - objectBrowserBucket = "/object-browser/:bucket" - mainObjectBrowser = "/object-browser" license = "/license" watch = "/watch" heal = "/heal" @@ -281,30 +282,31 @@ var displayRules = map[string]func() bool{ // endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here var endpointRules = map[string]ConfigurationActionSet{ - configuration: configurationActionSet, - users: usersActionSet, - usersDetail: usersActionSet, - groups: groupsActionSet, - iamPolicies: iamPoliciesActionSet, - policiesDetail: iamPoliciesActionSet, - dashboard: dashboardActionSet, - metrics: dashboardActionSet, - profiling: profilingActionSet, - buckets: bucketsActionSet, - bucketsDetail: bucketsActionSet, - serviceAccounts: serviceAccountsActionSet, - changePassword: changePasswordActionSet, - remoteBuckets: remoteBucketsActionSet, - replication: replicationActionSet, - objectBrowser: objectBrowserActionSet, - mainObjectBrowser: objectBrowserActionSet, - objectBrowserBucket: objectBrowserActionSet, - license: licenseActionSet, - watch: watchActionSet, - heal: healActionSet, - trace: traceActionSet, - logs: logsActionSet, - healthInfo: healthInfoActionSet, + configuration: configurationActionSet, + users: usersActionSet, + usersDetail: usersActionSet, + groups: groupsActionSet, + iamPolicies: iamPoliciesActionSet, + policiesDetail: iamPoliciesActionSet, + dashboard: dashboardActionSet, + metrics: dashboardActionSet, + profiling: profilingActionSet, + buckets: bucketsActionSet, + bucketsGeneral: bucketsActionSet, + bucketsAdmin: bucketsActionSet, + bucketsAdminMain: bucketsActionSet, + serviceAccounts: serviceAccountsActionSet, + changePassword: changePasswordActionSet, + remoteBuckets: remoteBucketsActionSet, + replication: replicationActionSet, + bucketsBrowser: objectBrowserActionSet, + bucketsBrowserMain: objectBrowserActionSet, + license: licenseActionSet, + watch: watchActionSet, + heal: healActionSet, + trace: traceActionSet, + logs: logsActionSet, + healthInfo: healthInfoActionSet, } // operatorRules contains the mapping between endpoints and ActionSets for operator only mode diff --git a/pkg/acl/endpoints_test.go b/pkg/acl/endpoints_test.go index 9cd0c99bd..83ff555f4 100644 --- a/pkg/acl/endpoints_test.go +++ b/pkg/acl/endpoints_test.go @@ -50,7 +50,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { args: args{ []string{"admin:ServerInfo"}, }, - want: 8, + want: 7, }, { name: "policies endpoint", @@ -63,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "admin:ListUserPolicies", }, }, - want: 8, + want: 7, }, { name: "all admin endpoints", @@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "admin:*", }, }, - want: 22, + want: 21, }, { name: "all s3 endpoints", @@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "s3:*", }, }, - want: 8, + want: 9, }, { name: "all admin and s3 endpoints", @@ -91,14 +91,14 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "s3:*", }, }, - want: 24, + want: 25, }, { name: "Console User - default endpoints", args: args{ []string{}, }, - want: 6, + want: 5, }, } diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BrowserHandler.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BrowserHandler.tsx new file mode 100644 index 000000000..e4821beb6 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BrowserHandler.tsx @@ -0,0 +1,107 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useEffect } from "react"; +import { connect } from "react-redux"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { Link } from "react-router-dom"; +import { Grid, IconButton, Tooltip } from "@material-ui/core"; +import get from "lodash/get"; +import { AppState } from "../../../../store"; +import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary"; +import { setFileModeEnabled } from "../../ObjectBrowser/actions"; +import ObjectDetails from "../ListBuckets/Objects/ObjectDetails/ObjectDetails"; +import ListObjects from "../ListBuckets/Objects/ListObjects/ListObjects"; +import PageHeader from "../../Common/PageHeader/PageHeader"; +import { SettingsIcon } from "../../../../icons"; + +interface IBrowserHandlerProps { + fileMode: boolean; + match: any; + history: any; + classes: any; + setFileModeEnabled: typeof setFileModeEnabled; +} + +const styles = (theme: Theme) => + createStyles({ + breadcrumLink: { + textDecoration: "none", + color: "black", + }, + ...containerForHeader(theme.spacing(4)), + }); + +const BrowserHandler = ({ + fileMode, + match, + history, + classes, + setFileModeEnabled, +}: IBrowserHandlerProps) => { + const bucketName = match.params["bucketName"]; + const internalPaths = get(match.params, "subpaths", ""); + + useEffect(() => { + setFileModeEnabled(false); + }, [internalPaths, setFileModeEnabled]); + + const openBucketConfiguration = () => { + history.push(`/buckets/${bucketName}/admin`); + }; + + return ( + + + Buckets > {bucketName} + + } + actions={ + + + + + + + + } + /> + + {fileMode ? : } + + + ); +}; + +const mapStateToProps = ({ objectBrowser }: AppState) => ({ + fileMode: get(objectBrowser, "fileMode", false), + bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""), +}); + +const mapDispatchToProps = { + setFileModeEnabled, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export default withStyles(styles)(connector(BrowserHandler)); diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketDetails.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketDetails.tsx index 1f525a058..5cd6d509d 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketDetails.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketDetails.tsx @@ -47,7 +47,7 @@ import ListItem from "@material-ui/core/ListItem"; import ListItemText from "@material-ui/core/ListItemText"; import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle"; import { IconButton, Tooltip } from "@material-ui/core"; -import { BucketsIcon, DeleteIcon } from "../../../../icons"; +import { BucketsIcon, DeleteIcon, FolderIcon } from "../../../../icons"; import DeleteBucket from "../ListBuckets/DeleteBucket"; import AccessRulePanel from "./AccessRulePanel"; import RefreshIcon from "../../../../icons/RefreshIcon"; @@ -226,7 +226,7 @@ const BucketDetails = ({ ]); useEffect(() => { - let matchURL = match.params ? match.params["0"] : "summary"; + let matchURL = match.params ? match.params["0"] : "browse"; if (!matchURL) { matchURL = ""; @@ -283,22 +283,22 @@ const BucketDetails = ({ switch (newTab) { case "events": - mainRoute += "/events"; + mainRoute += "/admin/events"; break; case "replication": - mainRoute += "/replication"; + mainRoute += "/admin/replication"; break; case "lifecycle": - mainRoute += "/lifecycle"; + mainRoute += "/admin/lifecycle"; break; case "access": - mainRoute += "/access"; + mainRoute += "/admin/access"; break; case "prefix": - mainRoute += "/prefix"; + mainRoute += "/admin/prefix"; break; default: - mainRoute += "/summary"; + mainRoute += "/admin/summary"; } setBucketDetailsTab(newTab); @@ -312,6 +312,10 @@ const BucketDetails = ({ } }; + const openBucketBrowser = () => { + history.push(`/buckets/${bucketName}/browse`); + }; + return ( {deleteOpen && ( @@ -331,6 +335,20 @@ const BucketDetails = ({ } + actions={ + + + + + + + + } /> @@ -443,38 +461,38 @@ const BucketDetails = ({ {distributedSetup && ( )} {distributedSetup && ( )} ( - + )} /> diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketEventsPanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketEventsPanel.tsx index 3ba3e54b2..2ec150f95 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketEventsPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketEventsPanel.tsx @@ -20,7 +20,7 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { Button } from "@material-ui/core"; import get from "lodash/get"; import Grid from "@material-ui/core/Grid"; -import { AddIcon, CreateIcon } from "../../../../icons"; +import { AddIcon } from "../../../../icons"; import { BucketEvent, BucketEventList } from "../types"; import { setErrorSnackMessage } from "../../../../actions"; import { AppState } from "../../../../store"; diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx index bd3de809a..8a695f82f 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx @@ -22,7 +22,7 @@ import get from "lodash/get"; import * as reactMoment from "react-moment"; import Grid from "@material-ui/core/Grid"; import { LifeCycleItem } from "../types"; -import { AddIcon, CreateIcon } from "../../../../icons"; +import { AddIcon } from "../../../../icons"; import { actionsTray, searchField, diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx index 21900e670..fca9d6b5f 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketReplicationPanel.tsx @@ -19,7 +19,7 @@ import { connect } from "react-redux"; import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { Button } from "@material-ui/core"; import Grid from "@material-ui/core/Grid"; -import { AddIcon, CreateIcon } from "../../../../icons"; +import { AddIcon } from "../../../../icons"; import { setErrorSnackMessage } from "../../../../actions"; import { actionsTray, diff --git a/portal-ui/src/screens/Console/Buckets/Buckets.tsx b/portal-ui/src/screens/Console/Buckets/Buckets.tsx index dc55bcdbe..dfffbf3da 100644 --- a/portal-ui/src/screens/Console/Buckets/Buckets.tsx +++ b/portal-ui/src/screens/Console/Buckets/Buckets.tsx @@ -16,13 +16,14 @@ import React from "react"; import history from "../../../history"; -import { Route, Router, Switch, withRouter } from "react-router-dom"; +import { Route, Router, Switch, withRouter, Redirect } from "react-router-dom"; import { connect } from "react-redux"; import { AppState } from "../../../store"; import { setMenuOpen } from "../../../actions"; import NotFoundPage from "../../NotFoundPage"; import ListBuckets from "./ListBuckets/ListBuckets"; import BucketDetails from "./BucketDetails/BucketDetails"; +import BrowserHandler from "./BucketDetails/BrowserHandler"; const mapState = (state: AppState) => ({ open: state.system.sidebarOpen, @@ -34,8 +35,17 @@ const Buckets = () => { return ( - - + + + + + } + /> diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/BucketListItem.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/BucketListItem.tsx new file mode 100644 index 000000000..24dd9733b --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/BucketListItem.tsx @@ -0,0 +1,155 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import React, { Fragment } from "react"; +import { Link } from "react-router-dom"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { BucketsIcon } from "../../../../icons"; +import DeleteIcon from "@material-ui/icons/Delete"; +import { Bucket } from "../types"; +import { niceBytes } from "../../../../common/utils"; +import { IconButton, Tooltip } from "@material-ui/core"; +import CheckboxWrapper from "../../Common/FormComponents/CheckboxWrapper/CheckboxWrapper"; + +const styles = (theme: Theme) => + createStyles({ + linkContainer: { + textDecoration: "none", + position: "relative", + color: "initial", + textAlign: "center", + border: "#EAEDEE 1px solid", + borderRadius: 3, + padding: 15, + backgroundColor: "#fff", + width: 200, + margin: 10, + zIndex: 200, + "&:hover": { + backgroundColor: "#EAEAEA", + "& .innerElement": { + visibility: "visible", + }, + }, + "& .innerElement": { + visibility: "hidden", + }, + }, + bucketName: { + fontSize: 14, + fontWeight: "bold", + whiteSpace: "nowrap", + textOverflow: "ellipsis", + overflow: "hidden", + width: "100%", + display: "block", + }, + bucketsIcon: { + width: 40, + height: 40, + color: "#393939", + }, + bucketSize: { + color: "#777", + fontSize: 10, + }, + deleteButton: { + position: "absolute", + zIndex: 300, + top: 0, + right: 0, + }, + checkBoxElement: { + zIndex: 500, + position: "absolute", + display: "block", + bottom: 0, + left: 0, + }, + }); + +interface IBucketListItem { + bucket: Bucket; + classes: any; + onDelete: (bucketName: string) => void; + onSelect: (e: React.ChangeEvent) => void; + selected: boolean; +} + +const BucketListItem = ({ + classes, + bucket, + onDelete, + onSelect, + selected, +}: IBucketListItem) => { + const onDeleteClick = (e: any) => { + e.preventDefault(); + e.stopPropagation(); + onDelete(bucket.name); + }; + + const onCheckboxClick = (e: React.ChangeEvent) => { + e.stopPropagation(); + e.preventDefault(); + onSelect(e); + }; + + return ( + + + + + + + + +
+ + {bucket.name} + + + Used Space: {niceBytes(bucket.size || "0")} + + { + e.stopPropagation(); + }} + > + + + +
+ ); +}; + +export default withStyles(styles)(BucketListItem); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx index 0daa6bdf1..814732f3c 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx @@ -22,10 +22,8 @@ import Grid from "@material-ui/core/Grid"; import TextField from "@material-ui/core/TextField"; import InputAdornment from "@material-ui/core/InputAdornment"; import FileCopyIcon from "@material-ui/icons/FileCopy"; -import Moment from "react-moment"; import { Bucket, BucketList, HasPermissionResponse } from "../types"; -import { AddIcon, CreateIcon } from "../../../../icons"; -import { niceBytes } from "../../../../common/utils"; +import { AddIcon } from "../../../../icons"; import { AppState } from "../../../../store"; import { addBucketOpen, addBucketReset } from "../actions"; import { setErrorSnackMessage } from "../../../../actions"; @@ -36,12 +34,12 @@ import { } from "../../Common/FormComponents/common/styleLibrary"; import { ErrorResponseHandler } from "../../../../common/types"; import api from "../../../../common/api"; -import TableWrapper from "../../Common/TableWrapper/TableWrapper"; import AddBucket from "./AddBucket"; import DeleteBucket from "./DeleteBucket"; import PageHeader from "../../Common/PageHeader/PageHeader"; import BulkReplicationModal from "./BulkReplicationModal"; import SearchIcon from "../../../../icons/SearchIcon"; +import BucketListItem from "./BucketListItem"; const styles = (theme: Theme) => createStyles({ @@ -70,6 +68,12 @@ const styles = (theme: Theme) => }, }, }, + bucketsIconsContainer: { + display: "flex", + flexWrap: "wrap", + flexDirection: "row", + justifyContent: "flex-start", + }, ...actionsTray, ...searchField, ...containerForHeader(theme.spacing(4)), @@ -77,6 +81,7 @@ const styles = (theme: Theme) => interface IListBucketsProps { classes: any; + history: any; addBucketOpen: typeof addBucketOpen; addBucketModalOpen: boolean; addBucketReset: typeof addBucketReset; @@ -85,6 +90,7 @@ interface IListBucketsProps { const ListBuckets = ({ classes, + history, addBucketOpen, addBucketModalOpen, addBucketReset, @@ -178,15 +184,6 @@ const ListBuckets = ({ setSelectedBucket(bucket); }; - const tableActions = [ - { type: "view", to: `/buckets`, sendOnlyId: true }, - { type: "delete", onClick: confirmDeleteBucket, sendOnlyId: true }, - ]; - - const displayParsedDate = (date: string) => { - return {date}; - }; - const filteredRecords = records.filter((b: Bucket) => { if (filterBuckets === "") { return true; @@ -226,6 +223,27 @@ const ListBuckets = ({ } }; + /* + [ + { label: "Name", elementKey: "name" }, + { + label: "Creation Date", + elementKey: "creation_date", + renderFunction: displayParsedDate, + }, + { + label: "Size", + elementKey: "size", + renderFunction: niceBytes, + width: 60, + contentTextAlign: "right", + }, + ] + + + + */ + return ( {addBucketModalOpen && ( @@ -298,31 +316,18 @@ const ListBuckets = ({
- - + + {filteredRecords.map((bucket, index) => { + return ( + + ); + })}
diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx index e59d36238..1bb136c74 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx @@ -21,13 +21,15 @@ import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/I import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary"; import { connect } from "react-redux"; -import { createFolder } from "../../../../ObjectBrowser/actions"; +import { setFileModeEnabled } from "../../../../ObjectBrowser/actions"; +import history from "../../../../../../history"; interface ICreateFolder { classes: any; modalOpen: boolean; + bucketName: string; folderName: string; - createFolder: (newFolder: string) => any; + setFileModeEnabled: typeof setFileModeEnabled; onClose: () => any; } @@ -46,23 +48,28 @@ const styles = (theme: Theme) => const CreateFolderModal = ({ modalOpen, folderName, + bucketName, onClose, - createFolder, + setFileModeEnabled, classes, }: ICreateFolder) => { const [pathUrl, setPathUrl] = useState(""); + const currentPath = `${bucketName}/${folderName}`; + const resetForm = () => { setPathUrl(""); }; const createProcess = () => { - createFolder(pathUrl); + const newPath = `/buckets/${bucketName}/browse/${folderName !== "" ? `${folderName}/` : ""}${pathUrl}`; + + history.push(newPath); + + setFileModeEnabled(false); onClose(); }; - const folderTruncated = folderName.split("/").slice(2).join("/"); - return (

- Current Path: {folderTruncated}/ + Current Path: {currentPath}

interface IListObjectsProps { classes: any; match: any; - addRoute: (param1: string, param2: string, param3: string) => any; - setAllRoutes: (path: string) => any; + history: any; routesList: Route[]; downloadingFiles: string[]; - setLastAsFile: () => any; rewindEnabled: boolean; rewindDate: any; bucketToRewind: string; setLoadingProgress: typeof setLoadingProgress; setSnackBarMessage: typeof setSnackBarMessage; setErrorSnackMessage: typeof setErrorSnackMessage; - fileIsBeingPrepared: typeof fileIsBeingPrepared; - fileDownloadStarted: typeof fileDownloadStarted; resetRewind: typeof resetRewind; + setFileModeEnabled: typeof setFileModeEnabled; } function useInterval(callback: any, delay: number) { @@ -223,30 +215,25 @@ const defLoading = Loading...; const ListObjects = ({ classes, match, - addRoute, - setAllRoutes, - routesList, + history, downloadingFiles, rewindEnabled, rewindDate, bucketToRewind, - setLastAsFile, setLoadingProgress, setSnackBarMessage, setErrorSnackMessage, - fileIsBeingPrepared, - fileDownloadStarted, resetRewind, + setFileModeEnabled, }: IListObjectsProps) => { const [records, setRecords] = useState([]); const [loading, setLoading] = useState(true); const [rewind, setRewind] = useState([]); - const [loadingRewind, setLoadingRewind] = useState(true); + const [loadingRewind, setLoadingRewind] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); const [deleteMultipleOpen, setDeleteMultipleOpen] = useState(false); const [createFolderOpen, setCreateFolderOpen] = useState(false); const [selectedObject, setSelectedObject] = useState(""); - const [selectedBucket, setSelectedBucket] = useState(""); const [filterObjects, setFilterObjects] = useState(""); const [loadingStartTime, setLoadingStartTime] = useState(0); const [loadingMessage, setLoadingMessage] = @@ -260,9 +247,8 @@ const ListObjects = ({ null ); - const internalPaths = match.params[0]; - - const bucketName = match.params["bucket"]; + const internalPaths = get(match.params, "subpaths", ""); + const bucketName = match.params["bucketName"]; const fileUpload = useRef(null); @@ -354,55 +340,10 @@ const ListObjects = ({ ]); useEffect(() => { - const internalPaths = match.params[0]; - - const verifyIfIsFile = () => { - if (rewindEnabled) { - const rewindParsed = rewindDate.toISOString(); - api - .invoke( - "GET", - `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${ - internalPaths ? `${internalPaths}/` : "" - }` - ) - .then((res: RewindObjectList) => { - //It is a file since it has elements in the object, setting file flag and waiting for component mount - if (res.objects === null) { - setLastAsFile(); - } else { - // It is a folder, we remove loader - setLoadingRewind(false); - setLoading(false); - } - }) - .catch((err: ErrorResponseHandler) => { - setLoadingRewind(false); - setLoading(false); - setErrorSnackMessage(err); - }); - } else { - api - .invoke( - "GET", - `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}` - ) - .then((res: BucketObjectsList) => { - //It is a file since it has elements in the object, setting file flag and waiting for component mount - if (res.objects !== null) { - setLastAsFile(); - } else { - // It is a folder, we remove loader - setLoading(false); - } - }) - .catch((err: ErrorResponseHandler) => { - setLoading(false); - setErrorSnackMessage(err); - }); - } - }; + setLoading(true); + }, [internalPaths]); + useEffect(() => { if (loading) { let extraPath = ""; if (internalPaths) { @@ -416,8 +357,6 @@ const ListObjects = ({ api .invoke("GET", `/api/v1/buckets/${bucketName}/objects${extraPath}`) .then((res: BucketObjectsList) => { - setSelectedBucket(bucketName); - const records: BucketObject[] = res.objects || []; const folders: BucketObject[] = []; const files: BucketObject[] = []; @@ -437,10 +376,68 @@ const ListObjects = ({ setRecords(recordsInElement); // In case no objects were retrieved, We check if item is a file if (!res.objects && extraPath !== "") { - verifyIfIsFile(); - return; + if (rewindEnabled) { + const rewindParsed = rewindDate.toISOString(); + api + .invoke( + "GET", + `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${ + internalPaths ? `${internalPaths}/` : "" + }` + ) + .then((res: RewindObjectList) => { + //It is a file since it has elements in the object, setting file flag and waiting for component mount + if (res.objects === null) { + setFileModeEnabled(true); + setLoadingRewind(false); + setLoading(false); + } else { + // It is a folder, we remove loader + setLoadingRewind(false); + setLoading(false); + setFileModeEnabled(false); + } + }) + .catch((err: ErrorResponseHandler) => { + setLoadingRewind(false); + setLoading(false); + setErrorSnackMessage(err); + }); + } else { + api + .invoke( + "GET", + `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}` + ) + .then((res: BucketObjectsList) => { + //It is a file since it has elements in the object, setting file flag and waiting for component mount + if (!res.objects) { + // It is a folder, we remove loader + setFileModeEnabled(false); + setLoading(false); + } else { + // This is an empty folder. + if ( + res.objects.length === 1 && + res.objects[0].name.endsWith("/") + ) { + setFileModeEnabled(false); + } else { + setFileModeEnabled(true); + } + + setLoading(false); + } + }) + .catch((err: ErrorResponseHandler) => { + setLoading(false); + setErrorSnackMessage(err); + }); + } + } else { + setFileModeEnabled(false); + setLoading(false); } - setLoading(false); }) .catch((err: ErrorResponseHandler) => { setLoading(false); @@ -450,24 +447,14 @@ const ListObjects = ({ }, [ loading, match, - setLastAsFile, setErrorSnackMessage, bucketName, rewindEnabled, rewindDate, + internalPaths, + setFileModeEnabled, ]); - useEffect(() => { - const url = get(match, "url", "/object-browser"); - if (url !== routesList[routesList.length - 1].route) { - setAllRoutes(url); - } - }, [match, routesList, setAllRoutes]); - - useEffect(() => { - setLoading(true); - }, [routesList, setLoading]); - const closeDeleteModalAndRefresh = (refresh: boolean) => { setDeleteOpen(false); @@ -580,10 +567,6 @@ const ListObjects = ({ setSelectedObject(object); }; - const removeDownloadAnimation = (path: string) => { - fileDownloadStarted(path); - }; - const displayDeleteFlag = (state: boolean) => { return state ? "Yes" : "No"; }; @@ -596,16 +579,11 @@ const ListObjects = ({ ); } - download( - selectedBucket, - object.name, - object.version_id, - removeDownloadAnimation - ); + download(bucketName, object.name, object.version_id); }; const openPath = (idElement: string) => { - const currentPath = get(match, "url", "/object-browser"); + const currentPath = get(match, "url", `/buckets/${bucketName}`); // Element is a folder, we redirect to it if (idElement.endsWith("/")) { @@ -615,7 +593,7 @@ const ListObjects = ({ const lastIndex = idElementClean.length - 1; const newPath = `${currentPath}/${idElementClean[lastIndex]}`; - addRoute(newPath, idElementClean[lastIndex], "path"); + history.push(newPath); return; } // Element is a file. we open details here @@ -623,24 +601,12 @@ const ListObjects = ({ const fileName = pathInArray[pathInArray.length - 1]; const newPath = `${currentPath}/${fileName}`; - addRoute(newPath, fileName, "file"); + history.push(newPath); return; }; const uploadObject = (e: any): void => { - // Handle of deeper routes. - const currentPath = routesList[routesList.length - 1].route; - const splitPaths = currentPath - .split("/") - .filter((item) => item.trim() !== ""); - - let path = ""; - - if (splitPaths.length > 2) { - path = `${splitPaths.slice(2).join("/")}/`; - } - - upload(e, selectedBucket, path); + upload(e, bucketName, `${internalPaths}/`); }; const openPreview = (fileObject: BucketObject) => { @@ -798,23 +764,16 @@ const ListObjects = ({ }, ]; - let pageTitle = "Folder"; + const ccPath = internalPaths.split("/").pop(); - if (match) { - if ("bucket" in match.params) { - pageTitle = match.params["bucket"]; - } - if ("0" in match.params) { - pageTitle = match.params["0"].split("/").pop(); - } - } + const pageTitle = ccPath !== "" ? ccPath : "/"; return ( {deleteOpen && ( @@ -822,7 +781,7 @@ const ListObjects = ({ {deleteMultipleOpen && ( @@ -830,7 +789,8 @@ const ListObjects = ({ {createFolderOpen && ( )} @@ -850,8 +810,7 @@ const ListObjects = ({ /> )} - - + - + } actions={ @@ -983,12 +945,13 @@ const ListObjects = ({ columns={rewindEnabled ? rewindModeColumns : listModeColumns} isLoading={rewindEnabled ? loadingRewind : loading} loadingMessage={loadingMessage} - entityName="Rewind Objects" + entityName="Objects" idField="name" records={rewindEnabled ? rewind : filteredRecords} customPaperHeight={classes.browsePaper} selectedItems={selectedObjects} onSelect={selectListObjects} + customEmptyMessage={`This location is empty${!rewindEnabled ? ", please try uploading a new file" : ""}`} /> @@ -1005,14 +968,10 @@ const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({ }); const mapDispatchToProps = { - addRoute, - setAllRoutes, - setLastAsFile, setLoadingProgress, setSnackBarMessage, setErrorSnackMessage, - fileIsBeingPrepared, - fileDownloadStarted, + setFileModeEnabled, resetRewind, }; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx deleted file mode 100644 index 8ffdda9ca..000000000 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of MinIO Console Server -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import React, { useEffect } from "react"; -import ListObjects from "./ListObjects"; -import ObjectDetails from "../ObjectDetails/ObjectDetails"; -import get from "lodash/get"; -import { setAllRoutes } from "../../../../ObjectBrowser/actions"; -import { connect } from "react-redux"; -import { withRouter } from "react-router-dom"; -import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers"; - -interface ObjectBrowserReducer { - objectBrowser: ObjectBrowserState; -} - -interface ObjectRoutingProps { - routesList: Route[]; - setAllRoutes: (path: string) => any; - match: any; -} - -const ObjectRouting = ({ - routesList, - match, - setAllRoutes, -}: ObjectRoutingProps) => { - const currentItem = routesList[routesList.length - 1]; - - useEffect(() => { - const url = get(match, "url", "/object-browser"); - - if (url !== routesList[routesList.length - 1].route) { - setAllRoutes(url); - } - }, [match, routesList, setAllRoutes]); - - return currentItem.type === "path" ? ( - - ) : ( - - ); -}; - -const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({ - routesList: get(objectBrowser, "routesList", []), -}); - -const mapDispatchToProps = { - setAllRoutes, -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); - -export default withRouter(connector(ObjectRouting)); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx index 3103ddd53..d87512200 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx @@ -16,6 +16,7 @@ import React, { Fragment, useEffect, useState } from "react"; import { connect } from "react-redux"; +import { withRouter } from "react-router-dom"; import get from "lodash/get"; import * as reactMoment from "react-moment"; import clsx from "clsx"; @@ -48,17 +49,10 @@ import { searchField, } from "../../../../Common/FormComponents/common/styleLibrary"; import { FileInfoResponse, IFileInfo } from "./types"; -import { - fileDownloadStarted, - fileIsBeingPrepared, - removeRouteLevel, -} from "../../../../ObjectBrowser/actions"; -import { Route } from "../../../../ObjectBrowser/reducers"; import { download, extensionPreview } from "../utils"; import { TabPanel } from "../../../../../shared/tabs"; import history from "../../../../../../history"; import api from "../../../../../../common/api"; -import PageHeader from "../../../../Common/PageHeader/PageHeader"; import ShareIcon from "../../../../../../icons/ShareIcon"; import DownloadIcon from "../../../../../../icons/DownloadIcon"; import DeleteIcon from "../../../../../../icons/DeleteIcon"; @@ -86,7 +80,7 @@ import { BucketObject } from "../ListObjects/types"; const styles = (theme: Theme) => createStyles({ - objectNameContainer: { + currentItemContainer: { marginBottom: 8, }, objectPathContainer: { @@ -98,7 +92,7 @@ const styles = (theme: Theme) => color: "#000", }, }, - objectName: { + currentItem: { fontSize: 24, }, propertiesContainer: { @@ -212,17 +206,14 @@ const styles = (theme: Theme) => interface IObjectDetailsProps { classes: any; - routesList: Route[]; downloadingFiles: string[]; rewindEnabled: boolean; rewindDate: any; + match: any; bucketToRewind: string; distributedSetup: boolean; - removeRouteLevel: (newRoute: string) => any; setErrorSnackMessage: typeof setErrorSnackMessage; setSnackBarMessage: typeof setSnackBarMessage; - fileIsBeingPrepared: typeof fileIsBeingPrepared; - fileDownloadStarted: typeof fileDownloadStarted; } const emptyFile: IFileInfo = { @@ -239,17 +230,14 @@ const emptyFile: IFileInfo = { const ObjectDetails = ({ classes, - routesList, downloadingFiles, rewindEnabled, rewindDate, distributedSetup, + match, bucketToRewind, - removeRouteLevel, setErrorSnackMessage, setSnackBarMessage, - fileIsBeingPrepared, - fileDownloadStarted, }: IObjectDetailsProps) => { const [loadObjectData, setLoadObjectData] = useState(true); const [shareFileModalOpen, setShareFileModalOpen] = useState(false); @@ -266,11 +254,10 @@ const ObjectDetails = ({ const [metadata, setMetadata] = useState({}); const [selectedTab, setSelectedTab] = useState(0); - const currentItem = routesList[routesList.length - 1]; - const allPathData = currentItem.route.split("/"); - const objectName = allPathData[allPathData.length - 1]; - const bucketName = allPathData[2]; - const pathInBucket = allPathData.slice(3).join("/"); + const internalPaths = get(match.params, "subpaths", ""); + const bucketName = match.params["bucketName"]; + const allPathData = internalPaths.split("/"); + const currentItem = allPathData.pop(); const previewObject: BucketObject = { name: actualInfo.name, @@ -282,7 +269,7 @@ const ObjectDetails = ({ useEffect(() => { if (loadObjectData) { - const encodedPath = encodeURIComponent(pathInBucket); + const encodedPath = encodeURIComponent(internalPaths); api .invoke( "GET", @@ -313,14 +300,14 @@ const ObjectDetails = ({ }, [ loadObjectData, bucketName, - pathInBucket, + internalPaths, setErrorSnackMessage, distributedSetup, ]); useEffect(() => { if (metadataLoad) { - const encodedPath = encodeURIComponent(pathInBucket); + const encodedPath = encodeURIComponent(internalPaths); api .invoke( "GET", @@ -337,7 +324,7 @@ const ObjectDetails = ({ setMetadataLoad(false); }); } - }, [bucketName, metadataLoad, pathInBucket]); + }, [bucketName, metadataLoad, internalPaths]); let tagKeys: string[] = []; @@ -369,10 +356,6 @@ const ObjectDetails = ({ setDeleteTagModalOpen(true); }; - const removeDownloadAnimation = (path: string) => { - fileDownloadStarted(path); - }; - const downloadObject = (object: IFileInfo, includeVersion?: boolean) => { if (object.size && parseInt(object.size) > 104857600) { // If file is bigger than 100MB we show a notification @@ -382,9 +365,9 @@ const ObjectDetails = ({ } download( bucketName, - pathInBucket, + internalPaths, object.version_id, - removeDownloadAnimation, + () => {}, includeVersion ); }; @@ -432,10 +415,8 @@ const ObjectDetails = ({ setDeleteOpen(false); if (redirectBack) { - const newPath = allPathData.slice(0, -1).join("/"); - - removeRouteLevel(newPath); - history.push(newPath); + const newPath = allPathData.join("/"); + history.push(`/buckets/${bucketName}/browse${newPath === "" ? "" : `/${newPath}`}`); } }; @@ -477,7 +458,7 @@ const ObjectDetails = ({ @@ -486,7 +467,7 @@ const ObjectDetails = ({ )} @@ -494,7 +475,7 @@ const ObjectDetails = ({ )} - - + } - title={objectName} + title={currentItem} subTitle={ - + } actions={ @@ -621,7 +604,7 @@ const ObjectDetails = ({ onClick={() => { setSelectedTab(2); }} - disabled={extensionPreview(objectName) === "none"} + disabled={extensionPreview(currentItem) === "none"} > @@ -742,9 +725,9 @@ const ObjectDetails = ({ - {Object.keys(metadata).map((element) => { + {Object.keys(metadata).map((element, index) => { return ( - + {actualInfo.version_id && actualInfo.version_id !== "null" && ( { const versOrd = r.is_delete_marker ? "Yes" : "No"; return `${versOrd}`; @@ -857,13 +842,10 @@ const mapStateToProps = ({ objectBrowser, system }: AppState) => ({ }); const mapDispatchToProps = { - removeRouteLevel, setErrorSnackMessage, - fileIsBeingPrepared, - fileDownloadStarted, setSnackBarMessage, }; const connector = connect(mapStateToProps, mapDispatchToProps); -export default connector(withStyles(styles)(ObjectDetails)); +export default withRouter(connector(withStyles(styles)(ObjectDetails))); diff --git a/portal-ui/src/screens/Console/Buckets/types.tsx b/portal-ui/src/screens/Console/Buckets/types.tsx index 09bc14e3c..7c9e7e8c0 100644 --- a/portal-ui/src/screens/Console/Buckets/types.tsx +++ b/portal-ui/src/screens/Console/Buckets/types.tsx @@ -17,6 +17,7 @@ export interface Bucket { name: string; creation_date: Date; + size?: string; } export interface BucketEncryptionInfo { diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index de9248a27..05b89e155 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -48,8 +48,6 @@ import ConfigurationMain from "./Configurations/ConfigurationMain"; import WebhookPanel from "./Configurations/ConfigurationPanels/WebhookPanel"; import TenantsMain from "./Tenants/TenantsMain"; import TenantDetails from "./Tenants/TenantDetails/TenantDetails"; -import ObjectBrowser from "./ObjectBrowser/ObjectBrowser"; -import ObjectRouting from "./Buckets/ListBuckets/Objects/ListObjects/ObjectRouting"; import License from "./License/License"; import Trace from "./Trace/Trace"; import LogsMain from "./Logs/LogsMain"; @@ -238,18 +236,6 @@ const Console = ({ component: Buckets, path: "/buckets/*", }, - { - component: ObjectBrowser, - path: "/object-browser", - }, - { - component: ObjectRouting, - path: "/object-browser/:bucket", - }, - { - component: ObjectRouting, - path: "/object-browser/:bucket/*", - }, { component: Watch, path: "/watch", diff --git a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/DriveInfoCard.tsx b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/DriveInfoCard.tsx index a19dbe086..865dbdc5b 100644 --- a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/DriveInfoCard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/DriveInfoCard.tsx @@ -87,14 +87,14 @@ interface ICardProps { } const DriveInfoCard = ({ classes, drive }: ICardProps) => { - console.log(drive); const driveStatusToClass = (health_status: string) => { switch (health_status) { case "offline": return classes.redState; case "ok": return classes.greenState; - deefault: return classes.greyState; + default: + return classes.greyState; } }; diff --git a/portal-ui/src/screens/Console/DirectCSI/FormatErrorsResult.tsx b/portal-ui/src/screens/Console/DirectCSI/FormatErrorsResult.tsx index 17ff94f19..22c5fb928 100644 --- a/portal-ui/src/screens/Console/DirectCSI/FormatErrorsResult.tsx +++ b/portal-ui/src/screens/Console/DirectCSI/FormatErrorsResult.tsx @@ -52,7 +52,6 @@ const download = (filename: string, text: string) => { "href", "data:application/json;charset=utf-8," + encodeURIComponent(text) ); - console.log(filename); element.setAttribute("download", filename); element.style.display = "none"; diff --git a/portal-ui/src/screens/Console/Menu/Menu.tsx b/portal-ui/src/screens/Console/Menu/Menu.tsx index abdc20a4b..ce3476096 100644 --- a/portal-ui/src/screens/Console/Menu/Menu.tsx +++ b/portal-ui/src/screens/Console/Menu/Menu.tsx @@ -199,14 +199,6 @@ const Menu = ({ name: "Dashboard", icon: , }, - { - group: "User", - type: "item", - component: NavLink, - to: "/object-browser", - name: "Object Browser", - icon: , - }, { group: "User", type: "item", diff --git a/portal-ui/src/screens/Console/ObjectBrowser/BrowseBuckets.tsx b/portal-ui/src/screens/Console/ObjectBrowser/BrowseBuckets.tsx deleted file mode 100644 index a239045d5..000000000 --- a/portal-ui/src/screens/Console/ObjectBrowser/BrowseBuckets.tsx +++ /dev/null @@ -1,338 +0,0 @@ -// This file is part of MinIO Console Server -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import React, { useState, useEffect, Fragment } from "react"; -import { withRouter } from "react-router-dom"; -import { connect } from "react-redux"; -import get from "lodash/get"; -import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import Grid from "@material-ui/core/Grid"; -import TextField from "@material-ui/core/TextField"; -import InputAdornment from "@material-ui/core/InputAdornment"; -import { IconButton, Tooltip } from "@material-ui/core"; -import { AddIcon, BucketsIcon, CreateIcon } from "../../../icons"; -import { niceBytes } from "../../../common/utils"; -import { Bucket, BucketList, HasPermissionResponse } from "../Buckets/types"; -import { - actionsTray, - objectBrowserCommon, - searchField, -} from "../Common/FormComponents/common/styleLibrary"; -import { addRoute, resetRoutesList } from "./actions"; -import { setErrorSnackMessage } from "../../../actions"; -import { ErrorResponseHandler } from "../../../common/types"; -import BrowserBreadcrumbs from "./BrowserBreadcrumbs"; -import TableWrapper from "../Common/TableWrapper/TableWrapper"; -import AddBucket from "../Buckets/ListBuckets/AddBucket"; -import api from "../../../common/api"; -import ScreenTitle from "../Common/ScreenTitle/ScreenTitle"; -import RefreshIcon from "../../../icons/RefreshIcon"; -import SearchIcon from "../../../icons/SearchIcon"; - -const styles = (theme: Theme) => - createStyles({ - seeMore: { - marginTop: theme.spacing(3), - }, - paper: { - display: "flex", - overflow: "auto", - flexDirection: "column", - }, - - addSideBar: { - width: "320px", - padding: "20px", - }, - tableToolbar: { - paddingLeft: theme.spacing(2), - paddingRight: theme.spacing(0), - }, - minTableHeader: { - color: "#393939", - "& tr": { - "& th": { - fontWeight: "bold", - }, - }, - }, - usedSpaceCol: { - width: 150, - textAlign: "right", - }, - subTitleLabel: { - alignItems: "center", - display: "flex", - }, - bucketName: { - display: "flex", - alignItems: "center", - "& .MuiSvgIcon-root": { - width: 16, - height: 16, - marginRight: 4, - }, - }, - iconBucket: { - backgroundImage: "url(/images/ob_bucket_clear.svg)", - backgroundRepeat: "no-repeat", - backgroundPosition: "center center", - width: 16, - height: 40, - marginRight: 10, - }, - "@global": { - ".rowLine:hover .iconBucketElm": { - backgroundImage: "url(/images/ob_bucket_filled.svg)", - }, - }, - browsePaper: { - height: "calc(100vh - 280px)", - }, - ...actionsTray, - ...searchField, - ...objectBrowserCommon, - }); - -interface IBrowseBucketsProps { - classes: any; - addRoute: (path: string, label: string, type: string) => any; - resetRoutesList: (doVar: boolean) => any; - displayErrorMessage: typeof setErrorSnackMessage; - match: any; -} - -const BrowseBuckets = ({ - classes, - match, - addRoute, - resetRoutesList, - displayErrorMessage, -}: IBrowseBucketsProps) => { - const [loading, setLoading] = useState(true); - const [records, setRecords] = useState([]); - const [addScreenOpen, setAddScreenOpen] = useState(false); - const [filterBuckets, setFilterBuckets] = useState(""); - const [loadingPerms, setLoadingPerms] = useState(true); - const [canCreateBucket, setCanCreateBucket] = useState(false); - - // check the permissions for creating bucket - useEffect(() => { - if (loadingPerms) { - api - .invoke("POST", `/api/v1/has-permission`, { - actions: [ - { - id: "createBucket", - action: "s3:CreateBucket", - }, - ], - }) - .then((res: HasPermissionResponse) => { - const canCreate = res.permissions - .filter((s) => s.id === "createBucket") - .pop(); - if (canCreate && canCreate.can) { - setCanCreateBucket(true); - } else { - setCanCreateBucket(false); - } - - setLoadingPerms(false); - // setRecords(res.buckets || []); - }) - .catch((err: ErrorResponseHandler) => { - setLoadingPerms(false); - setErrorSnackMessage(err); - }); - } - }, [loadingPerms]); - - useEffect(() => { - resetRoutesList(true); - }, [match, resetRoutesList]); - - useEffect(() => { - if (loading) { - api - .invoke("GET", `/api/v1/buckets`) - .then((res: BucketList) => { - setLoading(false); - setRecords(res.buckets || []); - }) - .catch((err: ErrorResponseHandler) => { - setLoading(false); - displayErrorMessage(err); - }); - } - }, [loading, displayErrorMessage]); - - const closeAddModalAndRefresh = (refresh: boolean) => { - setAddScreenOpen(false); - - if (refresh) { - setLoading(true); - } - }; - - const filteredRecords = records.filter((b: Bucket) => { - if (filterBuckets === "") { - return true; - } - return b.name.indexOf(filterBuckets) >= 0; - }); - - const handleViewChange = (idElement: string) => { - const currentPath = get(match, "url", "/object-browser"); - const newPath = `${currentPath}/${idElement}`; - - addRoute(newPath, idElement, "path"); - }; - - const renderBucket = (bucketName: string) => { - return ( -
- - {bucketName} -
- ); - }; - - return ( - - {addScreenOpen && ( - - )} - - - - - - } - title={"All Buckets"} - subTitle={ - - - - } - actions={ - - {canCreateBucket && ( - - - { - setAddScreenOpen(true); - }} - > - - - - - )} - - { - setLoading(true); - }} - > - - - - - } - /> - - - { - setFilterBuckets(val.target.value); - }} - InputProps={{ - disableUnderline: true, - startAdornment: ( - - - - ), - }} - /> - - -
-
- - - - - - ); -}; - -const mapDispatchToProps = { - addRoute, - resetRoutesList, - displayErrorMessage: setErrorSnackMessage, -}; - -const connector = connect(null, mapDispatchToProps); - -export default withRouter(connector(withStyles(styles)(BrowseBuckets))); diff --git a/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx b/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx index f40c21cad..4c9049585 100644 --- a/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx +++ b/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx @@ -21,8 +21,7 @@ import Moment from "react-moment"; import { connect } from "react-redux"; import { withStyles } from "@material-ui/core"; import { createStyles, Theme } from "@material-ui/core/styles"; -import { removeRouteLevel } from "./actions"; -import { ObjectBrowserState, Route } from "./reducers"; +import { ObjectBrowserState } from "./reducers"; import { objectBrowserCommon } from "../Common/FormComponents/common/styleLibrary"; import { Link } from "react-router-dom"; @@ -32,11 +31,10 @@ interface ObjectBrowserReducer { interface IObjectBrowser { classes: any; - objectsList: Route[]; - rewindEnabled: boolean; - rewindDate: any; - removeRouteLevel: (path: string) => any; - title?: boolean; + bucketName: string; + internalPaths: string; + rewindEnabled?: boolean; + rewindDate?: any; } const styles = (theme: Theme) => @@ -46,36 +44,44 @@ const styles = (theme: Theme) => const BrowserBreadcrumbs = ({ classes, - objectsList, + bucketName, + internalPaths, rewindEnabled, rewindDate, - removeRouteLevel, - title = true, }: IObjectBrowser) => { - const listBreadcrumbs = objectsList.map((objectItem, index) => { - return ( - - { - removeRouteLevel(objectItem.route); - }} - > - {objectItem.label} - - {index < objectsList.length - 1 && / } - - ); - }); + let paths = internalPaths; + + if (internalPaths !== "") { + paths = `/${internalPaths}`; + } + + const splitPaths = paths.split("/"); + + const listBreadcrumbs = splitPaths.map( + (objectItem: string, index: number) => { + const subSplit = splitPaths.slice(1, index + 1).join("/"); + + const route = `/buckets/${bucketName}/browse${ + objectItem !== "" ? `/${subSplit}` : ""}`; + const label = objectItem === "" ? bucketName : objectItem; + + return ( + + {label} + {index < splitPaths.length - 1 && / } + + ); + } + ); + + const title = false; return ( {title && (
- {objectsList && objectsList.length > 0 - ? objectsList.slice(-1)[0].label - : ""} - {rewindEnabled && objectsList.length > 1 && ( + {splitPaths && splitPaths.length > 0 ? splitPaths[splitPaths.length - 1] : ""} + {rewindEnabled && splitPaths.length > 1 && (  (Rewind:{" "} ) @@ -93,15 +99,10 @@ const BrowserBreadcrumbs = ({ }; const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({ - objectsList: get(objectBrowser, "routesList", []), rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false), rewindDate: get(objectBrowser, "rewind.dateToRewind", null), }); -const mapDispatchToProps = { - removeRouteLevel, -}; +const connector = connect(mapStateToProps, null); -const connector = connect(mapStateToProps, mapDispatchToProps); - -export default connector(withStyles(styles)(BrowserBreadcrumbs)); +export default withStyles(styles)(connector(BrowserBreadcrumbs)); diff --git a/portal-ui/src/screens/Console/ObjectBrowser/ObjectBrowser.tsx b/portal-ui/src/screens/Console/ObjectBrowser/ObjectBrowser.tsx deleted file mode 100644 index a07d9558f..000000000 --- a/portal-ui/src/screens/Console/ObjectBrowser/ObjectBrowser.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// This file is part of MinIO Console Server -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import React from "react"; -import get from "lodash/get"; -import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import { Grid } from "@material-ui/core"; -import BrowseBuckets from "./BrowseBuckets"; -import { containerForHeader } from "../Common/FormComponents/common/styleLibrary"; -import PageHeader from "../Common/PageHeader/PageHeader"; - -interface IObjectBrowserProps { - match: any; - classes: any; -} - -const styles = (theme: Theme) => - createStyles({ - watchList: { - background: "white", - maxHeight: "400", - overflow: "auto", - "& ul": { - margin: "4", - padding: "0", - }, - "& ul li": { - listStyle: "none", - margin: "0", - padding: "0", - borderBottom: "1px solid #dedede", - }, - }, - actionsTray: { - textAlign: "right", - "& button": { - marginLeft: 10, - }, - }, - inputField: { - background: "#FFFFFF", - padding: 12, - borderRadius: 5, - marginLeft: 10, - boxShadow: "0px 3px 6px #00000012", - }, - fieldContainer: { - background: "#FFFFFF", - padding: 0, - borderRadius: 5, - marginLeft: 10, - textAlign: "left", - minWidth: "206", - boxShadow: "0px 3px 6px #00000012", - }, - lastElementWPadding: { - paddingRight: "78", - }, - ...containerForHeader(theme.spacing(4)), - }); - -const ObjectBrowser = ({ match, classes }: IObjectBrowserProps) => { - const pathIn = get(match, "url", ""); - - return ( - - - - - {pathIn === "/object-browser" && } - - - - ); -}; - -export default withStyles(styles)(ObjectBrowser); diff --git a/portal-ui/src/screens/Console/ObjectBrowser/actions.ts b/portal-ui/src/screens/Console/ObjectBrowser/actions.ts index faf5eb73f..39514caec 100644 --- a/portal-ui/src/screens/Console/ObjectBrowser/actions.ts +++ b/portal-ui/src/screens/Console/ObjectBrowser/actions.ts @@ -14,61 +14,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -export const OBJECT_BROWSER_ADD_ROUTE = "OBJECT_BROWSER/ADD_ROUTE"; -export const OBJECT_BROWSER_RESET_ROUTES_LIST = - "OBJECT_BROWSER/RESET_ROUTES_LIST"; -export const OBJECT_BROWSER_REMOVE_ROUTE_LEVEL = - "OBJECT_BROWSER/REMOVE_ROUTE_LEVEL"; -export const OBJECT_BROWSER_SET_ALL_ROUTES = "OBJECT_BROWSER/SET_ALL_ROUTES"; -export const OBJECT_BROWSER_CREATE_FOLDER = "OBJECT_BROWSER/CREATE_FOLDER"; -export const OBJECT_BROWSER_SET_LAST_AS_FILE = - "OBJECT_BROWSER/SET_LAST_AS_FILE"; -export const OBJECT_BROWSER_DOWNLOAD_FILE_LOADER = - "OBJECT_BROWSER/DOWNLOAD_FILE_LOADER"; -export const OBJECT_BROWSER_DOWNLOADED_FILE = "OBJECT_BROWSER/DOWNLOADED_FILE"; export const REWIND_SET_ENABLE = "REWIND/SET_ENABLE"; export const REWIND_RESET_REWIND = "REWIND/RESET_REWIND"; -interface AddRouteAction { - type: typeof OBJECT_BROWSER_ADD_ROUTE; - route: string; - label: string; - routeType: string; -} - -interface ResetRoutesList { - type: typeof OBJECT_BROWSER_RESET_ROUTES_LIST; - reset: boolean; -} - -interface RemoveRouteLevel { - type: typeof OBJECT_BROWSER_REMOVE_ROUTE_LEVEL; - toRoute: string; -} - -interface SetAllRoutes { - type: typeof OBJECT_BROWSER_SET_ALL_ROUTES; - currentRoute: string; -} - -interface CreateFolder { - type: typeof OBJECT_BROWSER_CREATE_FOLDER; - newRoute: string; -} - -interface SetLastAsFile { - type: typeof OBJECT_BROWSER_SET_LAST_AS_FILE; -} - -interface SetFileDownload { - type: typeof OBJECT_BROWSER_DOWNLOAD_FILE_LOADER; - path: string; -} - -interface FileDownloaded { - type: typeof OBJECT_BROWSER_DOWNLOADED_FILE; - path: string; -} +export const REWIND_FILE_MODE_ENABLED = "BUCKET_BROWSER/FILE_MODE_ENABLED"; interface RewindSetEnabled { type: typeof REWIND_SET_ENABLE; @@ -81,74 +30,15 @@ interface RewindReset { type: typeof REWIND_RESET_REWIND; } +interface FileModeEnabled { + type: typeof REWIND_FILE_MODE_ENABLED; + status: boolean; +} + export type ObjectBrowserActionTypes = - | AddRouteAction - | ResetRoutesList - | RemoveRouteLevel - | SetAllRoutes - | CreateFolder - | SetLastAsFile - | SetFileDownload - | FileDownloaded | RewindSetEnabled - | RewindReset; - -export const addRoute = (route: string, label: string, routeType: string) => { - return { - type: OBJECT_BROWSER_ADD_ROUTE, - route, - label, - routeType, - }; -}; - -export const resetRoutesList = (reset: boolean) => { - return { - type: OBJECT_BROWSER_RESET_ROUTES_LIST, - reset, - }; -}; - -export const removeRouteLevel = (toRoute: string) => { - return { - type: OBJECT_BROWSER_REMOVE_ROUTE_LEVEL, - toRoute, - }; -}; - -export const setAllRoutes = (currentRoute: string) => { - return { - type: OBJECT_BROWSER_SET_ALL_ROUTES, - currentRoute, - }; -}; - -export const createFolder = (newRoute: string) => { - return { - type: OBJECT_BROWSER_CREATE_FOLDER, - newRoute, - }; -}; - -export const setLastAsFile = () => { - return { - type: OBJECT_BROWSER_SET_LAST_AS_FILE, - }; -}; - -export const fileIsBeingPrepared = (path: string) => { - return { - type: OBJECT_BROWSER_DOWNLOAD_FILE_LOADER, - path, - }; -}; - -export const fileDownloadStarted = (path: string) => { - return { - type: OBJECT_BROWSER_DOWNLOADED_FILE, - path, - }; -}; + | RewindReset + | FileModeEnabled; export const setRewindEnable = ( state: boolean, @@ -168,3 +58,10 @@ export const resetRewind = () => { type: REWIND_RESET_REWIND, }; }; + +export const setFileModeEnabled = (status: boolean) => { + return { + type: REWIND_FILE_MODE_ENABLED, + status, + }; +}; diff --git a/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts b/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts index fa5a16399..fe0556898 100644 --- a/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts +++ b/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts @@ -13,19 +13,10 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import history from "../../../history"; - import { - OBJECT_BROWSER_ADD_ROUTE, - OBJECT_BROWSER_CREATE_FOLDER, - OBJECT_BROWSER_REMOVE_ROUTE_LEVEL, - OBJECT_BROWSER_RESET_ROUTES_LIST, - OBJECT_BROWSER_SET_ALL_ROUTES, - OBJECT_BROWSER_SET_LAST_AS_FILE, - OBJECT_BROWSER_DOWNLOAD_FILE_LOADER, - OBJECT_BROWSER_DOWNLOADED_FILE, REWIND_SET_ENABLE, REWIND_RESET_REWIND, + REWIND_FILE_MODE_ENABLED, ObjectBrowserActionTypes, } from "./actions"; @@ -42,8 +33,7 @@ export interface RewindItem { } export interface ObjectBrowserState { - routesList: Route[]; - downloadingFiles: string[]; + fileMode: boolean; rewind: RewindItem; } @@ -51,10 +41,6 @@ export interface ObjectBrowserReducer { objectBrowser: ObjectBrowserState; } -const initialRoute = [ - { route: "/object-browser", label: "All Buckets", type: "path" }, -]; - const defaultRewind = { rewindEnabled: false, bucketToRewind: "", @@ -62,8 +48,7 @@ const defaultRewind = { }; const initialState: ObjectBrowserState = { - routesList: initialRoute, - downloadingFiles: [], + fileMode: false, rewind: { ...defaultRewind, }, @@ -74,107 +59,6 @@ export function objectBrowserReducer( action: ObjectBrowserActionTypes ): ObjectBrowserState { switch (action.type) { - case OBJECT_BROWSER_ADD_ROUTE: - const newRouteList = [ - ...state.routesList, - { route: action.route, label: action.label, type: action.routeType }, - ]; - history.push(action.route); - - return { ...state, routesList: newRouteList }; - case OBJECT_BROWSER_RESET_ROUTES_LIST: - return { - ...state, - routesList: [...initialRoute], - }; - case OBJECT_BROWSER_REMOVE_ROUTE_LEVEL: - const indexOfTopPath = - state.routesList.findIndex( - (element) => element.route === action.toRoute - ) + 1; - const newRouteLevels = state.routesList.slice(0, indexOfTopPath); - - return { - ...state, - routesList: newRouteLevels, - }; - case OBJECT_BROWSER_SET_ALL_ROUTES: - const splitRoutes = action.currentRoute.split("/"); - const routesArray: Route[] = []; - let initRoute = initialRoute[0].route; - - splitRoutes.forEach((route) => { - if (route !== "" && route !== "object-browser") { - initRoute = `${initRoute}/${route}`; - - routesArray.push({ - route: initRoute, - label: route, - type: "path", - }); - } - }); - - const newSetOfRoutes = [...initialRoute, ...routesArray]; - - return { - ...state, - routesList: newSetOfRoutes, - }; - case OBJECT_BROWSER_CREATE_FOLDER: - const newFoldersRoutes = [...state.routesList]; - let lastRoute = state.routesList[state.routesList.length - 1].route; - - const splitElements = action.newRoute.split("/"); - - splitElements.forEach((element) => { - const folderTrim = element.trim(); - if (folderTrim !== "") { - lastRoute = `${lastRoute}/${folderTrim}`; - - const newItem = { route: lastRoute, label: folderTrim, type: "path" }; - newFoldersRoutes.push(newItem); - } - }); - - history.push(lastRoute); - - return { - ...state, - routesList: newFoldersRoutes, - }; - case OBJECT_BROWSER_SET_LAST_AS_FILE: - const currentList = state.routesList; - const lastItem = currentList.slice(-1)[0]; - - if (lastItem.type === "path") { - lastItem.type = "file"; - } - - const newList = [...currentList.slice(0, -1), lastItem]; - - return { - ...state, - routesList: newList, - }; - case OBJECT_BROWSER_DOWNLOAD_FILE_LOADER: - const actualFiles = [...state.downloadingFiles]; - - actualFiles.push(action.path); - - return { - ...state, - downloadingFiles: [...actualFiles], - }; - case OBJECT_BROWSER_DOWNLOADED_FILE: - const downloadingFiles = state.downloadingFiles.filter( - (item) => item !== action.path - ); - - return { - ...state, - downloadingFiles: [...downloadingFiles], - }; case REWIND_SET_ENABLE: const rewindSetEnabled = { ...state.rewind, @@ -190,6 +74,8 @@ export function objectBrowserReducer( dateToRewind: null, }; return { ...state, rewind: resetItem }; + case REWIND_FILE_MODE_ENABLED: + return { ...state, fileMode: action.status }; default: return state; }