Object browser migrated into bucket details (#1017)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
<PageHeader
|
||||
label={
|
||||
<Fragment>
|
||||
<Link to={"/buckets"} className={classes.breadcrumLink}>Buckets</Link> > {bucketName}
|
||||
</Fragment>
|
||||
}
|
||||
actions={
|
||||
<Fragment>
|
||||
<Tooltip title={"Configure Bucket"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Configure Bucket"
|
||||
component="span"
|
||||
onClick={openBucketConfiguration}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
<Grid container className={classes.container}>
|
||||
{fileMode ? <ObjectDetails /> : <ListObjects />}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
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));
|
||||
@@ -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 (
|
||||
<Fragment>
|
||||
{deleteOpen && (
|
||||
@@ -331,6 +335,20 @@ const BucketDetails = ({
|
||||
</Link>
|
||||
</Fragment>
|
||||
}
|
||||
actions={
|
||||
<Fragment>
|
||||
<Tooltip title={"Browse Bucket"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Browse Bucket"
|
||||
component="span"
|
||||
onClick={openBucketBrowser}
|
||||
>
|
||||
<FolderIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
<Grid container className={classes.container}>
|
||||
<Grid item xs={12}>
|
||||
@@ -443,38 +461,38 @@ const BucketDetails = ({
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/buckets/:bucketName/summary"
|
||||
path="/buckets/:bucketName/admin/summary"
|
||||
component={BucketSummaryPanel}
|
||||
/>
|
||||
<Route
|
||||
path="/buckets/:bucketName/events"
|
||||
path="/buckets/:bucketName/admin/events"
|
||||
component={BucketEventsPanel}
|
||||
/>
|
||||
{distributedSetup && (
|
||||
<Route
|
||||
path="/buckets/:bucketName/replication"
|
||||
path="/buckets/:bucketName/admin/replication"
|
||||
component={BucketReplicationPanel}
|
||||
/>
|
||||
)}
|
||||
{distributedSetup && (
|
||||
<Route
|
||||
path="/buckets/:bucketName/lifecycle"
|
||||
path="/buckets/:bucketName/admin/lifecycle"
|
||||
component={BucketLifecyclePanel}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route
|
||||
path="/buckets/:bucketName/access"
|
||||
path="/buckets/:bucketName/admin/access"
|
||||
component={AccessDetailsPanel}
|
||||
/>
|
||||
<Route
|
||||
path="/buckets/:bucketName/prefix"
|
||||
path="/buckets/:bucketName/admin/prefix"
|
||||
component={AccessRulePanel}
|
||||
/>
|
||||
<Route
|
||||
path="/buckets/:bucketName"
|
||||
component={() => (
|
||||
<Redirect to={`/buckets/${bucketName}/summary`} />
|
||||
<Redirect to={`/buckets/${bucketName}/admin/summary`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/buckets/:bucketName/*" component={BucketDetails} />
|
||||
<Route path="/buckets/:bucketName" component={BucketDetails} />
|
||||
<Route path="/buckets/:bucketName/admin/*" component={BucketDetails} />
|
||||
<Route path="/buckets/:bucketName/admin" component={BucketDetails} />
|
||||
<Route
|
||||
path="/buckets/:bucketName/browse/:subpaths+"
|
||||
component={BrowserHandler}
|
||||
/>
|
||||
<Route path="/buckets/:bucketName/browse" component={BrowserHandler} />
|
||||
<Route
|
||||
path="/buckets/:bucketName"
|
||||
component={() => <Redirect to={`/buckets`} />}
|
||||
/>
|
||||
<Route path="/" component={ListBuckets} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
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<HTMLInputElement>) => 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<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onSelect(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Link
|
||||
to={`/buckets/${bucket.name}/browse`}
|
||||
className={classes.linkContainer}
|
||||
>
|
||||
<Tooltip title={`Delete ${bucket.name} bucket`}>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
aria-label="Delete Bucket"
|
||||
component="span"
|
||||
onClick={onDeleteClick}
|
||||
className={`innerElement ${classes.deleteButton}`}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<BucketsIcon width={40} height={40} className={classes.bucketsIcon} />
|
||||
<br />
|
||||
<Tooltip title={bucket.name}>
|
||||
<span className={classes.bucketName}>{bucket.name}</span>
|
||||
</Tooltip>
|
||||
<span className={classes.bucketSize}>
|
||||
<strong>Used Space:</strong> {niceBytes(bucket.size || "0")}
|
||||
</span>
|
||||
<span
|
||||
className={classes.checkBoxElement}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<CheckboxWrapper
|
||||
checked={selected}
|
||||
id={`select-${bucket.name}`}
|
||||
label={""}
|
||||
name={`select-${bucket.name}`}
|
||||
onChange={onCheckboxClick}
|
||||
value={bucket.name}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(BucketListItem);
|
||||
@@ -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 <Moment>{date}</Moment>;
|
||||
};
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
{addBucketModalOpen && (
|
||||
@@ -298,31 +316,18 @@ const ListBuckets = ({
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{
|
||||
label: "Creation Date",
|
||||
elementKey: "creation_date",
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
selectedItems={selectedBuckets}
|
||||
onSelect={selectListBuckets}
|
||||
/>
|
||||
<Grid item xs={12} className={classes.bucketsIconsContainer}>
|
||||
{filteredRecords.map((bucket, index) => {
|
||||
return (
|
||||
<BucketListItem
|
||||
bucket={bucket}
|
||||
key={`bucketListItem-${index.toString()}`}
|
||||
onDelete={confirmDeleteBucket}
|
||||
onSelect={selectListBuckets}
|
||||
selected={selectedBuckets.includes(bucket.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -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 (
|
||||
<React.Fragment>
|
||||
<ModalWrapper
|
||||
@@ -72,7 +79,7 @@ const CreateFolderModal = ({
|
||||
>
|
||||
<Grid container>
|
||||
<h3 className={classes.pathLabel}>
|
||||
Current Path: {folderTruncated}/
|
||||
Current Path: {currentPath}
|
||||
</h3>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
@@ -112,7 +119,7 @@ const CreateFolderModal = ({
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
createFolder,
|
||||
setFileModeEnabled,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
objectBrowserCommon,
|
||||
searchField,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../../../Common/PageHeader/PageHeader";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -50,12 +49,8 @@ import {
|
||||
import * as reactMoment from "react-moment";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
import {
|
||||
addRoute,
|
||||
fileDownloadStarted,
|
||||
fileIsBeingPrepared,
|
||||
resetRewind,
|
||||
setAllRoutes,
|
||||
setLastAsFile,
|
||||
setFileModeEnabled,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import {
|
||||
ObjectBrowserReducer,
|
||||
@@ -179,20 +174,17 @@ const styles = (theme: Theme) =>
|
||||
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 = <Typography component="h3">Loading...</Typography>;
|
||||
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<BucketObject[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [rewind, setRewind] = useState<RewindObject[]>([]);
|
||||
const [loadingRewind, setLoadingRewind] = useState<boolean>(true);
|
||||
const [loadingRewind, setLoadingRewind] = useState<boolean>(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
|
||||
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
|
||||
const [selectedObject, setSelectedObject] = useState<string>("");
|
||||
const [selectedBucket, setSelectedBucket] = useState<string>("");
|
||||
const [filterObjects, setFilterObjects] = useState<string>("");
|
||||
const [loadingStartTime, setLoadingStartTime] = useState<number>(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<HTMLInputElement>(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 (
|
||||
<React.Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteObject
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
selectedBucket={bucketName}
|
||||
selectedObject={selectedObject}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
@@ -822,7 +781,7 @@ const ListObjects = ({
|
||||
{deleteMultipleOpen && (
|
||||
<DeleteMultipleObjects
|
||||
deleteOpen={deleteMultipleOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
selectedBucket={bucketName}
|
||||
selectedObjects={selectedObjects}
|
||||
closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh}
|
||||
/>
|
||||
@@ -830,7 +789,8 @@ const ListObjects = ({
|
||||
{createFolderOpen && (
|
||||
<CreateFolderModal
|
||||
modalOpen={createFolderOpen}
|
||||
folderName={routesList[routesList.length - 1].route}
|
||||
bucketName={bucketName}
|
||||
folderName={internalPaths}
|
||||
onClose={closeAddFolderModal}
|
||||
/>
|
||||
)}
|
||||
@@ -850,8 +810,7 @@ const ListObjects = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<PageHeader label="Object Browser" />
|
||||
<Grid container className={classes.container}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<ScreenTitle
|
||||
icon={
|
||||
@@ -862,7 +821,10 @@ const ListObjects = ({
|
||||
title={pageTitle}
|
||||
subTitle={
|
||||
<Fragment>
|
||||
<BrowserBreadcrumbs title={false} />
|
||||
<BrowserBreadcrumbs
|
||||
bucketName={bucketName}
|
||||
internalPaths={internalPaths}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
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" : ""}`}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -1005,14 +968,10 @@ const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
addRoute,
|
||||
setAllRoutes,
|
||||
setLastAsFile,
|
||||
setLoadingProgress,
|
||||
setSnackBarMessage,
|
||||
setErrorSnackMessage,
|
||||
fileIsBeingPrepared,
|
||||
fileDownloadStarted,
|
||||
setFileModeEnabled,
|
||||
resetRewind,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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" ? (
|
||||
<ListObjects />
|
||||
) : (
|
||||
<ObjectDetails routesList={routesList} />
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
routesList: get(objectBrowser, "routesList", []),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setAllRoutes,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default withRouter(connector(ObjectRouting));
|
||||
@@ -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<boolean>(true);
|
||||
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
||||
@@ -266,11 +254,10 @@ const ObjectDetails = ({
|
||||
const [metadata, setMetadata] = useState<any>({});
|
||||
const [selectedTab, setSelectedTab] = useState<number>(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 = ({
|
||||
<SetRetention
|
||||
open={retentionModalOpen}
|
||||
closeModalAndRefresh={closeRetentionModal}
|
||||
objectName={objectName}
|
||||
objectName={currentItem}
|
||||
objectInfo={actualInfo}
|
||||
bucketName={bucketName}
|
||||
/>
|
||||
@@ -486,7 +467,7 @@ const ObjectDetails = ({
|
||||
<DeleteObject
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={bucketName}
|
||||
selectedObject={pathInBucket}
|
||||
selectedObject={internalPaths}
|
||||
closeDeleteModalAndRefresh={closeDeleteModal}
|
||||
/>
|
||||
)}
|
||||
@@ -494,7 +475,7 @@ const ObjectDetails = ({
|
||||
<AddTagModal
|
||||
modalOpen={tagModalOpen}
|
||||
currentTags={actualInfo.tags}
|
||||
selectedObject={pathInBucket}
|
||||
selectedObject={internalPaths}
|
||||
versionId={actualInfo.version_id}
|
||||
bucketName={bucketName}
|
||||
onCloseAndUpdate={closeAddTagModal}
|
||||
@@ -504,7 +485,7 @@ const ObjectDetails = ({
|
||||
<DeleteTagModal
|
||||
deleteOpen={deleteTagModalOpen}
|
||||
currentTags={actualInfo.tags}
|
||||
selectedObject={pathInBucket}
|
||||
selectedObject={internalPaths}
|
||||
versionId={actualInfo.version_id}
|
||||
bucketName={bucketName}
|
||||
onCloseAndUpdate={closeDeleteTagModal}
|
||||
@@ -515,14 +496,13 @@ const ObjectDetails = ({
|
||||
<SetLegalHoldModal
|
||||
open={legalholdOpen}
|
||||
closeModalAndRefresh={closeLegalholdModal}
|
||||
objectName={pathInBucket}
|
||||
objectName={internalPaths}
|
||||
bucketName={bucketName}
|
||||
actualInfo={actualInfo}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label={"Object Browser"} />
|
||||
|
||||
<Grid container className={classes.container}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<ScreenTitle
|
||||
icon={
|
||||
@@ -530,10 +510,13 @@ const ObjectDetails = ({
|
||||
<ObjectBrowserIcon width={40} />
|
||||
</Fragment>
|
||||
}
|
||||
title={objectName}
|
||||
title={currentItem}
|
||||
subTitle={
|
||||
<Fragment>
|
||||
<BrowserBreadcrumbs title={false} />
|
||||
<BrowserBreadcrumbs
|
||||
bucketName={bucketName}
|
||||
internalPaths={internalPaths}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
actions={
|
||||
@@ -621,7 +604,7 @@ const ObjectDetails = ({
|
||||
onClick={() => {
|
||||
setSelectedTab(2);
|
||||
}}
|
||||
disabled={extensionPreview(objectName) === "none"}
|
||||
disabled={extensionPreview(currentItem) === "none"}
|
||||
>
|
||||
<ListItemText primary="Preview" />
|
||||
</ListItem>
|
||||
@@ -742,9 +725,9 @@ const ObjectDetails = ({
|
||||
<Grid item xs={12}>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<TableBody>
|
||||
{Object.keys(metadata).map((element) => {
|
||||
{Object.keys(metadata).map((element, index) => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableRow key={`tRow-${index.toString()}`}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
@@ -773,7 +756,7 @@ const ObjectDetails = ({
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<TextField
|
||||
placeholder={`Search ${objectName}`}
|
||||
placeholder={`Search ${currentItem}`}
|
||||
className={clsx(classes.search, classes.searchField)}
|
||||
id="search-resource"
|
||||
label=""
|
||||
@@ -805,6 +788,7 @@ const ObjectDetails = ({
|
||||
versions.length - versions.indexOf(r);
|
||||
return `v${versOrd}`;
|
||||
},
|
||||
elementKey: "version_id"
|
||||
},
|
||||
{ label: "Version ID", elementKey: "version_id" },
|
||||
{
|
||||
@@ -817,6 +801,7 @@ const ObjectDetails = ({
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
renderFullObject: true,
|
||||
elementKey: "is_delete_marker",
|
||||
renderFunction: (r) => {
|
||||
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)));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
export interface Bucket {
|
||||
name: string;
|
||||
creation_date: Date;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export interface BucketEncryptionInfo {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -199,14 +199,6 @@ const Menu = ({
|
||||
name: "Dashboard",
|
||||
icon: <DashboardIcon />,
|
||||
},
|
||||
{
|
||||
group: "User",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/object-browser",
|
||||
name: "Object Browser",
|
||||
icon: <ObjectBrowserIcon />,
|
||||
},
|
||||
{
|
||||
group: "User",
|
||||
type: "item",
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<boolean>(true);
|
||||
const [records, setRecords] = useState<Bucket[]>([]);
|
||||
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
|
||||
const [filterBuckets, setFilterBuckets] = useState<string>("");
|
||||
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
|
||||
const [canCreateBucket, setCanCreateBucket] = useState<boolean>(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 (
|
||||
<div className={classes.bucketName}>
|
||||
<BucketsIcon />
|
||||
<span>{bucketName}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddBucket
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<ScreenTitle
|
||||
icon={
|
||||
<Fragment>
|
||||
<BucketsIcon width={40} />
|
||||
</Fragment>
|
||||
}
|
||||
title={"All Buckets"}
|
||||
subTitle={
|
||||
<Fragment>
|
||||
<BrowserBreadcrumbs title={false} />
|
||||
</Fragment>
|
||||
}
|
||||
actions={
|
||||
<Fragment>
|
||||
{canCreateBucket && (
|
||||
<Fragment>
|
||||
<Tooltip title={"Create Bucket"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Create Bucket"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
)}
|
||||
<Tooltip title={"Refresh List"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter Buckets"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterBuckets(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={[
|
||||
{
|
||||
type: "view",
|
||||
sendOnlyId: true,
|
||||
onClick: handleViewChange,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: renderBucket,
|
||||
},
|
||||
{
|
||||
label: "Used Space",
|
||||
elementKey: "size",
|
||||
renderFunction: niceBytes,
|
||||
globalClass: classes.usedSpaceCol,
|
||||
rowClass: classes.usedSpaceCol,
|
||||
width: 100,
|
||||
contentTextAlign: "right",
|
||||
headerTextAlign: "right",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Buckets"
|
||||
idField="name"
|
||||
customPaperHeight={classes.browsePaper}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
addRoute,
|
||||
resetRoutesList,
|
||||
displayErrorMessage: setErrorSnackMessage,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default withRouter(connector(withStyles(styles)(BrowseBuckets)));
|
||||
@@ -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 (
|
||||
<React.Fragment key={`breadcrumbs-${index.toString()}`}>
|
||||
<Link
|
||||
to={objectItem.route}
|
||||
onClick={() => {
|
||||
removeRouteLevel(objectItem.route);
|
||||
}}
|
||||
>
|
||||
{objectItem.label}
|
||||
</Link>
|
||||
{index < objectsList.length - 1 && <span> / </span>}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
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 (
|
||||
<React.Fragment key={`breadcrumbs-${index.toString()}`}>
|
||||
<Link to={route}>{label}</Link>
|
||||
{index < splitPaths.length - 1 && <span> / </span>}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const title = false;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{title && (
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.sectionTitle}>
|
||||
{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 && (
|
||||
<small className={classes.smallLabel}>
|
||||
(Rewind:{" "}
|
||||
<Moment date={rewindDate} format="MMMM Do YYYY, h:mm a" /> )
|
||||
@@ -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));
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<PageHeader label={"Object Browser"} />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
{pathIn === "/object-browser" && <BrowseBuckets />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ObjectBrowser);
|
||||
@@ -14,61 +14,10 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,19 +13,10 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user