Skip to content

Commit e4510cb

Browse files
authored
Add upload api and integrate it with object browser on UI (#327)
1 parent 2c14142 commit e4510cb

13 files changed

+917
-149
lines changed

portal-ui/bindata_assetfs.go

Lines changed: 117 additions & 140 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 122 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,28 @@
1616

1717
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
1818
import Grid from "@material-ui/core/Grid";
19-
import { Button } from "@material-ui/core";
20-
import Typography from "@material-ui/core/Typography";
2119
import TextField from "@material-ui/core/TextField";
2220
import InputAdornment from "@material-ui/core/InputAdornment";
2321
import SearchIcon from "@material-ui/icons/Search";
2422
import { BucketObject, BucketObjectsList } from "./types";
2523
import api from "../../../../../../common/api";
26-
import React, { useEffect, useState } from "react";
24+
import React from "react";
2725
import TableWrapper from "../../../../Common/TableWrapper/TableWrapper";
28-
import { MinTablePaginationActions } from "../../../../../../common/MinTablePaginationActions";
29-
import { CreateIcon } from "../../.././../../../icons";
3026
import { niceBytes } from "../../../../../../common/utils";
31-
import Moment from "react-moment";
3227
import DeleteObject from "./DeleteObject";
28+
3329
import {
3430
actionsTray,
3531
containerForHeader,
3632
searchField,
3733
} from "../../../../Common/FormComponents/common/styleLibrary";
3834
import PageHeader from "../../../../Common/PageHeader/PageHeader";
3935
import storage from "local-storage-fallback";
36+
import { isNullOrUndefined } from "util";
37+
import { Button, Input } from "@material-ui/core";
38+
import * as reactMoment from "react-moment";
39+
import { CreateIcon } from "../../../../../../icons";
40+
import Snackbar from "@material-ui/core/Snackbar";
4041

4142
const styles = (theme: Theme) =>
4243
createStyles({
@@ -88,6 +89,8 @@ interface IListObjectsState {
8889
selectedObject: string;
8990
selectedBucket: string;
9091
filterObjects: string;
92+
openSnackbar: boolean;
93+
snackBarMessage: string;
9194
}
9295

9396
class ListObjects extends React.Component<
@@ -104,6 +107,8 @@ class ListObjects extends React.Component<
104107
selectedObject: "",
105108
selectedBucket: "",
106109
filterObjects: "",
110+
openSnackbar: false,
111+
snackBarMessage: "",
107112
};
108113

109114
fetchRecords = () => {
@@ -141,6 +146,73 @@ class ListObjects extends React.Component<
141146
});
142147
}
143148

149+
showSnackBarMessage(text: string) {
150+
this.setState({ openSnackbar: true, snackBarMessage: text });
151+
}
152+
153+
closeSnackBar() {
154+
this.setState({ openSnackbar: false, snackBarMessage: `` });
155+
}
156+
157+
upload(e: any, bucketName: string, path: string) {
158+
let listObjects = this;
159+
if (isNullOrUndefined(e) || isNullOrUndefined(e.target)) {
160+
return;
161+
}
162+
const token: string = storage.getItem("token")!;
163+
e.preventDefault();
164+
let file = e.target.files[0];
165+
const fileName = file.name;
166+
167+
const objectName = `${path}${fileName}`;
168+
let uploadUrl = `/api/v1/buckets/${bucketName}/objects/upload?prefix=${objectName}`;
169+
let xhr = new XMLHttpRequest();
170+
171+
xhr.open("POST", uploadUrl, true);
172+
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
173+
174+
xhr.withCredentials = false;
175+
xhr.onload = function(event) {
176+
// TODO: handle status
177+
if (xhr.status == 401 || xhr.status == 403) {
178+
listObjects.showSnackBarMessage(
179+
"An error occurred while uploading the file."
180+
);
181+
}
182+
if (xhr.status == 500) {
183+
listObjects.showSnackBarMessage(
184+
"An error occurred while uploading the file."
185+
);
186+
}
187+
if (xhr.status == 200) {
188+
listObjects.showSnackBarMessage("Object uploaded successfully.");
189+
listObjects.fetchRecords();
190+
}
191+
};
192+
193+
xhr.upload.addEventListener("error", (event) => {
194+
// TODO: handle error
195+
this.showSnackBarMessage("An error occurred while uploading the file.");
196+
});
197+
198+
xhr.upload.addEventListener("progress", (event) => {
199+
// TODO: handle progress with event.loaded, event.total
200+
});
201+
202+
xhr.onerror = () => {
203+
listObjects.showSnackBarMessage(
204+
"An error occurred while uploading the file."
205+
);
206+
};
207+
208+
var formData = new FormData();
209+
var blobFile = new Blob([file]);
210+
211+
formData.append("upfile", blobFile);
212+
xhr.send(formData);
213+
e.target.value = null;
214+
}
215+
144216
download(bucketName: string, objectName: string) {
145217
var anchor = document.createElement("a");
146218
document.body.appendChild(anchor);
@@ -184,9 +256,11 @@ class ListObjects extends React.Component<
184256
selectedBucket,
185257
deleteOpen,
186258
filterObjects,
259+
snackBarMessage,
260+
openSnackbar,
187261
} = this.state;
188262
const displayParsedDate = (date: string) => {
189-
return <Moment>{date}</Moment>;
263+
return <reactMoment.default>{date}</reactMoment.default>;
190264
};
191265

192266
const confirmDeleteObject = (object: string) => {
@@ -197,6 +271,25 @@ class ListObjects extends React.Component<
197271
this.download(selectedBucket, object);
198272
};
199273

274+
const uploadObject = (e: any): void => {
275+
// TODO: handle deeper paths/folders
276+
let file = e.target.files[0];
277+
this.showSnackBarMessage(`Uploading: ${file.name}`);
278+
this.upload(e, selectedBucket, "");
279+
};
280+
281+
const snackBarAction = (
282+
<Button
283+
color="secondary"
284+
size="small"
285+
onClick={() => {
286+
this.closeSnackBar();
287+
}}
288+
>
289+
Dismiss
290+
</Button>
291+
);
292+
200293
const tableActions = [
201294
{ type: "download", onClick: downloadObject, sendOnlyId: true },
202295
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
@@ -226,6 +319,11 @@ class ListObjects extends React.Component<
226319
}}
227320
/>
228321
)}
322+
<Snackbar
323+
open={openSnackbar}
324+
message={snackBarMessage}
325+
action={snackBarAction}
326+
/>
229327
<PageHeader label="Objects" />
230328
<Grid container>
231329
<Grid item xs={12} className={classes.container}>
@@ -249,6 +347,23 @@ class ListObjects extends React.Component<
249347
),
250348
}}
251349
/>
350+
351+
<>
352+
<Button
353+
variant="contained"
354+
color="primary"
355+
startIcon={<CreateIcon />}
356+
component="label"
357+
>
358+
Upload Object
359+
<Input
360+
type="file"
361+
onChange={(e) => uploadObject(e)}
362+
id="file-input"
363+
style={{ display: "none" }}
364+
/>
365+
</Button>
366+
</>
252367
</Grid>
253368
<Grid item xs={12}>
254369
<br />

restapi/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type MinioClient interface {
5858
listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
5959
getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error)
6060
getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error)
61+
putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error)
6162
}
6263

6364
// Interface implementation
@@ -128,6 +129,10 @@ func (c minioClient) getObjectLegalHold(ctx context.Context, bucketName, objectN
128129
return c.client.GetObjectLegalHold(ctx, bucketName, objectName, opts)
129130
}
130131

132+
func (c minioClient) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
133+
return c.client.PutObject(ctx, bucketName, objectName, reader, objectSize, opts)
134+
}
135+
131136
// MCClient interface with all functions to be implemented
132137
// by mock when testing, it should include all mc/S3Client respective api calls
133138
// that are used within this project.

restapi/doc.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

restapi/embedded_spec.go

Lines changed: 84 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)