Added navigation support to object versions (#1626)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
53
portal-ui/src/icons/NewPathIcon.tsx
Normal file
53
portal-ui/src/icons/NewPathIcon.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const NewPathIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M23.4,121.5c-11.5,0-21.4,9.8-21.4,21.2c0.2,11.8,9.7,21.2,21.4,21.4
|
||||
c11.4,0,21.2-9.9,21.2-21.4C44.3,131.1,35,121.7,23.4,121.5"
|
||||
/>
|
||||
<path
|
||||
d="M23.4,175.4c-11.5,0-21.4,9.8-21.4,21.2c0.2,11.8,9.7,21.2,21.4,21.4
|
||||
c11.4,0,21.2-9.9,21.2-21.4C44.3,184.9,35,175.6,23.4,175.4"
|
||||
/>
|
||||
<path
|
||||
d="M158.6,40.2h-12.2c-4.3,0-8.3,2.5-10.2,6.4l-76.6,157c-2.7,5.6-0.4,12.4,5.2,15.2
|
||||
c1.6,0.8,3.3,1.2,5,1.2H82c4.3,0,8.3-2.5,10.2-6.4l76.6-157c2.7-5.6,0.4-12.4-5.2-15.2C162,40.6,160.3,40.2,158.6,40.2"
|
||||
/>
|
||||
<path
|
||||
d="M205,121.1c-1.2,0-2.4,0.1-3.6,0.1L233,56.5c2.7-5.6,0.4-12.4-5.2-15.2
|
||||
c-1.6-0.8-3.3-1.2-5-1.2h-12.2c-4.3,0-8.3,2.5-10.2,6.4l-76.6,157c-2.7,5.6-0.4,12.4,5.2,15.2c1.6,0.8,3.3,1.2,5,1.2h12.2
|
||||
c4.3,0,8.3-2.5,10.2-6.4L165,196c14.8,22.1,44.7,28.1,66.8,13.3s28.1-44.7,13.3-66.8C236.2,129.1,221.1,121.1,205,121.1
|
||||
M205.3,207.3c-21,0-38.1-17-38.1-38.1c0-21,17-38.1,38.1-38.1c21,0,38.1,17,38.1,38.1c0,0,0,0,0,0
|
||||
C243.4,190.3,226.3,207.3,205.3,207.3"
|
||||
/>
|
||||
<path d="M211.3,151.3h-11.9v11.9h-11.9v11.9h11.9v11.9h11.9v-11.9h11.9v-11.9h-11.9V151.3z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default NewPathIcon;
|
||||
39
portal-ui/src/icons/VersionsIcon.tsx
Normal file
39
portal-ui/src/icons/VersionsIcon.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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 * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const VersionsIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 256 256"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
id="Path_7269"
|
||||
d="M147.85,227.97c-2.7,0-4.89-2.19-4.89-4.89l0,0V32.93c0-2.7,2.19-4.89,4.89-4.89c0,0,0,0,0,0
|
||||
h98.98c2.7,0,4.89,2.19,4.89,4.89c0,0,0,0,0,0v190.14c0,2.7-2.19,4.89-4.89,4.89l0,0H147.85z M71.37,205.43
|
||||
c-2.7,0-4.89-2.19-4.89-4.89l0,0V55.48c-0.01-2.7,2.17-4.9,4.87-4.91c0.01,0,0.01,0,0.02,0h56.4c2.7,0,4.89,2.19,4.89,4.89l0,0
|
||||
v145.05c0,2.7-2.19,4.89-4.89,4.89c0,0,0,0,0,0L71.37,205.43z M9.17,182.88c-2.7,0-4.88-2.18-4.89-4.87V78.02
|
||||
c0-2.7,2.19-4.89,4.89-4.89h42.15c2.7,0,4.89,2.19,4.89,4.89V178c0,2.7-2.19,4.89-4.89,4.89l0,0L9.17,182.88z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default VersionsIcon;
|
||||
@@ -167,3 +167,5 @@ export { default as FileVideoIcon } from "./FileVideoIcon";
|
||||
export { default as ChangePasswordIcon } from "./ChangePasswordIcon";
|
||||
export { default as LockIcon } from "./LockIcon";
|
||||
export { default as BackCaretIcon } from "./BackCaretIcon";
|
||||
export { default as VersionsIcon } from "./VersionsIcon";
|
||||
export { default as NewPathIcon } from "./NewPathIcon";
|
||||
|
||||
@@ -19,16 +19,15 @@ import { connect } from "react-redux";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Grid, IconButton, Tooltip } from "@mui/material";
|
||||
import get from "lodash/get";
|
||||
import { AppState } from "../../../../store";
|
||||
import { containerForHeader } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
setFileModeEnabled,
|
||||
setSearchObjects,
|
||||
setVersionsModeEnabled,
|
||||
setSearchVersions,
|
||||
} 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/SettingsIcon";
|
||||
@@ -44,15 +43,18 @@ import SearchBox from "../../Common/SearchBox";
|
||||
import BackLink from "../../../../common/BackLink";
|
||||
|
||||
interface IBrowserHandlerProps {
|
||||
fileMode: boolean;
|
||||
versionsMode: boolean;
|
||||
match: any;
|
||||
history: any;
|
||||
classes: any;
|
||||
setFileModeEnabled: typeof setFileModeEnabled;
|
||||
setVersionsModeEnabled: typeof setVersionsModeEnabled;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
bucketInfo: BucketInfo | null;
|
||||
searchObjects: string;
|
||||
versionedFile: string;
|
||||
searchVersions: string;
|
||||
setSearchObjects: typeof setSearchObjects;
|
||||
setSearchVersions: typeof setSearchVersions;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -71,20 +73,23 @@ const styles = (theme: Theme) =>
|
||||
});
|
||||
|
||||
const BrowserHandler = ({
|
||||
fileMode,
|
||||
versionsMode,
|
||||
match,
|
||||
history,
|
||||
classes,
|
||||
setFileModeEnabled,
|
||||
setVersionsModeEnabled,
|
||||
searchObjects,
|
||||
setSearchObjects,
|
||||
setSearchVersions,
|
||||
versionedFile,
|
||||
searchVersions,
|
||||
}: IBrowserHandlerProps) => {
|
||||
const bucketName = match.params["bucketName"];
|
||||
const internalPaths = get(match.params, "subpaths", "");
|
||||
|
||||
useEffect(() => {
|
||||
setFileModeEnabled(false);
|
||||
}, [internalPaths, setFileModeEnabled]);
|
||||
setVersionsModeEnabled(false);
|
||||
}, [internalPaths, setVersionsModeEnabled]);
|
||||
|
||||
const openBucketConfiguration = () => {
|
||||
history.push(`/buckets/${bucketName}/admin`);
|
||||
@@ -94,24 +99,11 @@ const BrowserHandler = ({
|
||||
<Fragment>
|
||||
<PageHeader
|
||||
label={
|
||||
<Fragment>
|
||||
{fileMode ? (
|
||||
<Fragment>
|
||||
<Link to={"/buckets"} className={classes.breadcrumLink}>
|
||||
Buckets
|
||||
</Link>{" "}
|
||||
> {bucketName}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<BackLink
|
||||
label={"Back to Buckets"}
|
||||
to={"/buckets"}
|
||||
className={classes.backToBuckets}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
<BackLink
|
||||
label={"Back to Buckets"}
|
||||
to={"/buckets"}
|
||||
className={classes.backToBuckets}
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<SecureComponent
|
||||
@@ -134,40 +126,55 @@ const BrowserHandler = ({
|
||||
}
|
||||
middleComponent={
|
||||
<Fragment>
|
||||
{!fileMode && (
|
||||
{!versionsMode ? (
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<SearchBox
|
||||
placeholder={"Start typing to filter objects in bucket"}
|
||||
placeholder={"Start typing to filter objects in the bucket"}
|
||||
onChange={(value) => {
|
||||
setSearchObjects(value);
|
||||
}}
|
||||
value={searchObjects}
|
||||
/>
|
||||
</SecureComponent>
|
||||
) : (
|
||||
<Fragment>
|
||||
<SearchBox
|
||||
placeholder={`Start typing to filter versions of ${versionedFile}`}
|
||||
onChange={(value) => {
|
||||
setSearchVersions(value);
|
||||
}}
|
||||
value={searchVersions}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
<Grid>{fileMode ? <ObjectDetails /> : <ListObjects />}</Grid>
|
||||
<Grid>
|
||||
<ListObjects />
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ objectBrowser, buckets }: AppState) => ({
|
||||
fileMode: get(objectBrowser, "fileMode", false),
|
||||
versionsMode: get(objectBrowser, "versionsMode", false),
|
||||
bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""),
|
||||
bucketInfo: buckets.bucketDetails.bucketInfo,
|
||||
searchObjects: objectBrowser.searchObjects,
|
||||
versionedFile: objectBrowser.versionedFile,
|
||||
searchVersions: objectBrowser.searchVersions,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setFileModeEnabled,
|
||||
setVersionsModeEnabled,
|
||||
setErrorSnackMessage,
|
||||
setSearchObjects,
|
||||
setSearchVersions,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
modalStyleUtils,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { connect } from "react-redux";
|
||||
import { setFileModeEnabled } from "../../../../ObjectBrowser/actions";
|
||||
import history from "../../../../../../history";
|
||||
import { decodeFileName, encodeFileName } from "../../../../../../common/utils";
|
||||
import { setModalErrorSnackMessage } from "../../../../../../actions";
|
||||
@@ -38,7 +37,6 @@ interface ICreateFolder {
|
||||
modalOpen: boolean;
|
||||
bucketName: string;
|
||||
folderName: string;
|
||||
setFileModeEnabled: typeof setFileModeEnabled;
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
onClose: () => any;
|
||||
existingFiles: BucketObject[];
|
||||
@@ -55,7 +53,6 @@ const CreateFolderModal = ({
|
||||
folderName,
|
||||
bucketName,
|
||||
onClose,
|
||||
setFileModeEnabled,
|
||||
setModalErrorSnackMessage,
|
||||
classes,
|
||||
existingFiles,
|
||||
@@ -90,7 +87,6 @@ const CreateFolderModal = ({
|
||||
`${folderPath}${pathUrl}`
|
||||
)}/`;
|
||||
history.push(newPath);
|
||||
setFileModeEnabled(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -162,7 +158,6 @@ const CreateFolderModal = ({
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setFileModeEnabled,
|
||||
setModalErrorSnackMessage,
|
||||
};
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ const styles = (theme: Theme) =>
|
||||
opacity: 0,
|
||||
marginLeft: -1,
|
||||
"&.open": {
|
||||
width: 400,
|
||||
width: 300,
|
||||
minWidth: 300,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
|
||||
@@ -43,7 +43,6 @@ import TableWrapper, {
|
||||
import {
|
||||
decodeFileName,
|
||||
encodeFileName,
|
||||
niceBytes,
|
||||
niceBytesInt,
|
||||
} from "../../../../../../common/utils";
|
||||
|
||||
@@ -51,19 +50,19 @@ import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
objectBrowserCommon,
|
||||
objectBrowserExtras,
|
||||
searchField,
|
||||
tableStyles,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { Badge, Typography } from "@mui/material";
|
||||
import * as reactMoment from "react-moment";
|
||||
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
|
||||
import {
|
||||
completeObject,
|
||||
openList,
|
||||
resetRewind,
|
||||
setFileModeEnabled,
|
||||
setNewObject,
|
||||
setSearchObjects,
|
||||
setVersionsModeEnabled,
|
||||
updateProgress,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import { Route } from "../../../../ObjectBrowser/reducers";
|
||||
@@ -89,7 +88,6 @@ import {
|
||||
} from "../../../../../../common/SecureComponent";
|
||||
|
||||
import withSuspense from "../../../../Common/Components/withSuspense";
|
||||
import { displayName } from "./utils";
|
||||
import {
|
||||
BucketsIcon,
|
||||
DownloadIcon,
|
||||
@@ -101,6 +99,8 @@ import DetailsListPanel from "./DetailsListPanel";
|
||||
import ObjectDetailPanel from "./ObjectDetailPanel";
|
||||
import RBIconButton from "../../../BucketDetails/SummaryItems/RBIconButton";
|
||||
import MultiSelectionPanel from "./MultiSelectionPanel";
|
||||
import { listModeColumns, rewindModeColumns } from "./ListObjectsHelpers";
|
||||
import VersionsNavigator from "../ObjectDetails/VersionsNavigator";
|
||||
|
||||
const HistoryIcon = React.lazy(
|
||||
() => import("../../../../../../icons/HistoryIcon")
|
||||
@@ -169,17 +169,7 @@ const styles = (theme: Theme) =>
|
||||
borderBottom: 0,
|
||||
padding: "0.8rem 15px 0",
|
||||
},
|
||||
titleSpacer: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
listIcon: {
|
||||
display: "block",
|
||||
marginTop: "-10px",
|
||||
"& .min-icon": {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
...objectBrowserExtras,
|
||||
...objectBrowserCommon,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
@@ -216,16 +206,17 @@ interface IListObjectsProps {
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
resetRewind: typeof resetRewind;
|
||||
setFileModeEnabled: typeof setFileModeEnabled;
|
||||
loadingBucket: boolean;
|
||||
setBucketInfo: typeof setBucketInfo;
|
||||
bucketInfo: BucketInfo | null;
|
||||
versionsMode: boolean;
|
||||
setBucketDetailsLoad: typeof setBucketDetailsLoad;
|
||||
setNewObject: typeof setNewObject;
|
||||
updateProgress: typeof updateProgress;
|
||||
completeObject: typeof completeObject;
|
||||
openList: typeof openList;
|
||||
setSearchObjects: typeof setSearchObjects;
|
||||
setVersionsModeEnabled: typeof setVersionsModeEnabled;
|
||||
}
|
||||
|
||||
function useInterval(callback: any, delay: number) {
|
||||
@@ -263,7 +254,6 @@ const ListObjects = ({
|
||||
setSnackBarMessage,
|
||||
setErrorSnackMessage,
|
||||
resetRewind,
|
||||
setFileModeEnabled,
|
||||
setBucketDetailsLoad,
|
||||
loadingBucket,
|
||||
setBucketInfo,
|
||||
@@ -273,7 +263,9 @@ const ListObjects = ({
|
||||
completeObject,
|
||||
setSearchObjects,
|
||||
searchObjects,
|
||||
versionsMode,
|
||||
openList,
|
||||
setVersionsModeEnabled,
|
||||
}: IListObjectsProps) => {
|
||||
const [records, setRecords] = useState<BucketObject[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
@@ -546,14 +538,14 @@ const ListObjects = ({
|
||||
.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);
|
||||
//setFileModeEnabled(true);
|
||||
setLoadingRewind(false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
// It is a folder, we remove loader
|
||||
setLoadingRewind(false);
|
||||
setLoading(false);
|
||||
setFileModeEnabled(false);
|
||||
//setFileModeEnabled(false);
|
||||
}
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
@@ -573,7 +565,7 @@ const ListObjects = ({
|
||||
//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);
|
||||
//setFileModeEnabled(false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
// This code prevents the program from opening a file when a substring of that file is entered as a new folder.
|
||||
@@ -594,9 +586,9 @@ const ListObjects = ({
|
||||
res.objects[0].name.endsWith("/")) ||
|
||||
!found
|
||||
) {
|
||||
setFileModeEnabled(false);
|
||||
//setFileModeEnabled(false);
|
||||
} else {
|
||||
setFileModeEnabled(true);
|
||||
//setFileModeEnabled(true);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@@ -608,7 +600,7 @@ const ListObjects = ({
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setFileModeEnabled(false);
|
||||
//setFileModeEnabled(false);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
@@ -629,7 +621,6 @@ const ListObjects = ({
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
internalPaths,
|
||||
setFileModeEnabled,
|
||||
bucketInfo,
|
||||
displayListObjects,
|
||||
]);
|
||||
@@ -684,24 +675,6 @@ const ListObjects = ({
|
||||
uploadObject(newFiles, "");
|
||||
};
|
||||
|
||||
const displayParsedDate = (object: BucketObject) => {
|
||||
if (object.name.endsWith("/")) {
|
||||
return "";
|
||||
}
|
||||
return <reactMoment.default>{object.last_modified}</reactMoment.default>;
|
||||
};
|
||||
|
||||
const displayNiceBytes = (object: BucketObject) => {
|
||||
if (object.name.endsWith("/")) {
|
||||
return "";
|
||||
}
|
||||
return niceBytes(String(object.size));
|
||||
};
|
||||
|
||||
const displayDeleteFlag = (state: boolean) => {
|
||||
return state ? "Yes" : "No";
|
||||
};
|
||||
|
||||
const downloadObject = (object: BucketObject | RewindObject) => {
|
||||
const identityDownload = encodeFileName(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
@@ -969,15 +942,6 @@ const ListObjects = ({
|
||||
setSelectedPreview(null);
|
||||
};
|
||||
|
||||
const tableActions: ItemActions[] = [
|
||||
{
|
||||
type: "view",
|
||||
label: "View",
|
||||
onClick: openPath,
|
||||
sendOnlyId: true,
|
||||
},
|
||||
];
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (searchObjects === "") {
|
||||
return true;
|
||||
@@ -1027,67 +991,6 @@ const ListObjects = ({
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
const renderName = (element: string) => {
|
||||
return displayName(element);
|
||||
};
|
||||
|
||||
const listModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: renderName,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
enableSort: true,
|
||||
},
|
||||
];
|
||||
|
||||
const rewindModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: renderName,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Object Date",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Deleted",
|
||||
elementKey: "delete_flag",
|
||||
renderFunction: displayDeleteFlag,
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
},
|
||||
];
|
||||
|
||||
const pageTitle = decodeFileName(internalPaths);
|
||||
const currentPath = pageTitle.split("/").filter((i: string) => i !== "");
|
||||
|
||||
@@ -1136,6 +1039,15 @@ const ListObjects = ({
|
||||
uploadPath = uploadPath.concat(currentPath);
|
||||
}
|
||||
|
||||
const tableActions: ItemActions[] = [
|
||||
{
|
||||
type: "view",
|
||||
label: "View",
|
||||
onClick: openPath,
|
||||
sendOnlyId: true,
|
||||
},
|
||||
];
|
||||
|
||||
const multiActionButtons = [
|
||||
{
|
||||
action: downloadSelected,
|
||||
@@ -1345,40 +1257,58 @@ const ListObjects = ({
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
{versionsMode ? (
|
||||
<Fragment>
|
||||
{selectedInternalPaths !== null && (
|
||||
<VersionsNavigator
|
||||
internalPaths={selectedInternalPaths}
|
||||
bucketName={bucketName}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={rewindEnabled ? rewindModeColumns : listModeColumns}
|
||||
isLoading={rewindEnabled ? loadingRewind : loading}
|
||||
loadingMessage={loadingMessage}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={payload}
|
||||
customPaperHeight={`${classes.browsePaper} ${
|
||||
detailsOpen ? "actionsPanelOpen" : ""
|
||||
}`}
|
||||
selectedItems={selectedObjects}
|
||||
onSelect={selectListObjects}
|
||||
customEmptyMessage={`This location is empty${
|
||||
!rewindEnabled ? ", please try uploading a new file" : ""
|
||||
}`}
|
||||
sortConfig={{
|
||||
currentSort: currentSortField,
|
||||
currentDirection: sortDirection,
|
||||
triggerSort: sortChange,
|
||||
}}
|
||||
onSelectAll={selectAllItems}
|
||||
/>
|
||||
</SecureComponent>
|
||||
)}
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={rewindEnabled ? rewindModeColumns : listModeColumns}
|
||||
isLoading={rewindEnabled ? loadingRewind : loading}
|
||||
loadingMessage={loadingMessage}
|
||||
entityName="Objects"
|
||||
idField="name"
|
||||
records={payload}
|
||||
customPaperHeight={`${classes.browsePaper} ${
|
||||
detailsOpen ? "actionsPanelOpen" : ""
|
||||
}`}
|
||||
selectedItems={selectedObjects}
|
||||
onSelect={selectListObjects}
|
||||
customEmptyMessage={`This location is empty${
|
||||
!rewindEnabled ? ", please try uploading a new file" : ""
|
||||
}`}
|
||||
sortConfig={{
|
||||
currentSort: currentSortField,
|
||||
currentDirection: sortDirection,
|
||||
triggerSort: sortChange,
|
||||
}}
|
||||
onSelectAll={selectAllItems}
|
||||
/>
|
||||
<DetailsListPanel
|
||||
open={detailsOpen}
|
||||
closePanel={() => {
|
||||
setDetailsOpen(false);
|
||||
setSelectedInternalPaths(null);
|
||||
setSelectedObjects([]);
|
||||
setVersionsModeEnabled(false);
|
||||
}}
|
||||
>
|
||||
{selectedObjects.length > 0 && (
|
||||
@@ -1408,6 +1338,7 @@ const mapStateToProps = ({ objectBrowser, buckets }: AppState) => ({
|
||||
rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false),
|
||||
rewindDate: get(objectBrowser, "rewind.dateToRewind", null),
|
||||
bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""),
|
||||
versionsMode: get(objectBrowser, "versionsMode", false),
|
||||
loadingBucket: buckets.bucketDetails.loadingBucket,
|
||||
bucketInfo: buckets.bucketDetails.bucketInfo,
|
||||
searchObjects: objectBrowser.searchObjects,
|
||||
@@ -1416,7 +1347,6 @@ const mapStateToProps = ({ objectBrowser, buckets }: AppState) => ({
|
||||
const mapDispatchToProps = {
|
||||
setSnackBarMessage,
|
||||
setErrorSnackMessage,
|
||||
setFileModeEnabled,
|
||||
resetRewind,
|
||||
setBucketDetailsLoad,
|
||||
setBucketInfo,
|
||||
@@ -1425,6 +1355,7 @@ const mapDispatchToProps = {
|
||||
completeObject,
|
||||
openList,
|
||||
setSearchObjects,
|
||||
setVersionsModeEnabled,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 * as reactMoment from "react-moment";
|
||||
import { BucketObject } from "./types";
|
||||
import { niceBytes } from "../../../../../../common/utils";
|
||||
import { displayFileIconName } from "./utils";
|
||||
|
||||
// Functions
|
||||
|
||||
export const displayParsedDate = (object: BucketObject) => {
|
||||
if (object.name.endsWith("/")) {
|
||||
return "";
|
||||
}
|
||||
return <reactMoment.default>{object.last_modified}</reactMoment.default>;
|
||||
};
|
||||
|
||||
export const displayNiceBytes = (object: BucketObject) => {
|
||||
if (object.name.endsWith("/")) {
|
||||
return "";
|
||||
}
|
||||
return niceBytes(String(object.size));
|
||||
};
|
||||
|
||||
export const displayDeleteFlag = (state: boolean) => {
|
||||
return state ? "Yes" : "No";
|
||||
};
|
||||
|
||||
// Table Props
|
||||
|
||||
export const listModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayFileIconName,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
enableSort: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const rewindModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayFileIconName,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Object Date",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
renderFullObject: true,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
elementKey: "size",
|
||||
renderFunction: displayNiceBytes,
|
||||
renderFullObject: true,
|
||||
width: 60,
|
||||
contentTextAlign: "right",
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
label: "Deleted",
|
||||
elementKey: "delete_flag",
|
||||
renderFunction: displayDeleteFlag,
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
},
|
||||
];
|
||||
@@ -44,13 +44,14 @@ import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions
|
||||
import {
|
||||
completeObject,
|
||||
setNewObject,
|
||||
setVersionsModeEnabled,
|
||||
updateProgress,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import {
|
||||
DisabledIcon,
|
||||
NextArrowIcon,
|
||||
PreviewIcon,
|
||||
VersionsIcon,
|
||||
} from "../../../../../../icons";
|
||||
import { ShareIcon, DownloadIcon, DeleteIcon } from "../../../../../../icons";
|
||||
import history from "../../../../../../history";
|
||||
@@ -114,11 +115,14 @@ interface IObjectDetailPanelProps {
|
||||
rewindDate: any;
|
||||
bucketToRewind: string;
|
||||
distributedSetup: boolean;
|
||||
versionsMode: boolean;
|
||||
selectedVersion: string;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setNewObject: typeof setNewObject;
|
||||
updateProgress: typeof updateProgress;
|
||||
completeObject: typeof completeObject;
|
||||
setVersionsModeEnabled: typeof setVersionsModeEnabled;
|
||||
}
|
||||
|
||||
const emptyFile: IFileInfo = {
|
||||
@@ -142,6 +146,9 @@ const ObjectDetailPanel = ({
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
versionsMode,
|
||||
selectedVersion,
|
||||
setVersionsModeEnabled,
|
||||
}: IObjectDetailPanelProps) => {
|
||||
const [loadObjectData, setLoadObjectData] = useState<boolean>(true);
|
||||
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
||||
@@ -151,6 +158,7 @@ const ObjectDetailPanel = ({
|
||||
const [selectedTag, setSelectedTag] = useState<string[]>(["", ""]);
|
||||
const [legalholdOpen, setLegalholdOpen] = useState<boolean>(false);
|
||||
const [actualInfo, setActualInfo] = useState<IFileInfo | null>(null);
|
||||
const [allInfoElements, setAllInfoElements] = useState<IFileInfo[]>([]);
|
||||
const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(null);
|
||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
@@ -175,6 +183,22 @@ const ObjectDetailPanel = ({
|
||||
}
|
||||
}, [internalPaths, bucketName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (distributedSetup && allInfoElements.length >= 1) {
|
||||
let infoElement =
|
||||
allInfoElements.find((el: IFileInfo) => el.is_latest) || emptyFile;
|
||||
|
||||
if (selectedVersion !== "") {
|
||||
infoElement =
|
||||
allInfoElements.find(
|
||||
(el: IFileInfo) => el.version_id === selectedVersion
|
||||
) || emptyFile;
|
||||
}
|
||||
|
||||
setActualInfo(infoElement);
|
||||
}
|
||||
}, [selectedVersion, distributedSetup, allInfoElements]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadObjectData && internalPaths !== "") {
|
||||
api
|
||||
@@ -187,9 +211,7 @@ const ObjectDetailPanel = ({
|
||||
.then((res: IFileInfo[]) => {
|
||||
const result = get(res, "objects", []);
|
||||
if (distributedSetup) {
|
||||
setActualInfo(
|
||||
result.find((el: IFileInfo) => el.is_latest) || emptyFile
|
||||
);
|
||||
setAllInfoElements(result);
|
||||
setVersions(result);
|
||||
const tVersionSize = result.reduce(
|
||||
(acc: number, currValue: IFileInfo) => {
|
||||
@@ -220,6 +242,7 @@ const ObjectDetailPanel = ({
|
||||
internalPaths,
|
||||
setErrorSnackMessage,
|
||||
distributedSetup,
|
||||
selectedVersion,
|
||||
]);
|
||||
|
||||
let tagKeys: string[] = [];
|
||||
@@ -329,18 +352,15 @@ const ObjectDetailPanel = ({
|
||||
setPreviewOpen(false);
|
||||
};
|
||||
|
||||
const openExtraInfo = () => {
|
||||
const newPath = `/buckets/${bucketName}/browse${
|
||||
internalPaths !== "" ? `/${internalPaths}` : ``
|
||||
}`;
|
||||
|
||||
history.push(newPath);
|
||||
};
|
||||
|
||||
if (!actualInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const objectName =
|
||||
objectNameArray.length > 0
|
||||
? objectNameArray[objectNameArray.length - 1]
|
||||
: actualInfo.name;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{shareFileModalOpen && actualInfo && (
|
||||
@@ -428,11 +448,8 @@ const ObjectDetailPanel = ({
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
<div className={classes.titleLabel}>
|
||||
{objectNameArray.length > 0
|
||||
? objectNameArray[objectNameArray.length - 1]
|
||||
: actualInfo.name}
|
||||
</div>
|
||||
|
||||
<div className={classes.titleLabel}>{objectName}</div>
|
||||
|
||||
<ul className={classes.objectActions}>
|
||||
<li>Actions:</li>
|
||||
@@ -479,17 +496,22 @@ const ObjectDetailPanel = ({
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
disabled={actualInfo.is_delete_marker || selectedVersion !== ""}
|
||||
/>
|
||||
</li>
|
||||
</SecureComponent>
|
||||
<li>
|
||||
<ObjectActionButton
|
||||
label={"Expand Details"}
|
||||
icon={<NextArrowIcon />}
|
||||
label={
|
||||
versionsMode ? "Hide Object Versions" : "Display Object Versions"
|
||||
}
|
||||
icon={<VersionsIcon />}
|
||||
onClick={() => {
|
||||
openExtraInfo();
|
||||
setVersionsModeEnabled(!versionsMode, objectName);
|
||||
}}
|
||||
disabled={
|
||||
!(actualInfo.version_id && actualInfo.version_id !== "null")
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -497,107 +519,150 @@ const ObjectDetailPanel = ({
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 className={classes.sectionTitle}>Details</h1>
|
||||
</div>
|
||||
{selectedVersion !== "" && (
|
||||
<Box className={classes.detailContainer}>
|
||||
<strong>Version ID:</strong>
|
||||
<br />
|
||||
{selectedVersion}
|
||||
</Box>
|
||||
)}
|
||||
<Box className={classes.detailContainer}>
|
||||
<LabelValuePair
|
||||
label={"Tags:"}
|
||||
value={
|
||||
<ObjectTags
|
||||
objectInfo={actualInfo}
|
||||
tagKeys={tagKeys}
|
||||
bucketName={bucketName}
|
||||
onDeleteTag={deleteTag}
|
||||
onAddTagClick={() => {
|
||||
setTagModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{selectedVersion === "" ? (
|
||||
<LabelValuePair
|
||||
label={"Tags:"}
|
||||
value={
|
||||
<ObjectTags
|
||||
objectInfo={actualInfo}
|
||||
tagKeys={tagKeys}
|
||||
bucketName={bucketName}
|
||||
onDeleteTag={deleteTag}
|
||||
onAddTagClick={() => {
|
||||
setTagModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Fragment>
|
||||
<strong>Tags: </strong>
|
||||
<br />
|
||||
{tagKeys.length === 0
|
||||
? "N/A"
|
||||
: tagKeys.map((tagKey, index) => {
|
||||
return (
|
||||
<span key={`key-vs-${index.toString()}`}>
|
||||
{tagKey}:{get(actualInfo, `tags.${tagKey}`, "")}
|
||||
{index < tagKeys.length - 1 ? ", " : ""}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
)}
|
||||
</Box>
|
||||
<Box className={classes.detailContainer}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_LEGAL_HOLD]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
actualInfo.version_id && actualInfo.version_id !== "null" ? (
|
||||
<EditablePropertyItem
|
||||
iamScopes={[IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD]}
|
||||
secureCmpProps={{
|
||||
matchAll: false,
|
||||
errorProps: {
|
||||
disabled: true,
|
||||
onClick: null,
|
||||
},
|
||||
}}
|
||||
resourceName={bucketName}
|
||||
property={"Legal Hold:"}
|
||||
value={
|
||||
actualInfo.legal_hold_status
|
||||
? actualInfo.legal_hold_status.toLowerCase()
|
||||
: "Off"
|
||||
}
|
||||
onEdit={() => {
|
||||
setLegalholdOpen(true);
|
||||
}}
|
||||
isLoading={false}
|
||||
/>
|
||||
) : (
|
||||
<LabelValuePair
|
||||
label={"Legal Hold:"}
|
||||
value={
|
||||
<LabelWithIcon
|
||||
icon={<DisabledIcon />}
|
||||
label={
|
||||
<label className={classes.textMuted}>Disabled</label>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{selectedVersion === "" ? (
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
actualInfo.version_id && actualInfo.version_id !== "null" ? (
|
||||
<EditablePropertyItem
|
||||
iamScopes={[IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD]}
|
||||
secureCmpProps={{
|
||||
matchAll: false,
|
||||
errorProps: {
|
||||
disabled: true,
|
||||
onClick: null,
|
||||
},
|
||||
}}
|
||||
resourceName={bucketName}
|
||||
property={"Legal Hold:"}
|
||||
value={
|
||||
actualInfo.legal_hold_status
|
||||
? actualInfo.legal_hold_status.toLowerCase()
|
||||
: "Off"
|
||||
}
|
||||
onEdit={() => {
|
||||
setLegalholdOpen(true);
|
||||
}}
|
||||
isLoading={false}
|
||||
/>
|
||||
) : (
|
||||
<LabelValuePair
|
||||
label={"Legal Hold:"}
|
||||
value={
|
||||
<LabelWithIcon
|
||||
icon={<DisabledIcon />}
|
||||
label={
|
||||
<label className={classes.textMuted}>Disabled</label>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Fragment>
|
||||
<strong>Legal Hold:</strong>
|
||||
<br />
|
||||
{actualInfo.legal_hold_status ? "On" : "Off"}
|
||||
</Fragment>
|
||||
)}
|
||||
</SecureComponent>
|
||||
|
||||
</Box>
|
||||
<Box className={classes.detailContainer}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_RETENTION]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
actualInfo.version_id && actualInfo.version_id !== "null" ? (
|
||||
<EditablePropertyItem
|
||||
iamScopes={[IAM_SCOPES.S3_PUT_OBJECT_RETENTION]}
|
||||
secureCmpProps={{
|
||||
matchAll: false,
|
||||
}}
|
||||
resourceName={bucketName}
|
||||
property={"Retention:"}
|
||||
value={
|
||||
actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "None"
|
||||
}
|
||||
onEdit={openRetentionModal}
|
||||
isLoading={false}
|
||||
/>
|
||||
) : (
|
||||
<LabelValuePair
|
||||
label={"Retention:"}
|
||||
value={
|
||||
<LabelWithIcon
|
||||
icon={<DisabledIcon />}
|
||||
label={
|
||||
<label className={classes.textMuted}>Disabled</label>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{selectedVersion === "" ? (
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
actualInfo.version_id && actualInfo.version_id !== "null" ? (
|
||||
<EditablePropertyItem
|
||||
iamScopes={[IAM_SCOPES.S3_PUT_OBJECT_RETENTION]}
|
||||
secureCmpProps={{
|
||||
matchAll: false,
|
||||
}}
|
||||
resourceName={bucketName}
|
||||
property={"Retention:"}
|
||||
value={
|
||||
actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "None"
|
||||
}
|
||||
onEdit={openRetentionModal}
|
||||
isLoading={false}
|
||||
/>
|
||||
) : (
|
||||
<LabelValuePair
|
||||
label={"Retention:"}
|
||||
value={
|
||||
<LabelWithIcon
|
||||
icon={<DisabledIcon />}
|
||||
label={
|
||||
<label className={classes.textMuted}>Disabled</label>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Fragment>
|
||||
<strong>Object Retention:</strong>
|
||||
<br />
|
||||
{actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "None"}
|
||||
</Fragment>
|
||||
)}
|
||||
</SecureComponent>
|
||||
</Box>
|
||||
<hr className={classes.hrClass} />
|
||||
@@ -616,25 +681,27 @@ const ObjectDetailPanel = ({
|
||||
</Box>
|
||||
<hr className={classes.hrClass} />
|
||||
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<Fragment>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 className={classes.sectionTitle}>Versions</h1>
|
||||
</div>
|
||||
<Box className={classes.detailContainer}>
|
||||
<Box className={classes.metadataLinear}>
|
||||
<strong>Total available versions</strong>
|
||||
<br />
|
||||
{versions.length}
|
||||
{actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" &&
|
||||
selectedVersion === "" && (
|
||||
<Fragment>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 className={classes.sectionTitle}>Versions</h1>
|
||||
</div>
|
||||
<Box className={classes.detailContainer}>
|
||||
<Box className={classes.metadataLinear}>
|
||||
<strong>Total available versions</strong>
|
||||
<br />
|
||||
{versions.length}
|
||||
</Box>
|
||||
<Box className={classes.metadataLinear}>
|
||||
<strong>Versions Stored size:</strong>
|
||||
<br />
|
||||
{niceBytesInt(totalVersionsSize)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={classes.metadataLinear}>
|
||||
<strong>Versions Stored size:</strong>
|
||||
<br />
|
||||
{niceBytesInt(totalVersionsSize)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -644,6 +711,8 @@ const mapStateToProps = ({ objectBrowser, system }: AppState) => ({
|
||||
rewindDate: get(objectBrowser, "rewind.dateToRewind", null),
|
||||
bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""),
|
||||
distributedSetup: get(system, "distributedSetup", false),
|
||||
versionsMode: get(objectBrowser, "versionsMode", false),
|
||||
selectedVersion: get(objectBrowser, "selectedVersion", ""),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
@@ -652,6 +721,7 @@ const mapDispatchToProps = {
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
setVersionsModeEnabled,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -66,7 +66,71 @@ const FileZipIcon = React.lazy(
|
||||
() => import("../../../../../../icons/FileZipIcon")
|
||||
);
|
||||
|
||||
export const displayName = (element: string) => {
|
||||
interface IExtToIcon {
|
||||
icon: any;
|
||||
extensions: string[];
|
||||
}
|
||||
|
||||
export const extensionToIcon: IExtToIcon[] = [
|
||||
{
|
||||
icon: <FileVideoIcon />,
|
||||
extensions: ["mp4", "mov", "avi", "mpeg", "mpg"],
|
||||
},
|
||||
{
|
||||
icon: <FileMusicIcon />,
|
||||
extensions: ["mp3", "m4a", "aac"],
|
||||
},
|
||||
{
|
||||
icon: <FilePdfIcon />,
|
||||
extensions: ["pdf"],
|
||||
},
|
||||
{
|
||||
icon: <FilePptIcon />,
|
||||
extensions: ["ppt", "pptx"],
|
||||
},
|
||||
{
|
||||
icon: <FileXlsIcon />,
|
||||
extensions: ["xls", "xlsx"],
|
||||
},
|
||||
{
|
||||
icon: <FileLockIcon />,
|
||||
extensions: ["cer", "crt", "pem"],
|
||||
},
|
||||
{
|
||||
icon: <FileCodeIcon />,
|
||||
extensions: ["html", "xml", "css", "py", "go", "php", "cpp", "h", "java"],
|
||||
},
|
||||
{
|
||||
icon: <FileConfigIcon />,
|
||||
extensions: ["cfg", "yaml"],
|
||||
},
|
||||
{
|
||||
icon: <FileDbIcon />,
|
||||
extensions: ["sql"],
|
||||
},
|
||||
{
|
||||
icon: <FileFontIcon />,
|
||||
extensions: ["ttf", "otf"],
|
||||
},
|
||||
{
|
||||
icon: <FileTxtIcon />,
|
||||
extensions: ["txt"],
|
||||
},
|
||||
{
|
||||
icon: <FileZipIcon />,
|
||||
extensions: ["zip", "rar", "tar", "gz"],
|
||||
},
|
||||
{
|
||||
icon: <FileBookIcon />,
|
||||
extensions: ["epub", "mobi", "azw", "azw3"],
|
||||
},
|
||||
{
|
||||
icon: <FileImageIcon />,
|
||||
extensions: ["jpeg", "jpg", "gif", "tiff", "png", "heic", "dng"],
|
||||
},
|
||||
];
|
||||
|
||||
export const displayFileIconName = (element: string, returnOnlyIcon: boolean = false) => {
|
||||
let elementString = element;
|
||||
let icon = <ObjectBrowserIcon />;
|
||||
// Element is a folder
|
||||
@@ -75,69 +139,6 @@ export const displayName = (element: string) => {
|
||||
elementString = element.substr(0, element.length - 1);
|
||||
}
|
||||
|
||||
interface IExtToIcon {
|
||||
icon: any;
|
||||
extensions: string[];
|
||||
}
|
||||
|
||||
const extensionToIcon: IExtToIcon[] = [
|
||||
{
|
||||
icon: <FileVideoIcon />,
|
||||
extensions: ["mp4", "mov", "avi", "mpeg", "mpg"],
|
||||
},
|
||||
{
|
||||
icon: <FileMusicIcon />,
|
||||
extensions: ["mp3", "m4a", "aac"],
|
||||
},
|
||||
{
|
||||
icon: <FilePdfIcon />,
|
||||
extensions: ["pdf"],
|
||||
},
|
||||
{
|
||||
icon: <FilePptIcon />,
|
||||
extensions: ["ppt", "pptx"],
|
||||
},
|
||||
{
|
||||
icon: <FileXlsIcon />,
|
||||
extensions: ["xls", "xlsx"],
|
||||
},
|
||||
{
|
||||
icon: <FileLockIcon />,
|
||||
extensions: ["cer", "crt", "pem"],
|
||||
},
|
||||
{
|
||||
icon: <FileCodeIcon />,
|
||||
extensions: ["html", "xml", "css", "py", "go", "php", "cpp", "h", "java"],
|
||||
},
|
||||
{
|
||||
icon: <FileConfigIcon />,
|
||||
extensions: ["cfg", "yaml"],
|
||||
},
|
||||
{
|
||||
icon: <FileDbIcon />,
|
||||
extensions: ["sql"],
|
||||
},
|
||||
{
|
||||
icon: <FileFontIcon />,
|
||||
extensions: ["ttf", "otf"],
|
||||
},
|
||||
{
|
||||
icon: <FileTxtIcon />,
|
||||
extensions: ["txt"],
|
||||
},
|
||||
{
|
||||
icon: <FileZipIcon />,
|
||||
extensions: ["zip", "rar", "tar", "gz"],
|
||||
},
|
||||
{
|
||||
icon: <FileBookIcon />,
|
||||
extensions: ["epub", "mobi", "azw", "azw3"],
|
||||
},
|
||||
{
|
||||
icon: <FileImageIcon />,
|
||||
extensions: ["jpeg", "jpg", "gif", "tiff", "png", "heic", "dng"],
|
||||
},
|
||||
];
|
||||
const lowercaseElement = element.toLowerCase();
|
||||
for (const etc of extensionToIcon) {
|
||||
for (const ext of etc.extensions) {
|
||||
@@ -153,5 +154,9 @@ export const displayName = (element: string) => {
|
||||
|
||||
const splitItem = elementString.split("/");
|
||||
|
||||
if(returnOnlyIcon) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
return <IconWithLabel icon={icon} strings={splitItem} />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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 * as reactMoment from "react-moment";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { withStyles } from "@mui/styles";
|
||||
import { displayFileIconName } from "../ListObjects/utils";
|
||||
import { IFileInfo } from "./types";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import { DownloadIcon, RecoverIcon, ShareIcon } from "../../../../../../icons";
|
||||
|
||||
interface IFileVersionItem {
|
||||
fileName: string;
|
||||
versionInfo: IFileInfo;
|
||||
index: number;
|
||||
onShare: (versionInfo: IFileInfo) => void;
|
||||
onDownload: (versionInfo: IFileInfo) => void;
|
||||
onRestore: (versionInfo: IFileInfo) => void;
|
||||
globalClick: (versionInfo: IFileInfo) => void;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
mainFileVersionItem: {
|
||||
borderBottom: "#E2E2E2 1px solid",
|
||||
padding: "1rem 0",
|
||||
margin: "0 2rem 0 3.5rem",
|
||||
cursor: "pointer",
|
||||
},
|
||||
versionContainer: {
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
color: "#000",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& svg.min-icon": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
marginRight: 10,
|
||||
},
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: "1.5rem",
|
||||
},
|
||||
},
|
||||
versionID: {
|
||||
fontSize: "12px",
|
||||
color: "#000",
|
||||
margin: "2px 0",
|
||||
},
|
||||
versionData: {
|
||||
marginRight: "10px",
|
||||
fontSize: 12,
|
||||
color: "#868686",
|
||||
},
|
||||
ctrItem: {
|
||||
position: "relative",
|
||||
"&::before": {
|
||||
content: "' '",
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
width: "2px",
|
||||
height: "calc(100% + 2px)",
|
||||
backgroundColor: "#F8F8F8",
|
||||
left: "24px",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const FileVersionItem = ({
|
||||
classes,
|
||||
fileName,
|
||||
versionInfo,
|
||||
onShare,
|
||||
onDownload,
|
||||
onRestore,
|
||||
globalClick,
|
||||
index,
|
||||
}: IFileVersionItem) => {
|
||||
const disableButtons = versionInfo.is_delete_marker;
|
||||
|
||||
const versionItemButtons = [
|
||||
{
|
||||
icon: <DownloadIcon />,
|
||||
action: onDownload,
|
||||
tooltip: "Download this version",
|
||||
},
|
||||
{
|
||||
icon: <ShareIcon />,
|
||||
action: onShare,
|
||||
tooltip: "Share this version",
|
||||
},
|
||||
{
|
||||
icon: <RecoverIcon />,
|
||||
action: onRestore,
|
||||
tooltip: "Restore this version",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
flex={1}
|
||||
className={classes.ctrItem}
|
||||
onClick={() => {
|
||||
globalClick(versionInfo);
|
||||
}}
|
||||
>
|
||||
<Grid item xs={12} className={classes.mainFileVersionItem}>
|
||||
<Grid item xs={12} justifyContent={"space-between"}>
|
||||
<Grid container>
|
||||
<Grid item xs={4} className={classes.versionContainer}>
|
||||
{displayFileIconName(fileName, true)} v{index.toString()}
|
||||
</Grid>
|
||||
<Grid item xs={8} className={classes.buttonContainer}>
|
||||
{versionItemButtons.map((button, index) => {
|
||||
return (
|
||||
<Tooltip
|
||||
title={button.tooltip}
|
||||
key={`version-action-${button.tooltip}-${index.toString()}`}
|
||||
>
|
||||
<IconButton
|
||||
size={"small"}
|
||||
id={`version-action-${
|
||||
button.tooltip
|
||||
}-${index.toString()}`}
|
||||
className={`${classes.spacing} ${
|
||||
disableButtons ? classes.buttonDisabled : ""
|
||||
}`}
|
||||
disabled={disableButtons}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!disableButtons) {
|
||||
button.action(versionInfo);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: "#F8F8F8",
|
||||
borderRadius: "100%",
|
||||
width: "28px",
|
||||
height: "28px",
|
||||
padding: "5px",
|
||||
"& .min-icon": {
|
||||
width: "14px",
|
||||
height: "14px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{button.icon}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.versionID}>
|
||||
{versionInfo.version_id}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<span className={classes.versionData}>
|
||||
<strong>Last modified:</strong>{" "}
|
||||
<reactMoment.default>
|
||||
{versionInfo.last_modified}
|
||||
</reactMoment.default>
|
||||
</span>
|
||||
<span className={classes.versionData}>
|
||||
<strong>Deleted:</strong>{" "}
|
||||
{versionInfo.is_delete_marker ? "Yes" : "No"}
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(FileVersionItem);
|
||||
@@ -429,7 +429,7 @@ const ObjectDetails = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
{shareFileModalOpen && actualInfo && (
|
||||
<ShareFile
|
||||
open={shareFileModalOpen}
|
||||
@@ -587,7 +587,7 @@ const ObjectDetails = ({
|
||||
label: "Details",
|
||||
},
|
||||
content: (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 className={classes.sectionTitle}>Details</h1>
|
||||
</div>
|
||||
@@ -717,7 +717,7 @@ const ObjectDetails = ({
|
||||
actualInfo={actualInfo}
|
||||
/>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
),
|
||||
}}
|
||||
{{
|
||||
@@ -797,7 +797,7 @@ const ObjectDetails = ({
|
||||
disabled: extensionPreview(currentItem) === "none",
|
||||
},
|
||||
content: (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
{actualInfo && (
|
||||
<PreviewFileContent
|
||||
bucketName={bucketName}
|
||||
@@ -811,14 +811,14 @@ const ObjectDetails = ({
|
||||
isFullscreen
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
),
|
||||
}}
|
||||
</VerticalTabs>
|
||||
</Fragment>
|
||||
)}
|
||||
</PageLayout>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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, useState } from "react";
|
||||
import get from "lodash/get";
|
||||
import { connect } from "react-redux";
|
||||
import { withStyles } from "@mui/styles";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { LinearProgress, SelectChangeEvent } from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import ShareFile from "./ShareFile";
|
||||
import {
|
||||
actionsTray,
|
||||
buttonsStyles,
|
||||
containerForHeader,
|
||||
hrClass,
|
||||
tableStyles,
|
||||
spacingUtils,
|
||||
textStyleUtils,
|
||||
objectBrowserExtras,
|
||||
objectBrowserCommon,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { IFileInfo } from "./types";
|
||||
import { download } from "../utils";
|
||||
import api from "../../../../../../common/api";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
} from "../../../../../../actions";
|
||||
import { encodeFileName, niceBytesInt } from "../../../../../../common/utils";
|
||||
import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
|
||||
import RestoreFileVersion from "./RestoreFileVersion";
|
||||
import {
|
||||
completeObject,
|
||||
setNewObject,
|
||||
setSelectedVersion,
|
||||
updateProgress,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
|
||||
import { AppState } from "../../../../../../store";
|
||||
import { VersionsIcon } from "../../../../../../icons";
|
||||
import VirtualizedList from "../../../../Common/VirtualizedList/VirtualizedList";
|
||||
import FileVersionItem from "./FileVersionItem";
|
||||
import SelectWrapper from "../../../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
propertiesIcon: {
|
||||
marginLeft: 5,
|
||||
"& .min-icon": {
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
tag: {
|
||||
marginRight: 6,
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
"&.MuiChip-sizeSmall": {
|
||||
height: 18,
|
||||
},
|
||||
"& .min-icon": {
|
||||
height: 10,
|
||||
width: 10,
|
||||
},
|
||||
},
|
||||
search: {
|
||||
marginBottom: 8,
|
||||
"&.MuiFormControl-root": {
|
||||
marginRight: 0,
|
||||
},
|
||||
},
|
||||
capitalizeFirst: {
|
||||
textTransform: "capitalize",
|
||||
"& .min-icon": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
},
|
||||
titleCol: {
|
||||
width: "25%",
|
||||
},
|
||||
titleItem: {
|
||||
width: "35%",
|
||||
},
|
||||
versionsContainer: {
|
||||
border: "#EAEDEE 1px solid",
|
||||
padding: 10,
|
||||
},
|
||||
"@global": {
|
||||
".progressDetails": {
|
||||
paddingTop: 3,
|
||||
display: "inline-block",
|
||||
position: "relative",
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
".progressDetails > .MuiCircularProgress-root": {
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 3,
|
||||
},
|
||||
},
|
||||
tabsContainer: {
|
||||
border: "1px solid #eaeaea",
|
||||
borderTop: 0,
|
||||
},
|
||||
noBottomBorder: {
|
||||
borderBottom: 0,
|
||||
},
|
||||
versionsVirtualPanel: {
|
||||
flexGrow: 1,
|
||||
height: "calc(100% - 120px)",
|
||||
overflow: "auto",
|
||||
},
|
||||
screenTitleContainer: {
|
||||
position: "relative",
|
||||
"&::before": {
|
||||
content: "' '",
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
width: "2px",
|
||||
backgroundColor: "#F8F8F8",
|
||||
left: "24px",
|
||||
height: "52px",
|
||||
bottom: 0,
|
||||
},
|
||||
},
|
||||
sortByLabel: {
|
||||
color: "#838383",
|
||||
fontWeight: "bold",
|
||||
whiteSpace: "nowrap",
|
||||
marginRight: 12,
|
||||
fontSize: 14,
|
||||
},
|
||||
...hrClass,
|
||||
...buttonsStyles,
|
||||
...actionsTray,
|
||||
...tableStyles,
|
||||
...spacingUtils,
|
||||
...textStyleUtils,
|
||||
...objectBrowserCommon,
|
||||
...objectBrowserExtras,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
interface IVersionsNavigatorProps {
|
||||
classes: any;
|
||||
distributedSetup: boolean;
|
||||
internalPaths: string;
|
||||
bucketName: string;
|
||||
searchVersions: string;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setNewObject: typeof setNewObject;
|
||||
updateProgress: typeof updateProgress;
|
||||
completeObject: typeof completeObject;
|
||||
setSelectedVersion: typeof setSelectedVersion;
|
||||
}
|
||||
|
||||
const emptyFile: IFileInfo = {
|
||||
is_latest: true,
|
||||
last_modified: "",
|
||||
legal_hold_status: "",
|
||||
name: "",
|
||||
retention_mode: "",
|
||||
retention_until_date: "",
|
||||
size: "0",
|
||||
tags: {},
|
||||
version_id: null,
|
||||
};
|
||||
|
||||
const VersionsNavigator = ({
|
||||
classes,
|
||||
distributedSetup,
|
||||
setErrorSnackMessage,
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
searchVersions,
|
||||
completeObject,
|
||||
internalPaths,
|
||||
bucketName,
|
||||
setSelectedVersion,
|
||||
}: IVersionsNavigatorProps) => {
|
||||
const [loadObjectData, setLoadObjectData] = useState<boolean>(true);
|
||||
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
||||
const [actualInfo, setActualInfo] = useState<IFileInfo | null>(null);
|
||||
const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(null);
|
||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||
const [restoreVersionOpen, setRestoreVersionOpen] = useState<boolean>(false);
|
||||
const [restoreVersion, setRestoreVersion] = useState<string>("");
|
||||
const [sortValue, setSortValue] = useState<string>("date");
|
||||
|
||||
// calculate object name to display
|
||||
let objectNameArray: string[] = [];
|
||||
if (actualInfo) {
|
||||
objectNameArray = actualInfo.name.split("/");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loadObjectData && internalPaths !== "") {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}${
|
||||
distributedSetup ? "&with_versions=true" : ""
|
||||
}`
|
||||
)
|
||||
.then((res: IFileInfo[]) => {
|
||||
const result = get(res, "objects", []);
|
||||
if (distributedSetup) {
|
||||
setActualInfo(
|
||||
result.find((el: IFileInfo) => el.is_latest) || emptyFile
|
||||
);
|
||||
setVersions(result);
|
||||
} else {
|
||||
setActualInfo(result[0]);
|
||||
setVersions([]);
|
||||
}
|
||||
|
||||
setLoadObjectData(false);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(error);
|
||||
setLoadObjectData(false);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
loadObjectData,
|
||||
bucketName,
|
||||
internalPaths,
|
||||
setErrorSnackMessage,
|
||||
distributedSetup,
|
||||
]);
|
||||
|
||||
const shareObject = () => {
|
||||
setShareFileModalOpen(true);
|
||||
};
|
||||
|
||||
const closeShareModal = () => {
|
||||
setObjectToShare(null);
|
||||
setShareFileModalOpen(false);
|
||||
};
|
||||
|
||||
const downloadObject = (object: IFileInfo) => {
|
||||
const identityDownload = encodeFileName(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
setNewObject({
|
||||
bucketName,
|
||||
done: false,
|
||||
instanceID: identityDownload,
|
||||
percentage: 0,
|
||||
prefix: object.name,
|
||||
type: "download",
|
||||
waitingForFile: true,
|
||||
});
|
||||
|
||||
download(
|
||||
bucketName,
|
||||
internalPaths,
|
||||
object.version_id,
|
||||
parseInt(object.size || "0"),
|
||||
(progress) => {
|
||||
updateProgress(identityDownload, progress);
|
||||
},
|
||||
() => {
|
||||
completeObject(identityDownload);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const onShareItem = (item: IFileInfo) => {
|
||||
setObjectToShare(item);
|
||||
shareObject();
|
||||
};
|
||||
|
||||
const onRestoreItem = (item: IFileInfo) => {
|
||||
setRestoreVersion(item.version_id || "");
|
||||
setRestoreVersionOpen(true);
|
||||
};
|
||||
|
||||
const onDownloadItem = (item: IFileInfo) => {
|
||||
downloadObject(item);
|
||||
};
|
||||
|
||||
const onGlobalClick = (item: IFileInfo) => {
|
||||
setSelectedVersion(item.version_id || "");
|
||||
};
|
||||
|
||||
const filteredRecords = versions.filter((version) => {
|
||||
if (version.version_id) {
|
||||
return version.version_id.includes(searchVersions);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const closeRestoreModal = (reloadObjectData: boolean) => {
|
||||
setRestoreVersionOpen(false);
|
||||
setRestoreVersion("");
|
||||
|
||||
if (reloadObjectData) {
|
||||
setLoadObjectData(true);
|
||||
}
|
||||
};
|
||||
|
||||
const totalSpace = versions.reduce((acc: number, currValue: IFileInfo) => {
|
||||
if (currValue.size) {
|
||||
return acc + parseInt(currValue.size);
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
filteredRecords.sort((a, b) => {
|
||||
switch (sortValue) {
|
||||
case "version":
|
||||
if (a.version_id && b.version_id) {
|
||||
if (a.version_id < b.version_id) {
|
||||
return -1;
|
||||
}
|
||||
if (a.version_id > b.version_id) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
case "deleted":
|
||||
if (a.is_delete_marker && !b.is_delete_marker) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.is_delete_marker && b.is_delete_marker) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
const dateA = new Date(a.last_modified).getTime();
|
||||
const dateB = new Date(b.last_modified).getTime();
|
||||
|
||||
if (dateA < dateB) {
|
||||
return 1;
|
||||
}
|
||||
if (dateA > dateB) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
const renderVersion = (elementIndex: number) => {
|
||||
const item = filteredRecords[elementIndex];
|
||||
const versOrd = versions.length - versions.indexOf(item);
|
||||
|
||||
return (
|
||||
<FileVersionItem
|
||||
fileName={actualInfo?.name || ""}
|
||||
versionInfo={item}
|
||||
index={versOrd}
|
||||
onDownload={onDownloadItem}
|
||||
onRestore={onRestoreItem}
|
||||
onShare={onShareItem}
|
||||
globalClick={onGlobalClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{shareFileModalOpen && actualInfo && (
|
||||
<ShareFile
|
||||
open={shareFileModalOpen}
|
||||
closeModalAndRefresh={closeShareModal}
|
||||
bucketName={bucketName}
|
||||
dataObject={objectToShare || actualInfo}
|
||||
/>
|
||||
)}
|
||||
{restoreVersionOpen && actualInfo && (
|
||||
<RestoreFileVersion
|
||||
restoreOpen={restoreVersionOpen}
|
||||
bucketName={bucketName}
|
||||
versionID={restoreVersion}
|
||||
objectPath={actualInfo.name}
|
||||
onCloseAndUpdate={closeRestoreModal}
|
||||
/>
|
||||
)}
|
||||
<Grid container className={classes.versionsContainer}>
|
||||
{!actualInfo && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{actualInfo && (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.screenTitleContainer}>
|
||||
<ScreenTitle
|
||||
icon={
|
||||
<span className={classes.listIcon}>
|
||||
<VersionsIcon />
|
||||
</span>
|
||||
}
|
||||
title={
|
||||
<span className={classes.titleSpacer}>
|
||||
{objectNameArray.length > 0
|
||||
? objectNameArray[objectNameArray.length - 1]
|
||||
: actualInfo.name}{" "}
|
||||
Versions
|
||||
</span>
|
||||
}
|
||||
subTitle={
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.bucketDetails}>
|
||||
<span className={classes.detailsSpacer}>
|
||||
<strong>
|
||||
{versions.length} Version
|
||||
{versions.length === 1 ? "" : "s"}
|
||||
</strong>
|
||||
</span>
|
||||
<span className={classes.detailsSpacer}>
|
||||
<strong>{niceBytesInt(totalSpace)}</strong>
|
||||
</span>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
}
|
||||
actions={
|
||||
<Fragment>
|
||||
<span className={classes.sortByLabel}>Sort by</span>
|
||||
<SelectWrapper
|
||||
id={"sort-by"}
|
||||
label={""}
|
||||
value={sortValue}
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setSortValue(e.target.value as string);
|
||||
}}
|
||||
name={"sort-by"}
|
||||
options={[
|
||||
{ label: "Date", value: "date" },
|
||||
{
|
||||
label: "Version ID",
|
||||
value: "version",
|
||||
},
|
||||
{ label: "Deleted", value: "deleted" },
|
||||
]}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
className={classes.noBottomBorder}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.versionsVirtualPanel}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<VirtualizedList
|
||||
rowRenderFunction={renderVersion}
|
||||
totalItems={filteredRecords.length}
|
||||
defaultHeight={110}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ system, objectBrowser }: AppState) => ({
|
||||
distributedSetup: get(system, "distributedSetup", false),
|
||||
searchVersions: objectBrowser.searchVersions,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
setSelectedVersion,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default connector(withStyles(styles)(VersionsNavigator));
|
||||
@@ -175,7 +175,12 @@ const FormSwitchWrapper = ({
|
||||
<Grid container alignItems={"center"}>
|
||||
<Grid item xs>
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={10} md={9}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={description !== "" ? 4 : 10}
|
||||
md={description !== "" ? 3 : 9}
|
||||
>
|
||||
{label !== "" && (
|
||||
<InputLabel htmlFor={id} className={classes.inputLabel}>
|
||||
<span>{label}</span>
|
||||
|
||||
@@ -379,8 +379,8 @@ export const objectBrowserCommon = {
|
||||
},
|
||||
},
|
||||
"& .min-icon": {
|
||||
width: 14,
|
||||
minWidth: 14,
|
||||
width: 16,
|
||||
minWidth: 16,
|
||||
},
|
||||
},
|
||||
smallLabel: {
|
||||
@@ -404,7 +404,6 @@ export const objectBrowserCommon = {
|
||||
textAlign: "left" as const,
|
||||
marginLeft: 15,
|
||||
marginRight: 10,
|
||||
lineHeight: 35,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1360,6 +1359,20 @@ export const detailsPanel: any = {
|
||||
},
|
||||
};
|
||||
|
||||
export const objectBrowserExtras = {
|
||||
listIcon: {
|
||||
display: "block",
|
||||
marginTop: "-10px",
|
||||
"& .min-icon": {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
titleSpacer: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
}
|
||||
|
||||
// These classes are meant to be used as React.CSSProperties for TableWrapper
|
||||
export const TableRowPredefStyles: any = {
|
||||
deleted: {
|
||||
|
||||
@@ -569,6 +569,11 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
|
||||
NewAccountIcon
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.NewPathIcon /> <br />
|
||||
NewPathIcon
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.NewPoolIcon /> <br />
|
||||
NewPoolIcon
|
||||
@@ -834,6 +839,11 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
|
||||
VersionIcon
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.VersionsIcon /> <br />
|
||||
VersionsIcon
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3} sm={2} md={1}>
|
||||
<cicons.WarnIcon /> <br />
|
||||
WarnIcon
|
||||
|
||||
@@ -25,13 +25,14 @@ import { ObjectBrowserState } from "./reducers";
|
||||
import { objectBrowserCommon } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Link } from "react-router-dom";
|
||||
import { encodeFileName } from "../../../common/utils";
|
||||
import { BackCaretIcon, FolderIcon } from "../../../icons";
|
||||
import { BackCaretIcon, NewPathIcon } from "../../../icons";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import history from "../../../history";
|
||||
import { hasPermission } from "../../../common/SecureComponent";
|
||||
import { IAM_SCOPES } from "../../../common/SecureComponent/permissions";
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import { BucketObject } from "../Buckets/ListBuckets/Objects/ListObjects/types";
|
||||
import { setVersionsModeEnabled } from "./actions";
|
||||
|
||||
const CreateFolderModal = withSuspense(
|
||||
React.lazy(
|
||||
@@ -48,8 +49,10 @@ interface IObjectBrowser {
|
||||
bucketName: string;
|
||||
internalPaths: string;
|
||||
rewindEnabled?: boolean;
|
||||
rewindDate?: any;
|
||||
versionsMode: boolean;
|
||||
versionedFile: string;
|
||||
existingFiles: BucketObject[];
|
||||
setVersionsModeEnabled: typeof setVersionsModeEnabled;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -62,8 +65,10 @@ const BrowserBreadcrumbs = ({
|
||||
bucketName,
|
||||
internalPaths,
|
||||
rewindEnabled,
|
||||
rewindDate,
|
||||
existingFiles,
|
||||
versionsMode,
|
||||
versionedFile,
|
||||
setVersionsModeEnabled,
|
||||
}: IObjectBrowser) => {
|
||||
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
|
||||
|
||||
@@ -82,22 +87,55 @@ const BrowserBreadcrumbs = ({
|
||||
return (
|
||||
<Fragment key={`breadcrumbs-${index.toString()}`}>
|
||||
<span> / </span>
|
||||
<Link to={route}>{objectItem}</Link>
|
||||
<Link
|
||||
to={route}
|
||||
onClick={() => {
|
||||
setVersionsModeEnabled(false);
|
||||
}}
|
||||
>
|
||||
{objectItem}
|
||||
</Link>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
let versionsItem: any[] = [];
|
||||
|
||||
if (versionsMode) {
|
||||
versionsItem = [
|
||||
<Fragment key={`breadcrumbs-versionedItem`}>
|
||||
<span> / {versionedFile} - Versions</span>
|
||||
</Fragment>,
|
||||
];
|
||||
}
|
||||
|
||||
const listBreadcrumbs: any[] = [
|
||||
<Fragment key={`breadcrumbs-root-path`}>
|
||||
<Link to={`/buckets/${bucketName}/browse`}>{bucketName}</Link>
|
||||
<Link
|
||||
to={`/buckets/${bucketName}/browse`}
|
||||
onClick={() => {
|
||||
setVersionsModeEnabled(false);
|
||||
}}
|
||||
>
|
||||
{bucketName}
|
||||
</Link>
|
||||
</Fragment>,
|
||||
...breadcrumbsMap,
|
||||
...versionsItem,
|
||||
];
|
||||
|
||||
const closeAddFolderModal = () => {
|
||||
setCreateFolderOpen(false);
|
||||
};
|
||||
|
||||
const goBackFunction = () => {
|
||||
if (versionsMode) {
|
||||
setVersionsModeEnabled(false);
|
||||
} else {
|
||||
history.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{createFolderOpen && (
|
||||
@@ -111,14 +149,11 @@ const BrowserBreadcrumbs = ({
|
||||
)}
|
||||
<Grid item xs={12} className={`${classes.breadcrumbs}`}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
history.goBack();
|
||||
}}
|
||||
onClick={goBackFunction}
|
||||
sx={{
|
||||
border: "#EAEDEE 1px solid",
|
||||
backgroundColor: "#fff",
|
||||
borderLeft: 0,
|
||||
borderBottom: 0,
|
||||
borderRadius: 0,
|
||||
width: 39,
|
||||
height: 39,
|
||||
@@ -145,7 +180,7 @@ const BrowserBreadcrumbs = ({
|
||||
paddingLeft: "6px",
|
||||
}}
|
||||
>
|
||||
<FolderIcon />
|
||||
<NewPathIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className={classes.breadcrumbsList} dir="rtl">
|
||||
@@ -158,9 +193,14 @@ const BrowserBreadcrumbs = ({
|
||||
|
||||
const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({
|
||||
rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false),
|
||||
rewindDate: get(objectBrowser, "rewind.dateToRewind", null),
|
||||
versionsMode: get(objectBrowser, "versionsMode", false),
|
||||
versionedFile: get(objectBrowser, "versionedFile", ""),
|
||||
});
|
||||
|
||||
const connector = connect(mapStateToProps, null);
|
||||
const mapDispatchToProps = {
|
||||
setVersionsModeEnabled,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(BrowserBreadcrumbs));
|
||||
|
||||
@@ -18,7 +18,6 @@ import { IFileItem } from "./reducers";
|
||||
|
||||
export const REWIND_SET_ENABLE = "REWIND/SET_ENABLE";
|
||||
export const REWIND_RESET_REWIND = "REWIND/RESET_REWIND";
|
||||
export const REWIND_FILE_MODE_ENABLED = "BUCKET_BROWSER/FILE_MODE_ENABLED";
|
||||
|
||||
export const OBJECT_MANAGER_NEW_OBJECT = "OBJECT_MANAGER/NEW_OBJECT";
|
||||
export const OBJECT_MANAGER_UPDATE_PROGRESS_OBJECT =
|
||||
@@ -33,6 +32,13 @@ export const OBJECT_MANAGER_CLOSE_LIST = "OBJECT_MANAGER/CLOSE_LIST";
|
||||
export const OBJECT_MANAGER_SET_SEARCH_OBJECT =
|
||||
"OBJECT_MANAGER/SET_SEARCH_OBJECT";
|
||||
|
||||
export const BUCKET_BROWSER_VERSIONS_MODE_ENABLED =
|
||||
"BUCKET_BROWSER/VERSIONS_MODE_ENABLED";
|
||||
export const BUCKET_BROWSER_VERSIONS_SET_SEARCH =
|
||||
"BUCKET_BROWSER/VERSIONS_SET_SEARCH";
|
||||
export const BUCKET_BROWSER_SET_SELECTED_VERSION =
|
||||
"BUCKET_BROWSER/SET_SELECTED_VERSION";
|
||||
|
||||
interface RewindSetEnabled {
|
||||
type: typeof REWIND_SET_ENABLE;
|
||||
bucket: string;
|
||||
@@ -44,9 +50,10 @@ interface RewindReset {
|
||||
type: typeof REWIND_RESET_REWIND;
|
||||
}
|
||||
|
||||
interface FileModeEnabled {
|
||||
type: typeof REWIND_FILE_MODE_ENABLED;
|
||||
interface VersionsModeEnabled {
|
||||
type: typeof BUCKET_BROWSER_VERSIONS_MODE_ENABLED;
|
||||
status: boolean;
|
||||
objectName: string;
|
||||
}
|
||||
|
||||
interface OMNewObject {
|
||||
@@ -77,9 +84,11 @@ interface OMCleanList {
|
||||
interface OMToggleList {
|
||||
type: typeof OBJECT_MANAGER_TOGGLE_LIST;
|
||||
}
|
||||
|
||||
interface OMOpenList {
|
||||
type: typeof OBJECT_MANAGER_OPEN_LIST;
|
||||
}
|
||||
|
||||
interface OMCloseList {
|
||||
type: typeof OBJECT_MANAGER_CLOSE_LIST;
|
||||
}
|
||||
@@ -89,10 +98,20 @@ interface SetSearchObjects {
|
||||
searchString: string;
|
||||
}
|
||||
|
||||
interface SetSearchVersions {
|
||||
type: typeof BUCKET_BROWSER_VERSIONS_SET_SEARCH;
|
||||
searchString: string;
|
||||
}
|
||||
|
||||
interface SetSelectedversion {
|
||||
type: typeof BUCKET_BROWSER_SET_SELECTED_VERSION;
|
||||
selectedVersion: string;
|
||||
}
|
||||
|
||||
export type ObjectBrowserActionTypes =
|
||||
| RewindSetEnabled
|
||||
| RewindReset
|
||||
| FileModeEnabled
|
||||
| VersionsModeEnabled
|
||||
| OMNewObject
|
||||
| OMUpdateProgress
|
||||
| OMCompleteObject
|
||||
@@ -101,7 +120,9 @@ export type ObjectBrowserActionTypes =
|
||||
| OMToggleList
|
||||
| OMOpenList
|
||||
| OMCloseList
|
||||
| SetSearchObjects;
|
||||
| SetSearchObjects
|
||||
| SetSearchVersions
|
||||
| SetSelectedversion;
|
||||
|
||||
export const setRewindEnable = (
|
||||
state: boolean,
|
||||
@@ -122,10 +143,14 @@ export const resetRewind = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const setFileModeEnabled = (status: boolean) => {
|
||||
export const setVersionsModeEnabled = (
|
||||
status: boolean,
|
||||
objectName: string = ""
|
||||
) => {
|
||||
return {
|
||||
type: REWIND_FILE_MODE_ENABLED,
|
||||
type: BUCKET_BROWSER_VERSIONS_MODE_ENABLED,
|
||||
status,
|
||||
objectName,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -188,3 +213,17 @@ export const setSearchObjects = (searchString: string) => {
|
||||
searchString,
|
||||
};
|
||||
};
|
||||
|
||||
export const setSearchVersions = (searchString: string) => {
|
||||
return {
|
||||
type: BUCKET_BROWSER_VERSIONS_SET_SEARCH,
|
||||
searchString,
|
||||
};
|
||||
};
|
||||
|
||||
export const setSelectedVersion = (selectedVersion: string) => {
|
||||
return {
|
||||
type: BUCKET_BROWSER_SET_SELECTED_VERSION,
|
||||
selectedVersion,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import {
|
||||
REWIND_SET_ENABLE,
|
||||
REWIND_RESET_REWIND,
|
||||
REWIND_FILE_MODE_ENABLED,
|
||||
BUCKET_BROWSER_VERSIONS_MODE_ENABLED,
|
||||
ObjectBrowserActionTypes,
|
||||
OBJECT_MANAGER_NEW_OBJECT,
|
||||
OBJECT_MANAGER_UPDATE_PROGRESS_OBJECT,
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
OBJECT_MANAGER_CLOSE_LIST,
|
||||
OBJECT_MANAGER_OPEN_LIST,
|
||||
OBJECT_MANAGER_SET_SEARCH_OBJECT,
|
||||
BUCKET_BROWSER_VERSIONS_SET_SEARCH,
|
||||
BUCKET_BROWSER_SET_SELECTED_VERSION,
|
||||
} from "./actions";
|
||||
|
||||
export interface Route {
|
||||
@@ -42,10 +44,13 @@ export interface RewindItem {
|
||||
}
|
||||
|
||||
export interface ObjectBrowserState {
|
||||
fileMode: boolean;
|
||||
rewind: RewindItem;
|
||||
objectManager: ObjectManager;
|
||||
searchObjects: string;
|
||||
versionsMode: boolean;
|
||||
versionedFile: string;
|
||||
searchVersions: string;
|
||||
selectedVersion: string;
|
||||
}
|
||||
|
||||
export interface ObjectBrowserReducer {
|
||||
@@ -74,7 +79,7 @@ const defaultRewind = {
|
||||
};
|
||||
|
||||
const initialState: ObjectBrowserState = {
|
||||
fileMode: false,
|
||||
versionsMode: false,
|
||||
rewind: {
|
||||
...defaultRewind,
|
||||
},
|
||||
@@ -83,6 +88,9 @@ const initialState: ObjectBrowserState = {
|
||||
managerOpen: false,
|
||||
},
|
||||
searchObjects: "",
|
||||
versionedFile: "",
|
||||
searchVersions: "",
|
||||
selectedVersion: "",
|
||||
};
|
||||
|
||||
export function objectBrowserReducer(
|
||||
@@ -105,8 +113,15 @@ export function objectBrowserReducer(
|
||||
dateToRewind: null,
|
||||
};
|
||||
return { ...state, rewind: resetItem };
|
||||
case REWIND_FILE_MODE_ENABLED:
|
||||
return { ...state, fileMode: action.status };
|
||||
case BUCKET_BROWSER_VERSIONS_MODE_ENABLED:
|
||||
const objectN = !action.status ? "" : action.objectName;
|
||||
|
||||
return {
|
||||
...state,
|
||||
versionsMode: action.status,
|
||||
versionedFile: objectN,
|
||||
selectedVersion: "",
|
||||
};
|
||||
case OBJECT_MANAGER_NEW_OBJECT:
|
||||
const cloneObjects = [
|
||||
action.newObject,
|
||||
@@ -220,6 +235,16 @@ export function objectBrowserReducer(
|
||||
...state,
|
||||
searchObjects: action.searchString,
|
||||
};
|
||||
case BUCKET_BROWSER_VERSIONS_SET_SEARCH:
|
||||
return {
|
||||
...state,
|
||||
searchVersions: action.searchString,
|
||||
};
|
||||
case BUCKET_BROWSER_SET_SELECTED_VERSION:
|
||||
return {
|
||||
...state,
|
||||
selectedVersion: action.selectedVersion,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user