Added Object Manager feature for Uploads & downloads (#1265)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
47
portal-ui/src/icons/ObjectManagerIcon.tsx
Normal file
47
portal-ui/src/icons/ObjectManagerIcon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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 ObjectManagerIcon = (props: SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
>
|
||||
<g id="Layer 1">
|
||||
<path
|
||||
d="M217.452+193.452L217.452+224.458L38.4601+224.458L38.4601+193.452L0.104767+193.452L0.104767+255.464L255.807+255.464L255.807+193.452L217.452+193.452Z"
|
||||
opacity="1"
|
||||
/>
|
||||
<path
|
||||
d="M70.1156+194.746L98.6658+194.746L98.6658+97.0605L120.994+97.0605L84.3907+51.995L47.7878+97.0605L70.1156+97.0605L70.1156+194.746Z"
|
||||
opacity="1"
|
||||
/>
|
||||
<path
|
||||
d="M183.757+52.6023L155.207+52.6922L155.515+150.377L133.187+150.448L169.932+195.398L206.392+150.217L184.065+150.288L183.757+52.6023Z"
|
||||
opacity="1"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObjectManagerIcon;
|
||||
@@ -77,6 +77,7 @@ export { default as CircleIcon } from "./CircleIcon";
|
||||
export { default as PreviewIcon } from "./PreviewIcon";
|
||||
export { default as LockIcon } from "./LockIcon";
|
||||
export { default as VersionIcon } from "./VersionIcon";
|
||||
export { default as ObjectManagerIcon } from "./ObjectManagerIcon";
|
||||
|
||||
export { default as FileLockIcon } from "./FileLockIcon";
|
||||
export { default as FileXlsIcon } from "./FileXlsIcon";
|
||||
|
||||
@@ -64,8 +64,6 @@ import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
|
||||
import AddFolderIcon from "../../../../../../icons/AddFolderIcon";
|
||||
import HistoryIcon from "../../../../../../icons/HistoryIcon";
|
||||
import ObjectBrowserIcon from "../../../../../../icons/ObjectBrowserIcon";
|
||||
import ObjectBrowserFolderIcon from "../../../../../../icons/ObjectBrowserFolderIcon";
|
||||
import FolderIcon from "../../../../../../icons/FolderIcon";
|
||||
import RefreshIcon from "../../../../../../icons/RefreshIcon";
|
||||
import UploadIcon from "../../../../../../icons/UploadIcon";
|
||||
@@ -73,24 +71,7 @@ import { setBucketDetailsLoad, setBucketInfo } from "../../../actions";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import PageLayout from "../../../../Common/Layout/PageLayout";
|
||||
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
|
||||
import {
|
||||
DeleteIcon,
|
||||
FileBookIcon,
|
||||
FileCodeIcon,
|
||||
FileConfigIcon,
|
||||
FileDbIcon,
|
||||
FileFontIcon,
|
||||
FileImageIcon,
|
||||
FileLockIcon,
|
||||
FileMissingIcon,
|
||||
FileMusicIcon,
|
||||
FilePdfIcon,
|
||||
FilePptIcon,
|
||||
FileTxtIcon,
|
||||
FileVideoIcon,
|
||||
FileXlsIcon,
|
||||
FileZipIcon,
|
||||
} from "../../../../../../icons";
|
||||
import { DeleteIcon } from "../../../../../../icons";
|
||||
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
|
||||
import SecureComponent, {
|
||||
hasPermission,
|
||||
@@ -98,6 +79,12 @@ import SecureComponent, {
|
||||
import SearchBox from "../../../../Common/SearchBox";
|
||||
|
||||
import withSuspense from "../../../../Common/Components/withSuspense";
|
||||
import {
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import { displayName } from "./utils";
|
||||
|
||||
const CreateFolderModal = withSuspense(
|
||||
React.lazy(() => import("./CreateFolderModal"))
|
||||
@@ -233,6 +220,9 @@ interface IListObjectsProps {
|
||||
setBucketInfo: typeof setBucketInfo;
|
||||
bucketInfo: BucketInfo | null;
|
||||
setBucketDetailsLoad: typeof setBucketDetailsLoad;
|
||||
setNewObject: typeof setNewObject;
|
||||
updateProgress: typeof updateProgress;
|
||||
completeObject: typeof completeObject;
|
||||
}
|
||||
|
||||
function useInterval(callback: any, delay: number) {
|
||||
@@ -277,6 +267,9 @@ const ListObjects = ({
|
||||
loadingBucket,
|
||||
setBucketInfo,
|
||||
bucketInfo,
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
}: IListObjectsProps) => {
|
||||
const [records, setRecords] = useState<BucketObject[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
@@ -616,62 +609,86 @@ const ListObjects = ({
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
let files = e.target.files;
|
||||
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
|
||||
if (encodedPath !== "") {
|
||||
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
|
||||
}
|
||||
let xhr = new XMLHttpRequest();
|
||||
const areMultipleFiles = files.length > 1;
|
||||
const errorMessage = `An error occurred while uploading the file${
|
||||
areMultipleFiles ? "s" : ""
|
||||
}.`;
|
||||
const okMessage = `Object${
|
||||
areMultipleFiles ? "s" : ``
|
||||
} uploaded successfully.`;
|
||||
|
||||
xhr.open("POST", uploadUrl, true);
|
||||
if (files.length > 0) {
|
||||
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
|
||||
|
||||
xhr.withCredentials = false;
|
||||
xhr.onload = function (event) {
|
||||
if (
|
||||
xhr.status === 401 ||
|
||||
xhr.status === 403 ||
|
||||
xhr.status === 400 ||
|
||||
xhr.status === 500
|
||||
) {
|
||||
setSnackBarMessage(errorMessage);
|
||||
if (encodedPath !== "") {
|
||||
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
|
||||
}
|
||||
if (xhr.status === 200) {
|
||||
setSnackBarMessage(okMessage);
|
||||
|
||||
for (let file of files) {
|
||||
const fileName = file.name;
|
||||
const blobFile = new Blob([file], { type: file.type });
|
||||
|
||||
const identity = btoa(
|
||||
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
setNewObject({
|
||||
bucketName,
|
||||
done: false,
|
||||
instanceID: identity,
|
||||
percentage: 0,
|
||||
prefix: `${decodeFileName(encodedPath)}${fileName}`,
|
||||
type: "upload",
|
||||
waitingForFile: false,
|
||||
});
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
const areMultipleFiles = files.length > 1;
|
||||
const errorMessage = `An error occurred while uploading the file${
|
||||
areMultipleFiles ? "s" : ""
|
||||
}.`;
|
||||
const okMessage = `Object${
|
||||
areMultipleFiles ? "s" : ``
|
||||
} uploaded successfully.`;
|
||||
|
||||
xhr.open("POST", uploadUrl, true);
|
||||
|
||||
xhr.withCredentials = false;
|
||||
xhr.onload = function (event) {
|
||||
if (
|
||||
xhr.status === 401 ||
|
||||
xhr.status === 403 ||
|
||||
xhr.status === 400 ||
|
||||
xhr.status === 500
|
||||
) {
|
||||
setSnackBarMessage(errorMessage);
|
||||
}
|
||||
if (xhr.status === 200) {
|
||||
completeObject(identity);
|
||||
setSnackBarMessage(okMessage);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.upload.addEventListener("error", (event) => {
|
||||
setSnackBarMessage(errorMessage);
|
||||
});
|
||||
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
const progress = Math.floor((event.loaded * 100) / event.total);
|
||||
|
||||
updateProgress(identity, progress);
|
||||
});
|
||||
|
||||
xhr.onerror = () => {
|
||||
setSnackBarMessage(errorMessage);
|
||||
};
|
||||
xhr.onloadend = () => {
|
||||
setLoading(true);
|
||||
setLoadingProgress(100);
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append(file.size, blobFile, fileName);
|
||||
|
||||
xhr.send(formData);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.upload.addEventListener("error", (event) => {
|
||||
setSnackBarMessage(errorMessage);
|
||||
});
|
||||
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
setLoadingProgress(Math.floor((event.loaded * 100) / event.total));
|
||||
});
|
||||
|
||||
xhr.onerror = () => {
|
||||
setSnackBarMessage(errorMessage);
|
||||
};
|
||||
xhr.onloadend = () => {
|
||||
setLoading(true);
|
||||
setLoadingProgress(100);
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
for (let file of files) {
|
||||
const fileName = file.name;
|
||||
const blobFile = new Blob([file], { type: file.type });
|
||||
formData.append(file.size, blobFile, fileName);
|
||||
}
|
||||
|
||||
xhr.send(formData);
|
||||
e.target.value = null;
|
||||
};
|
||||
|
||||
@@ -699,14 +716,32 @@ const ListObjects = ({
|
||||
};
|
||||
|
||||
const downloadObject = (object: BucketObject) => {
|
||||
if (object.size > 104857600) {
|
||||
// If file is bigger than 100MB we show a notification
|
||||
setSnackBarMessage(
|
||||
"Download process started, it may take a few moments to complete"
|
||||
);
|
||||
}
|
||||
const identityDownload = btoa(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
download(bucketName, encodeFileName(object.name), object.version_id);
|
||||
setNewObject({
|
||||
bucketName,
|
||||
done: false,
|
||||
instanceID: identityDownload,
|
||||
percentage: 0,
|
||||
prefix: object.name,
|
||||
type: "download",
|
||||
waitingForFile: true,
|
||||
});
|
||||
|
||||
download(
|
||||
bucketName,
|
||||
encodeFileName(object.name),
|
||||
object.version_id,
|
||||
object.size,
|
||||
(progress) => {
|
||||
updateProgress(identityDownload, progress);
|
||||
},
|
||||
() => {
|
||||
completeObject(identityDownload);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const openPath = (idElement: string) => {
|
||||
@@ -783,113 +818,6 @@ const ListObjects = ({
|
||||
});
|
||||
}
|
||||
|
||||
const displayName = (element: string) => {
|
||||
let elementString = element;
|
||||
let icon = <ObjectBrowserIcon />;
|
||||
// Element is a folder
|
||||
if (element.endsWith("/")) {
|
||||
icon = <ObjectBrowserFolderIcon />;
|
||||
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) {
|
||||
if (lowercaseElement.endsWith(`.${ext}`)) {
|
||||
icon = etc.icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!element.endsWith("/") && element.indexOf(".") < 0) {
|
||||
icon = <FileMissingIcon />;
|
||||
}
|
||||
|
||||
const splitItem = elementString.split("/");
|
||||
|
||||
return (
|
||||
<div className={classes.fileName}>
|
||||
{icon}
|
||||
<span className={classes.fileNameText}>
|
||||
{splitItem[splitItem.length - 1]}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((b: BucketObject) => {
|
||||
if (filterObjects === "") {
|
||||
return true;
|
||||
@@ -938,11 +866,15 @@ const ListObjects = ({
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
const renderName = (element: string) => {
|
||||
return displayName(element, classes);
|
||||
};
|
||||
|
||||
const listModeColumns = [
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
renderFunction: renderName,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
@@ -967,7 +899,7 @@ const ListObjects = ({
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
renderFunction: displayName,
|
||||
renderFunction: renderName,
|
||||
enableSort: true,
|
||||
},
|
||||
{
|
||||
@@ -1118,7 +1050,7 @@ const ListObjects = ({
|
||||
</BoxIconButton>
|
||||
<input
|
||||
type="file"
|
||||
multiple={true}
|
||||
multiple
|
||||
onChange={(e) => uploadObject(e)}
|
||||
id="file-input"
|
||||
style={{ display: "none" }}
|
||||
@@ -1244,6 +1176,9 @@ const mapDispatchToProps = {
|
||||
resetRewind,
|
||||
setBucketDetailsLoad,
|
||||
setBucketInfo,
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// 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 {
|
||||
FileBookIcon,
|
||||
FileCodeIcon,
|
||||
FileConfigIcon,
|
||||
FileDbIcon,
|
||||
FileFontIcon,
|
||||
FileImageIcon,
|
||||
FileLockIcon,
|
||||
FileMissingIcon,
|
||||
FileMusicIcon,
|
||||
FilePdfIcon,
|
||||
FilePptIcon,
|
||||
FileTxtIcon,
|
||||
FileVideoIcon,
|
||||
FileXlsIcon,
|
||||
FileZipIcon,
|
||||
} from "../../../../../../icons";
|
||||
import ObjectBrowserIcon from "../../../../../../icons/ObjectBrowserIcon";
|
||||
import ObjectBrowserFolderIcon from "../../../../../../icons/ObjectBrowserFolderIcon";
|
||||
|
||||
export const displayName = (element: string, classes: any) => {
|
||||
let elementString = element;
|
||||
let icon = <ObjectBrowserIcon />;
|
||||
// Element is a folder
|
||||
if (element.endsWith("/")) {
|
||||
icon = <ObjectBrowserFolderIcon />;
|
||||
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) {
|
||||
if (lowercaseElement.endsWith(`.${ext}`)) {
|
||||
icon = etc.icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!element.endsWith("/") && element.indexOf(".") < 0) {
|
||||
icon = <FileMissingIcon />;
|
||||
}
|
||||
|
||||
const splitItem = elementString.split("/");
|
||||
|
||||
return (
|
||||
<div className={classes.fileName}>
|
||||
{icon}
|
||||
<span className={classes.fileNameText}>
|
||||
{splitItem[splitItem.length - 1]}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -81,6 +81,11 @@ import VerticalTabs from "../../../../Common/VerticalTabs/VerticalTabs";
|
||||
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
|
||||
import { RecoverIcon } from "../../../../../../icons";
|
||||
import SecureComponent from "../../../../../../common/SecureComponent/SecureComponent";
|
||||
import {
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -225,6 +230,9 @@ interface IObjectDetailsProps {
|
||||
distributedSetup: boolean;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setNewObject: typeof setNewObject;
|
||||
updateProgress: typeof updateProgress;
|
||||
completeObject: typeof completeObject;
|
||||
}
|
||||
|
||||
const emptyFile: IFileInfo = {
|
||||
@@ -249,6 +257,9 @@ const ObjectDetails = ({
|
||||
bucketToRewind,
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
}: IObjectDetailsProps) => {
|
||||
const [loadObjectData, setLoadObjectData] = useState<boolean>(true);
|
||||
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
|
||||
@@ -367,14 +378,32 @@ const ObjectDetails = ({
|
||||
};
|
||||
|
||||
const downloadObject = (object: IFileInfo) => {
|
||||
if (object.size && parseInt(object.size) > 104857600) {
|
||||
// If file is bigger than 100MB we show a notification
|
||||
setSnackBarMessage(
|
||||
"Download process started, it may take a few moments to complete"
|
||||
);
|
||||
}
|
||||
const identityDownload = btoa(
|
||||
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
|
||||
);
|
||||
|
||||
download(bucketName, internalPaths, object.version_id);
|
||||
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 tableActions: ItemActions[] = [
|
||||
@@ -951,6 +980,9 @@ const mapStateToProps = ({ objectBrowser, system }: AppState) => ({
|
||||
const mapDispatchToProps = {
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
setNewObject,
|
||||
updateProgress,
|
||||
completeObject,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -19,7 +19,10 @@ import { BucketObject, RewindObject } from "./ListObjects/types";
|
||||
export const download = (
|
||||
bucketName: string,
|
||||
objectPath: string,
|
||||
versionID: any
|
||||
versionID: any,
|
||||
fileSize: number,
|
||||
progressCallback: (progress: number) => void,
|
||||
completeCallback: () => void
|
||||
) => {
|
||||
const anchor = document.createElement("a");
|
||||
document.body.appendChild(anchor);
|
||||
@@ -27,7 +30,46 @@ export const download = (
|
||||
if (versionID) {
|
||||
path = path.concat(`&version_id=${versionID}`);
|
||||
}
|
||||
window.location.href = path;
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
req.open("GET", path, true);
|
||||
req.addEventListener(
|
||||
"progress",
|
||||
function (evt) {
|
||||
var percentComplete = Math.round((evt.loaded / fileSize) * 100);
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback(percentComplete);
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
req.responseType = "blob";
|
||||
req.onreadystatechange = () => {
|
||||
if (req.readyState === 4 && req.status === 200) {
|
||||
const rspHeader = req.getResponseHeader("Content-Disposition");
|
||||
|
||||
let filename = "download";
|
||||
|
||||
if (rspHeader) {
|
||||
filename = rspHeader.split('"')[1];
|
||||
}
|
||||
|
||||
if (completeCallback) {
|
||||
completeCallback();
|
||||
}
|
||||
|
||||
var link = document.createElement("a");
|
||||
link.href = window.URL.createObjectURL(req.response);
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
};
|
||||
req.send();
|
||||
};
|
||||
|
||||
// Review file extension by name & returns the type of preview browser that can be used
|
||||
@@ -50,7 +92,7 @@ export const extensionPreview = (
|
||||
"png",
|
||||
"heic",
|
||||
];
|
||||
const textExtensions = ["pdf", "txt"];
|
||||
const textExtensions = ["pdf", "txt", "json"];
|
||||
const audioExtensions = ["wav", "mp3", "alac", "aiff", "dsd", "pcm"];
|
||||
const videoExtensions = [
|
||||
"mp4",
|
||||
|
||||
@@ -117,6 +117,7 @@ import {
|
||||
VersionIcon,
|
||||
WarpIcon,
|
||||
WatchIcon,
|
||||
ObjectManagerIcon,
|
||||
} from "../../../icons";
|
||||
import WarnIcon from "../../../icons/WarnIcon";
|
||||
|
||||
@@ -717,6 +718,11 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
|
||||
WarnIcon
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ObjectManagerIcon />
|
||||
<br />
|
||||
ObjectManagerIcon
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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 { Theme } from "@mui/material/styles";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { IFileItem } from "../../ObjectBrowser/reducers";
|
||||
import ProgressBarWrapper from "../ProgressBarWrapper/ProgressBarWrapper";
|
||||
import { DownloadStatIcon, UploadStatIcon } from "../../../../icons";
|
||||
|
||||
interface IObjectHandled {
|
||||
classes: any;
|
||||
objectToDisplay: IFileItem;
|
||||
deleteFromList: (instanceID: string) => void;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
container: {
|
||||
borderBottom: "#E2E2E2 1px solid",
|
||||
padding: "15px 5px",
|
||||
margin: "0 15px",
|
||||
position: "relative",
|
||||
"& .showOnHover": {
|
||||
opacity: 0,
|
||||
transitionDuration: "0.2s",
|
||||
},
|
||||
"&.inProgress": {
|
||||
"& .hideOnProgress": {
|
||||
visibility: "hidden",
|
||||
},
|
||||
},
|
||||
"&:hover": {
|
||||
"& .showOnHover": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
headItem: {
|
||||
color: "#868686",
|
||||
fontSize: 12,
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
},
|
||||
progressContainer: {
|
||||
marginTop: 5,
|
||||
},
|
||||
objectDetails: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
iconContainer: {
|
||||
paddingTop: 5,
|
||||
marginRight: 5,
|
||||
"& svg": {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
closeIcon: {
|
||||
"&::before": {
|
||||
width: 1,
|
||||
height: 12,
|
||||
content: "' '",
|
||||
position: "absolute",
|
||||
transform: "rotate(45deg)",
|
||||
borderLeft: "#9c9c9c 2px solid",
|
||||
},
|
||||
"&::after": {
|
||||
width: 1,
|
||||
height: 12,
|
||||
content: "' '",
|
||||
position: "absolute",
|
||||
transform: "rotate(-45deg)",
|
||||
borderLeft: "#9c9c9c 2px solid",
|
||||
},
|
||||
},
|
||||
closeButton: {
|
||||
backgroundColor: "transparent",
|
||||
border: 0,
|
||||
right: 0,
|
||||
position: "absolute",
|
||||
},
|
||||
fileName: {
|
||||
width: 230,
|
||||
},
|
||||
});
|
||||
|
||||
const ObjectHandled = ({
|
||||
classes,
|
||||
objectToDisplay,
|
||||
deleteFromList,
|
||||
}: IObjectHandled) => {
|
||||
const prefix = `/${objectToDisplay.prefix}`;
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className={`${classes.container} ${
|
||||
!objectToDisplay.done ? "inProgress" : ""
|
||||
}`}
|
||||
>
|
||||
<div className={classes.clearListIcon}>
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteFromList(objectToDisplay.instanceID);
|
||||
}}
|
||||
className={`${classes.closeButton} hideOnProgress showOnHover`}
|
||||
disabled={!objectToDisplay.done}
|
||||
>
|
||||
<span className={classes.closeIcon}></span>
|
||||
</button>
|
||||
</div>
|
||||
<div className={classes.objectDetails}>
|
||||
<div className={classes.iconContainer}>
|
||||
{objectToDisplay.type === "download" ? (
|
||||
<DownloadStatIcon />
|
||||
) : (
|
||||
<UploadStatIcon />
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.fileName}>
|
||||
<div className={classes.headItem}>
|
||||
<strong>Bucket: </strong>
|
||||
{objectToDisplay.bucketName}
|
||||
</div>
|
||||
<Tooltip title={prefix} placement="top-start">
|
||||
<div className={classes.headItem}>{prefix}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.progressContainer}>
|
||||
{objectToDisplay.waitingForFile ? (
|
||||
<ProgressBarWrapper indeterminate value={0} ready={false} />
|
||||
) : (
|
||||
<ProgressBarWrapper
|
||||
value={objectToDisplay.percentage}
|
||||
ready={objectToDisplay.done}
|
||||
withLabel
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ObjectHandled);
|
||||
@@ -0,0 +1,136 @@
|
||||
// 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 { Theme } from "@mui/material/styles";
|
||||
import { connect } from "react-redux";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Tooltip, IconButton } from "@mui/material";
|
||||
import { AppState } from "../../../../store";
|
||||
import { IFileItem } from "../../ObjectBrowser/reducers";
|
||||
import { deleteFromList, cleanList } from "../../ObjectBrowser/actions";
|
||||
import { TrashIcon } from "../../../../icons";
|
||||
import ObjectHandled from "./ObjectHandled";
|
||||
|
||||
interface IObjectManager {
|
||||
objects: IFileItem[];
|
||||
classes: any;
|
||||
managerOpen: boolean;
|
||||
deleteFromList: typeof deleteFromList;
|
||||
cleanList: typeof cleanList;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
downloadContainer: {
|
||||
border: "#EAEDEE 1px solid",
|
||||
boxShadow: "rgba(0, 0, 0, 0.08) 0 3px 10px",
|
||||
backgroundColor: "#fff",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 80,
|
||||
width: 300,
|
||||
overflowY: "hidden",
|
||||
overflowX: "hidden",
|
||||
borderRadius: 3,
|
||||
zIndex: 1000,
|
||||
padding: 0,
|
||||
height: 0,
|
||||
transitionDuration: "0.3s",
|
||||
visibility: "hidden",
|
||||
"&.open": {
|
||||
visibility: "visible",
|
||||
minHeight: 400,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
marginBottom: 5,
|
||||
paddingBottom: 12,
|
||||
borderBottom: "#E2E2E2 1px solid",
|
||||
margin: "15px 15px 5px 15px",
|
||||
},
|
||||
actionsContainer: {
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
minHeight: 250,
|
||||
maxHeight: 335,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
cleanIcon: {
|
||||
position: "absolute",
|
||||
right: 14,
|
||||
top: 12,
|
||||
},
|
||||
cleanButton: {
|
||||
"& svg": {
|
||||
width: 20,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const ObjectManager = ({
|
||||
objects,
|
||||
classes,
|
||||
managerOpen,
|
||||
deleteFromList,
|
||||
cleanList,
|
||||
}: IObjectManager) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className={`${classes.downloadContainer} ${managerOpen ? "open" : ""}`}
|
||||
>
|
||||
<div className={classes.cleanIcon}>
|
||||
<Tooltip title={"Clean Completed Objects"} placement="bottom-start">
|
||||
<IconButton
|
||||
aria-label={"Clear Completed List"}
|
||||
size={"small"}
|
||||
onClick={cleanList}
|
||||
className={classes.cleanButton}
|
||||
>
|
||||
<TrashIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={classes.title}>Object Manager</div>
|
||||
<div className={classes.actionsContainer}>
|
||||
{objects.map((object, key) => (
|
||||
<ObjectHandled
|
||||
objectToDisplay={object}
|
||||
key={`object-handled-${object.instanceID}`}
|
||||
deleteFromList={deleteFromList}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
objects: state.objectBrowser.objectManager.objectsToManage,
|
||||
managerOpen: state.objectBrowser.objectManager.managerOpen,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { deleteFromList, cleanList });
|
||||
|
||||
export default withStyles(styles)(connector(ObjectManager));
|
||||
@@ -1,14 +1,28 @@
|
||||
import React from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { connect } from "react-redux";
|
||||
import { Box } from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { AppState } from "../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import OperatorLogo from "../../../../icons/OperatorLogo";
|
||||
import ConsoleLogo from "../../../../icons/ConsoleLogo";
|
||||
import { Box } from "@mui/material";
|
||||
import { IFileItem } from "../../ObjectBrowser/reducers";
|
||||
import { toggleList } from "../../ObjectBrowser/actions";
|
||||
import { ObjectManagerIcon } from "../../../../icons";
|
||||
|
||||
interface IPageHeader {
|
||||
classes: any;
|
||||
sidebarOpen?: boolean;
|
||||
operatorMode?: boolean;
|
||||
label: any;
|
||||
actions?: any;
|
||||
managerObjects?: IFileItem[];
|
||||
toggleList: typeof toggleList;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -45,20 +59,14 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
});
|
||||
|
||||
interface IPageHeader {
|
||||
classes: any;
|
||||
sidebarOpen?: boolean;
|
||||
operatorMode?: boolean;
|
||||
label: any;
|
||||
actions?: any;
|
||||
}
|
||||
|
||||
const PageHeader = ({
|
||||
classes,
|
||||
label,
|
||||
actions,
|
||||
sidebarOpen,
|
||||
operatorMode,
|
||||
managerObjects,
|
||||
toggleList,
|
||||
}: IPageHeader) => {
|
||||
return (
|
||||
<Grid
|
||||
@@ -82,11 +90,22 @@ const PageHeader = ({
|
||||
{label}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{actions && (
|
||||
<Grid item xs={12} sm={12} md={6} className={classes.rightMenu}>
|
||||
{actions}
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sm={12} md={6} className={classes.rightMenu}>
|
||||
{actions && actions}
|
||||
{managerObjects && managerObjects.length > 0 && (
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
toggleList();
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<ObjectManagerIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -94,8 +113,13 @@ const PageHeader = ({
|
||||
const mapState = (state: AppState) => ({
|
||||
sidebarOpen: state.system.sidebarOpen,
|
||||
operatorMode: state.system.operatorMode,
|
||||
managerObjects: state.objectBrowser.objectManager.objectsToManage,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, null);
|
||||
const mapDispatchToProps = {
|
||||
toggleList,
|
||||
};
|
||||
|
||||
const connector = connect(mapState, mapDispatchToProps);
|
||||
|
||||
export default connector(withStyles(styles)(PageHeader));
|
||||
|
||||
@@ -18,12 +18,15 @@ import React from "react";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import LinearProgress, {
|
||||
linearProgressClasses,
|
||||
LinearProgressProps,
|
||||
} from "@mui/material/LinearProgress";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
interface IProgressBarWrapper {
|
||||
value: number;
|
||||
ready: boolean;
|
||||
indeterminate?: boolean;
|
||||
withLabel?: boolean;
|
||||
}
|
||||
|
||||
const BorderLinearProgress = styled(LinearProgress)(() => ({
|
||||
@@ -37,18 +40,35 @@ const BorderLinearProgress = styled(LinearProgress)(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function LinearProgressWithLabel(props: LinearProgressProps) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Box sx={{ width: "100%", mr: 1 }}>
|
||||
<BorderLinearProgress variant="determinate" {...props} />
|
||||
</Box>
|
||||
<Box sx={{ minWidth: 35, fontSize: 14 }} className={"value"}>
|
||||
{`${Math.round(props.value || 0)}%`}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const ProgressBarWrapper = ({
|
||||
value,
|
||||
ready,
|
||||
indeterminate,
|
||||
withLabel,
|
||||
}: IProgressBarWrapper) => {
|
||||
return (
|
||||
<BorderLinearProgress
|
||||
variant={indeterminate && !ready ? "indeterminate" : "determinate"}
|
||||
value={ready ? 100 : value}
|
||||
color={ready ? "success" : "primary"}
|
||||
/>
|
||||
);
|
||||
const propsComponent: LinearProgressProps = {
|
||||
variant: indeterminate && !ready ? "indeterminate" : "determinate",
|
||||
value: ready ? 100 : value,
|
||||
color: ready ? "success" : "primary",
|
||||
};
|
||||
if (withLabel) {
|
||||
return <LinearProgressWithLabel {...propsComponent} />;
|
||||
}
|
||||
|
||||
return <BorderLinearProgress {...propsComponent} />;
|
||||
};
|
||||
|
||||
export default ProgressBarWrapper;
|
||||
|
||||
@@ -86,6 +86,10 @@ const IconsScreen = React.lazy(() => import("./Common/IconsScreen"));
|
||||
|
||||
const Speedtest = React.lazy(() => import("./Speedtest/Speedtest"));
|
||||
|
||||
const ObjectManager = React.lazy(
|
||||
() => import("./Common/ObjectManager/ObjectManager")
|
||||
);
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
const Buckets = React.lazy(() => import("./Buckets/Buckets"));
|
||||
@@ -539,6 +543,9 @@ const Console = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ObjectManager />
|
||||
</Suspense>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
{allowedRoutes.map((route: any) => (
|
||||
|
||||
@@ -14,11 +14,21 @@
|
||||
// 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 { 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 =
|
||||
"OBJECT_MANAGER/UPDATE_PROGRESS_OBJECT";
|
||||
export const OBJECT_MANAGER_COMPLETE_OBJECT = "OBJECT_MANAGER/COMPLETE_OBJECT";
|
||||
export const OBJECT_MANAGER_DELETE_FROM_OBJECT_LIST =
|
||||
"OBJECT_MANAGER/DELETE_FROM_OBJECT_LIST";
|
||||
export const OBJECT_MANAGER_CLEAN_LIST = "OBJECT_MANAGER/CLEAN_LIST";
|
||||
export const OBJECT_MANAGER_TOGGLE_LIST = "OBJECT_MANAGER/OPEN_LIST";
|
||||
|
||||
interface RewindSetEnabled {
|
||||
type: typeof REWIND_SET_ENABLE;
|
||||
bucket: string;
|
||||
@@ -35,10 +45,45 @@ interface FileModeEnabled {
|
||||
status: boolean;
|
||||
}
|
||||
|
||||
interface OMNewObject {
|
||||
type: typeof OBJECT_MANAGER_NEW_OBJECT;
|
||||
newObject: IFileItem;
|
||||
}
|
||||
|
||||
interface OMUpdateProgress {
|
||||
type: typeof OBJECT_MANAGER_UPDATE_PROGRESS_OBJECT;
|
||||
instanceID: string;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
interface OMCompleteObject {
|
||||
type: typeof OBJECT_MANAGER_COMPLETE_OBJECT;
|
||||
instanceID: string;
|
||||
}
|
||||
|
||||
interface OMDeleteFromList {
|
||||
type: typeof OBJECT_MANAGER_DELETE_FROM_OBJECT_LIST;
|
||||
instanceID: string;
|
||||
}
|
||||
|
||||
interface OMCleanList {
|
||||
type: typeof OBJECT_MANAGER_CLEAN_LIST;
|
||||
}
|
||||
|
||||
interface OMToggleList {
|
||||
type: typeof OBJECT_MANAGER_TOGGLE_LIST;
|
||||
}
|
||||
|
||||
export type ObjectBrowserActionTypes =
|
||||
| RewindSetEnabled
|
||||
| RewindReset
|
||||
| FileModeEnabled;
|
||||
| FileModeEnabled
|
||||
| OMNewObject
|
||||
| OMUpdateProgress
|
||||
| OMCompleteObject
|
||||
| OMDeleteFromList
|
||||
| OMCleanList
|
||||
| OMToggleList;
|
||||
|
||||
export const setRewindEnable = (
|
||||
state: boolean,
|
||||
@@ -65,3 +110,44 @@ export const setFileModeEnabled = (status: boolean) => {
|
||||
status,
|
||||
};
|
||||
};
|
||||
|
||||
export const setNewObject = (newObject: IFileItem) => {
|
||||
return {
|
||||
type: OBJECT_MANAGER_NEW_OBJECT,
|
||||
newObject,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateProgress = (instanceID: string, progress: number) => {
|
||||
return {
|
||||
type: OBJECT_MANAGER_UPDATE_PROGRESS_OBJECT,
|
||||
instanceID,
|
||||
progress,
|
||||
};
|
||||
};
|
||||
|
||||
export const completeObject = (instanceID: string) => {
|
||||
return {
|
||||
type: OBJECT_MANAGER_COMPLETE_OBJECT,
|
||||
instanceID,
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteFromList = (instanceID: string) => {
|
||||
return {
|
||||
type: OBJECT_MANAGER_DELETE_FROM_OBJECT_LIST,
|
||||
instanceID,
|
||||
};
|
||||
};
|
||||
|
||||
export const cleanList = () => {
|
||||
return {
|
||||
type: OBJECT_MANAGER_CLEAN_LIST,
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleList = () => {
|
||||
return {
|
||||
type: OBJECT_MANAGER_TOGGLE_LIST,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -18,6 +18,12 @@ import {
|
||||
REWIND_RESET_REWIND,
|
||||
REWIND_FILE_MODE_ENABLED,
|
||||
ObjectBrowserActionTypes,
|
||||
OBJECT_MANAGER_NEW_OBJECT,
|
||||
OBJECT_MANAGER_UPDATE_PROGRESS_OBJECT,
|
||||
OBJECT_MANAGER_COMPLETE_OBJECT,
|
||||
OBJECT_MANAGER_DELETE_FROM_OBJECT_LIST,
|
||||
OBJECT_MANAGER_CLEAN_LIST,
|
||||
OBJECT_MANAGER_TOGGLE_LIST,
|
||||
} from "./actions";
|
||||
|
||||
export interface Route {
|
||||
@@ -35,12 +41,28 @@ export interface RewindItem {
|
||||
export interface ObjectBrowserState {
|
||||
fileMode: boolean;
|
||||
rewind: RewindItem;
|
||||
objectManager: ObjectManager;
|
||||
}
|
||||
|
||||
export interface ObjectBrowserReducer {
|
||||
objectBrowser: ObjectBrowserState;
|
||||
}
|
||||
|
||||
export interface ObjectManager {
|
||||
objectsToManage: IFileItem[];
|
||||
managerOpen: boolean;
|
||||
}
|
||||
|
||||
export interface IFileItem {
|
||||
type: "download" | "upload";
|
||||
instanceID: string;
|
||||
bucketName: string;
|
||||
prefix: string;
|
||||
percentage: number;
|
||||
done: boolean;
|
||||
waitingForFile: boolean;
|
||||
}
|
||||
|
||||
const defaultRewind = {
|
||||
rewindEnabled: false,
|
||||
bucketToRewind: "",
|
||||
@@ -52,6 +74,10 @@ const initialState: ObjectBrowserState = {
|
||||
rewind: {
|
||||
...defaultRewind,
|
||||
},
|
||||
objectManager: {
|
||||
objectsToManage: [],
|
||||
managerOpen: false,
|
||||
},
|
||||
};
|
||||
|
||||
export function objectBrowserReducer(
|
||||
@@ -76,6 +102,97 @@ export function objectBrowserReducer(
|
||||
return { ...state, rewind: resetItem };
|
||||
case REWIND_FILE_MODE_ENABLED:
|
||||
return { ...state, fileMode: action.status };
|
||||
case OBJECT_MANAGER_NEW_OBJECT:
|
||||
const cloneObjects = [...state.objectManager.objectsToManage];
|
||||
|
||||
cloneObjects.push(action.newObject);
|
||||
|
||||
return {
|
||||
...state,
|
||||
objectManager: {
|
||||
objectsToManage: cloneObjects,
|
||||
managerOpen: true,
|
||||
},
|
||||
};
|
||||
case OBJECT_MANAGER_UPDATE_PROGRESS_OBJECT:
|
||||
const copyManager = [...state.objectManager.objectsToManage];
|
||||
|
||||
const itemUpdate = state.objectManager.objectsToManage.findIndex(
|
||||
(item) => item.instanceID === action.instanceID
|
||||
);
|
||||
|
||||
if (itemUpdate === -1) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
copyManager[itemUpdate].percentage = action.progress;
|
||||
copyManager[itemUpdate].waitingForFile = false;
|
||||
|
||||
return {
|
||||
...state,
|
||||
objectManager: {
|
||||
objectsToManage: copyManager,
|
||||
managerOpen: state.objectManager.managerOpen,
|
||||
},
|
||||
};
|
||||
case OBJECT_MANAGER_COMPLETE_OBJECT:
|
||||
const copyObject = [...state.objectManager.objectsToManage];
|
||||
|
||||
const objectToComplete = state.objectManager.objectsToManage.findIndex(
|
||||
(item) => item.instanceID === action.instanceID
|
||||
);
|
||||
|
||||
if (objectToComplete === -1) {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
copyObject[objectToComplete].percentage = 100;
|
||||
copyObject[objectToComplete].waitingForFile = false;
|
||||
copyObject[objectToComplete].done = true;
|
||||
|
||||
return {
|
||||
...state,
|
||||
objectManager: {
|
||||
objectsToManage: copyObject,
|
||||
managerOpen: state.objectManager.managerOpen,
|
||||
},
|
||||
};
|
||||
case OBJECT_MANAGER_DELETE_FROM_OBJECT_LIST:
|
||||
const notObject = state.objectManager.objectsToManage.filter(
|
||||
(element) => element.instanceID !== action.instanceID
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
objectManager: {
|
||||
objectsToManage: notObject,
|
||||
managerOpen:
|
||||
notObject.length === 0 ? false : state.objectManager.managerOpen,
|
||||
},
|
||||
};
|
||||
case OBJECT_MANAGER_CLEAN_LIST:
|
||||
const nonCompletedList = state.objectManager.objectsToManage.filter(
|
||||
(item) => !item.done
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
objectManager: {
|
||||
objectsToManage: nonCompletedList,
|
||||
managerOpen:
|
||||
nonCompletedList.length === 0
|
||||
? false
|
||||
: state.objectManager.managerOpen,
|
||||
},
|
||||
};
|
||||
case OBJECT_MANAGER_TOGGLE_LIST:
|
||||
return {
|
||||
...state,
|
||||
objectManager: {
|
||||
...state.objectManager,
|
||||
managerOpen: !state.objectManager.managerOpen,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user