diff --git a/portal-ui/src/icons/UploadFolderIcon.tsx b/portal-ui/src/icons/UploadFolderIcon.tsx new file mode 100644 index 000000000..0a5709f5c --- /dev/null +++ b/portal-ui/src/icons/UploadFolderIcon.tsx @@ -0,0 +1,57 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { SVGProps } from "react"; + +const UploadFile = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default UploadFile; diff --git a/portal-ui/src/icons/index.ts b/portal-ui/src/icons/index.ts index 84bedf31c..04b5a3c0e 100644 --- a/portal-ui/src/icons/index.ts +++ b/portal-ui/src/icons/index.ts @@ -112,3 +112,4 @@ export { default as DownloadStatIcon } from "./DownloadStatIcon"; export { default as UploadStatIcon } from "./UploadStatIcon"; export { default as ComputerLineIcon } from "./ComputerLineIcon"; export { default as JSONIcon } from "./JSONIcon"; +export { default as UploadFolderIcon } from "./UploadFolderIcon"; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx index 7fe43153b..a5ab1fe23 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx @@ -62,11 +62,14 @@ import { BucketInfo, BucketVersioning } from "../../../types"; import { ErrorResponseHandler } from "../../../../../../common/types"; import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle"; -import AddFolderIcon from "../../../../../../icons/AddFolderIcon"; -import HistoryIcon from "../../../../../../icons/HistoryIcon"; -import FolderIcon from "../../../../../../icons/FolderIcon"; -import RefreshIcon from "../../../../../../icons/RefreshIcon"; -import UploadIcon from "../../../../../../icons/UploadIcon"; +import { + UploadFolderIcon, + UploadIcon, + RefreshIcon, + FolderIcon, + HistoryIcon, + AddFolderIcon, +} from "../../../../../../icons"; import { setBucketDetailsLoad, setBucketInfo } from "../../../actions"; import { AppState } from "../../../../../../store"; import PageLayout from "../../../../Common/Layout/PageLayout"; @@ -302,6 +305,14 @@ const ListObjects = ({ const bucketName = match.params["bucketName"]; const fileUpload = useRef(null); + const folderUpload = useRef(null); + + useEffect(() => { + if (folderUpload.current !== null) { + folderUpload.current.setAttribute("directory", ""); + folderUpload.current.setAttribute("webkitdirectory", ""); + } + }, [folderUpload]); const displayDeleteObject = hasPermission(bucketName, [ IAM_SCOPES.S3_DELETE_OBJECT, @@ -599,7 +610,7 @@ const ListObjects = ({ setCreateFolderOpen(false); }; - const upload = (e: any, bucketName: string, encodedPath: string) => { + const upload = (e: any, bucketName: string, path: string) => { if ( e === null || e === undefined || @@ -613,16 +624,32 @@ const ListObjects = ({ let files = e.target.files; if (files.length > 0) { - let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`; - - if (encodedPath !== "") { - uploadUrl = `${uploadUrl}?prefix=${encodedPath}`; - } - for (let file of files) { + let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`; const fileName = file.name; const blobFile = new Blob([file], { type: file.type }); + let encodedPath = ""; + + const relativeFolderPath = get(file, "webkitRelativePath", ""); + + if (path !== "" || relativeFolderPath !== "") { + const finalFolderPath = relativeFolderPath + .split("/") + .slice(0, -1) + .join("/"); + + encodedPath = encodeFileName( + `${path}${finalFolderPath}${ + !finalFolderPath.endsWith("/") ? "/" : "" + }` + ); + } + + if (encodedPath !== "") { + uploadUrl = `${uploadUrl}?prefix=${encodedPath}`; + } + const identity = btoa( `${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}` ); @@ -758,7 +785,7 @@ const ListObjects = ({ const decodedPath = decodeFileName(internalPaths); pathPrefix = decodedPath.endsWith("/") ? decodedPath : decodedPath + "/"; } - upload(e, bucketName, encodeFileName(pathPrefix)); + upload(e, bucketName, pathPrefix); }; const openPreview = (fileObject: BucketObject) => { @@ -1037,7 +1064,7 @@ const ListObjects = ({ { if (fileUpload && fileUpload.current) { fileUpload.current.click(); @@ -1056,6 +1083,28 @@ const ListObjects = ({ style={{ display: "none" }} ref={fileUpload} /> + { + if (folderUpload && folderUpload.current) { + folderUpload.current.click(); + } + }} + disabled={rewindEnabled} + size="large" + > + + + uploadObject(e)} + id="file-input" + style={{ display: "none" }} + ref={folderUpload} + /> {
ObjectManagerIcon + + +
+ UploadFolderIcon +
); }; diff --git a/portal-ui/src/screens/Console/Common/ObjectManager/ObjectManager.tsx b/portal-ui/src/screens/Console/Common/ObjectManager/ObjectManager.tsx index 5c2f8dc3c..29be443ed 100644 --- a/portal-ui/src/screens/Console/Common/ObjectManager/ObjectManager.tsx +++ b/portal-ui/src/screens/Console/Common/ObjectManager/ObjectManager.tsx @@ -111,7 +111,7 @@ const ObjectManager = ({ -
Object Manager
+
Downloads / Uploads
{objects.map((object, key) => (