Skip to content

Commit a3d0cb3

Browse files
bexsoftBenjamin Perez
andauthored
Implemented Upload folder functionality (#1272)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net> Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
1 parent 85d549c commit a3d0cb3

File tree

6 files changed

+129
-18
lines changed

6 files changed

+129
-18
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2021 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import React, { SVGProps } from "react";
18+
19+
const UploadFile = (props: SVGProps<SVGSVGElement>) => {
20+
return (
21+
<svg
22+
{...props}
23+
className={`min-icon`}
24+
fill={"currentcolor"}
25+
xmlns="http://www.w3.org/2000/svg"
26+
viewBox="0 0 255.001 218.1"
27+
>
28+
<defs>
29+
<clipPath id="a">
30+
<path
31+
d="M53.548,94.912v44.816c.43-.22.737-.378,1.517-.759a20.07,20.07,0,0,1,27.673,15.21c.1.677.115.688.163,1.1.063.567.084.968.108,1.463.01.21.068,1.914.072,2,.2,2.214.363,4.336.452,6.449.269,6.381.536,11,.957,15.5.6,6.412.964,12.128,1.066,17.7a19.838,19.838,0,0,1-.976,6.231c.683,6.455,1.592,14.938,1.752,16.438.014.128.023.253.036.38,3.927-.511,5.969-.716,8.382-.813,8.553-.344,16.809-.382,29.335-.235,1.42.017,2.559.021,5.094.054,10.044.13,14.46.163,19.906.127.93-.007,1.643,0,3.234,0,7.429.005,10.477-.237,12.174-.958-.178-1.123-.351-2.228-.614-3.558-.313-1.589-.586-2.862-1.264-5.979-2.292-10.53-3.161-15.585-3.414-22.508a68.539,68.539,0,0,1,2.764-23.067A29.713,29.713,0,0,1,164.278,159c.461-.922.889-1.737,1.372-2.547a22.021,22.021,0,0,1,1.987-2.836,19.87,19.87,0,0,1,3.776-3.5A19.984,19.984,0,0,1,192.33,125.6a20.223,20.223,0,0,1,9.195,3V94.912Z"
32+
fill="none"
33+
/>
34+
</clipPath>
35+
<clipPath id="b">
36+
<path
37+
d="M204.03,236.91c-.393.722-.717,1.447-1.156,2.168-.795,1.3-1.66,2.592-2.547,3.811h3.7Z"
38+
fill="none"
39+
/>
40+
</clipPath>
41+
</defs>
42+
<g transform="translate(-0.036 -24.789)">
43+
<path d="M239.185,72.637A29.456,29.456,0,0,0,209.767,43.6H128.581l-1.119-1.512c-5.078-6.886-12.756-17.3-26.1-17.3H49.394A29.455,29.455,0,0,0,19.972,54.21a19.778,19.778,0,0,0,.236,3.081V70.763A29.818,29.818,0,0,0,.036,98.947c0,.6.023,1.205.076,1.806L9.8,207.577A29.8,29.8,0,0,0,39.545,236.2h175.73A29.8,29.8,0,0,0,245.021,207.6L254.947,100.8q.088-.928.09-1.852A29.792,29.792,0,0,0,239.185,72.637ZM49.394,44.808h51.963c6.586,0,13.645,18.813,20.7,18.813h87.709a9.429,9.429,0,0,1,9.4,9.4v4.7H40.213V54.206h-.229A9.431,9.431,0,0,1,49.394,44.808ZM225.031,206.43a9.781,9.781,0,0,1-9.754,9.748H39.547a9.779,9.779,0,0,1-9.75-9.748L20.051,98.947A9.782,9.782,0,0,1,29.8,89.192H225.268a9.788,9.788,0,0,1,9.758,9.755Z" />
44+
<g transform="translate(-351.512 467)">
45+
<g transform="translate(352 -469)" clip-path="url(#a)">
46+
<path d="M118.046,203.4c0,12.123,18.976,12.123,18.976,0V126.379l10.748,10.443c8.823,8.569,22.236-4.465,13.415-13.034L134.3,97.665a9.685,9.685,0,0,0-13.526,0L93.89,123.788c-8.82,8.568,4.592,21.6,13.415,13.034l10.745-10.443V203.4Z" />
47+
</g>
48+
</g>
49+
<g clip-path="url(#b)">
50+
<path d="M56.052,158.235c0-12.121,18.978-12.121,18.978,0v66.218H185.056V158.235c0-12.121,18.973-12.121,18.973,0v75.436a9.357,9.357,0,0,1-9.486,9.217h-129a9.357,9.357,0,0,1-9.486-9.217V158.235Zm64.5,45.162c0,12.123,18.976,12.123,18.976,0V126.379l10.748,10.443c8.823,8.569,22.236-4.465,13.415-13.034L136.8,97.665a9.685,9.685,0,0,0-13.526,0L96.394,123.788c-8.82,8.568,4.593,21.6,13.415,13.034l10.745-10.443V203.4Z" />
51+
</g>
52+
</g>
53+
</svg>
54+
);
55+
};
56+
57+
export default UploadFile;

portal-ui/src/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,4 @@ export { default as DownloadStatIcon } from "./DownloadStatIcon";
112112
export { default as UploadStatIcon } from "./UploadStatIcon";
113113
export { default as ComputerLineIcon } from "./ComputerLineIcon";
114114
export { default as JSONIcon } from "./JSONIcon";
115+
export { default as UploadFolderIcon } from "./UploadFolderIcon";

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,14 @@ import { BucketInfo, BucketVersioning } from "../../../types";
6262
import { ErrorResponseHandler } from "../../../../../../common/types";
6363

6464
import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
65-
import AddFolderIcon from "../../../../../../icons/AddFolderIcon";
66-
import HistoryIcon from "../../../../../../icons/HistoryIcon";
67-
import FolderIcon from "../../../../../../icons/FolderIcon";
68-
import RefreshIcon from "../../../../../../icons/RefreshIcon";
69-
import UploadIcon from "../../../../../../icons/UploadIcon";
65+
import {
66+
UploadFolderIcon,
67+
UploadIcon,
68+
RefreshIcon,
69+
FolderIcon,
70+
HistoryIcon,
71+
AddFolderIcon,
72+
} from "../../../../../../icons";
7073
import { setBucketDetailsLoad, setBucketInfo } from "../../../actions";
7174
import { AppState } from "../../../../../../store";
7275
import PageLayout from "../../../../Common/Layout/PageLayout";
@@ -302,6 +305,14 @@ const ListObjects = ({
302305
const bucketName = match.params["bucketName"];
303306

304307
const fileUpload = useRef<HTMLInputElement>(null);
308+
const folderUpload = useRef<HTMLInputElement>(null);
309+
310+
useEffect(() => {
311+
if (folderUpload.current !== null) {
312+
folderUpload.current.setAttribute("directory", "");
313+
folderUpload.current.setAttribute("webkitdirectory", "");
314+
}
315+
}, [folderUpload]);
305316

306317
const displayDeleteObject = hasPermission(bucketName, [
307318
IAM_SCOPES.S3_DELETE_OBJECT,
@@ -599,7 +610,7 @@ const ListObjects = ({
599610
setCreateFolderOpen(false);
600611
};
601612

602-
const upload = (e: any, bucketName: string, encodedPath: string) => {
613+
const upload = (e: any, bucketName: string, path: string) => {
603614
if (
604615
e === null ||
605616
e === undefined ||
@@ -613,16 +624,32 @@ const ListObjects = ({
613624
let files = e.target.files;
614625

615626
if (files.length > 0) {
616-
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
617-
618-
if (encodedPath !== "") {
619-
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
620-
}
621-
622627
for (let file of files) {
628+
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
623629
const fileName = file.name;
624630
const blobFile = new Blob([file], { type: file.type });
625631

632+
let encodedPath = "";
633+
634+
const relativeFolderPath = get(file, "webkitRelativePath", "");
635+
636+
if (path !== "" || relativeFolderPath !== "") {
637+
const finalFolderPath = relativeFolderPath
638+
.split("/")
639+
.slice(0, -1)
640+
.join("/");
641+
642+
encodedPath = encodeFileName(
643+
`${path}${finalFolderPath}${
644+
!finalFolderPath.endsWith("/") ? "/" : ""
645+
}`
646+
);
647+
}
648+
649+
if (encodedPath !== "") {
650+
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
651+
}
652+
626653
const identity = btoa(
627654
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
628655
);
@@ -758,7 +785,7 @@ const ListObjects = ({
758785
const decodedPath = decodeFileName(internalPaths);
759786
pathPrefix = decodedPath.endsWith("/") ? decodedPath : decodedPath + "/";
760787
}
761-
upload(e, bucketName, encodeFileName(pathPrefix));
788+
upload(e, bucketName, pathPrefix);
762789
};
763790

764791
const openPreview = (fileObject: BucketObject) => {
@@ -1037,7 +1064,7 @@ const ListObjects = ({
10371064
<BoxIconButton
10381065
tooltip={"Upload file"}
10391066
color="primary"
1040-
aria-label="Refresh List"
1067+
aria-label="Upload File"
10411068
onClick={() => {
10421069
if (fileUpload && fileUpload.current) {
10431070
fileUpload.current.click();
@@ -1056,6 +1083,28 @@ const ListObjects = ({
10561083
style={{ display: "none" }}
10571084
ref={fileUpload}
10581085
/>
1086+
<BoxIconButton
1087+
tooltip={"Upload folder"}
1088+
color="primary"
1089+
aria-label="Upload Folder"
1090+
onClick={() => {
1091+
if (folderUpload && folderUpload.current) {
1092+
folderUpload.current.click();
1093+
}
1094+
}}
1095+
disabled={rewindEnabled}
1096+
size="large"
1097+
>
1098+
<UploadFolderIcon />
1099+
</BoxIconButton>
1100+
<input
1101+
type="file"
1102+
multiple
1103+
onChange={(e) => uploadObject(e)}
1104+
id="file-input"
1105+
style={{ display: "none" }}
1106+
ref={folderUpload}
1107+
/>
10591108
</SecureComponent>
10601109
<Badge
10611110
badgeContent=" "

portal-ui/src/screens/Console/Common/IconsScreen.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import {
118118
WarpIcon,
119119
WatchIcon,
120120
ObjectManagerIcon,
121+
UploadFolderIcon
121122
} from "../../../icons";
122123
import WarnIcon from "../../../icons/WarnIcon";
123124

@@ -723,6 +724,11 @@ const IconsScreen = ({ classes }: IIconsScreenSimple) => {
723724
<br />
724725
ObjectManagerIcon
725726
</Grid>
727+
<Grid item>
728+
<UploadFolderIcon />
729+
<br />
730+
UploadFolderIcon
731+
</Grid>
726732
</div>
727733
);
728734
};

portal-ui/src/screens/Console/Common/ObjectManager/ObjectManager.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const ObjectManager = ({
111111
</IconButton>
112112
</Tooltip>
113113
</div>
114-
<div className={classes.title}>Object Manager</div>
114+
<div className={classes.title}>Downloads / Uploads</div>
115115
<div className={classes.actionsContainer}>
116116
{objects.map((object, key) => (
117117
<ObjectHandled

portal-ui/src/screens/Console/ObjectBrowser/reducers.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ export function objectBrowserReducer(
103103
case REWIND_FILE_MODE_ENABLED:
104104
return { ...state, fileMode: action.status };
105105
case OBJECT_MANAGER_NEW_OBJECT:
106-
const cloneObjects = [...state.objectManager.objectsToManage];
107-
108-
cloneObjects.push(action.newObject);
106+
const cloneObjects = [action.newObject, ...state.objectManager.objectsToManage];
109107

110108
return {
111109
...state,

0 commit comments

Comments
 (0)