From 8dc1415404dd73624e1dbe8482f1e51104a03c77 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 21 Jun 2021 14:52:57 +0200 Subject: [PATCH 1/3] Skip slaveUpdateEventHandler test on Windows Due to the lack of a simple way to automate graceful shutdown of a slave on Windows causing the test to be unreliable, skip it on Windows. --- test/integration/clusterio.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 5f3d6441..fff1c043 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -65,6 +65,12 @@ describe("Integration of Clusterio", function() { describe("clusterioslave", function() { describe("slaveUpdateEventHandler()", function() { it("should trigger when a new slave is added", async function() { + // On windows there's currently no way to automate graceful shutdown of the slave + // process as CTRL+C is some weird terminal thing and SIGINT isn't a thing. + if (process.platform === "win32") { + this.skip(); + } + slowTest(this); getControl().slaveUpdates = []; let config = "alt-slave-config.json"; From 5ab4b95e3cf4b5fccfcd471c8b59b0c3b369be5b Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 21 Jun 2021 14:54:56 +0200 Subject: [PATCH 2/3] Support non-standard zip mime types in save upload Browser like Chrome doesn't use the application/zip mime type when uploading .zip files. Add support for application/x-zip-compressed to the save upload endpoint. --- packages/master/src/routes.js | 9 +++++++-- test/master/routes.js | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/master/src/routes.js b/packages/master/src/routes.js index 0843f79a..b6d7f741 100644 --- a/packages/master/src/routes.js +++ b/packages/master/src/routes.js @@ -259,6 +259,11 @@ async function getStream(req, res) { } +const zipMimes = [ + "application/zip", + "application/x-zip-compressed", +]; + async function uploadSave(req, res) { try { res.locals.user.checkPermission("core.instance.save.upload"); @@ -319,7 +324,7 @@ async function uploadSave(req, res) { requestErrors.push("instance_id must come before files uploaded"); } - if (fileMime !== "application/zip") { + if (!zipMimes.includes(fileMime)) { requestErrors.push("invalid file Content-Type"); } @@ -350,7 +355,7 @@ async function uploadSave(req, res) { req.pipe(busboy); }); - } else if (mimeType === "application/zip") { + } else if (zipMimes.includes(mimeType)) { let filename = req.query.filename; if (typeof filename !== "string") { requestErrors.push("Missing or invalid filename parameter"); diff --git a/test/master/routes.js b/test/master/routes.js index 7037be75..cca2c3b7 100644 --- a/test/master/routes.js +++ b/test/master/routes.js @@ -325,5 +325,25 @@ describe("master/src/routes", function() { assert.deepEqual(JSON.parse(response.body), { saves: ["test.zip"] }); assert.equal(response.statusCode, 200); }); + it("should complete a valid transfer with non-standerd mime type", async function() { + master.forwardRequestToInstance = async (request, data) => { + let stream = await phin({ + url: `http://localhost:${port}/api/stream/${data.stream_id}`, + }); + assert.equal(stream.body.toString(), "totally a zip file"); + return { save: data.filename }; + }; + let response; + response = await phin({ + url: `http://localhost:${port}/api/upload-save?instance_id=123&filename=test.zip`, method: "POST", + headers: { + "X-Access-Token": testToken, + "Content-Type": "application/x-zip-compressed", + }, + data: "totally a zip file", + }); + assert.deepEqual(JSON.parse(response.body), { saves: ["test.zip"] }); + assert.equal(response.statusCode, 200); + }); }); }); From ea77e09058912b389b22493bbd905dd430c65472 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 21 Jun 2021 16:35:36 +0200 Subject: [PATCH 3/3] Show upload progress Add progress bar for file uploads in progress and notify user if the upload fails. --- packages/web_ui/src/components/SavesList.jsx | 47 +++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/web_ui/src/components/SavesList.jsx b/packages/web_ui/src/components/SavesList.jsx index 448577c1..bd5eb6a4 100644 --- a/packages/web_ui/src/components/SavesList.jsx +++ b/packages/web_ui/src/components/SavesList.jsx @@ -1,5 +1,5 @@ import React, { useContext, useState } from "react"; -import { Button, Popconfirm, Space, Table, Tooltip, Upload } from "antd"; +import { Button, List, Popconfirm, Progress, Space, Table, Tooltip, Upload } from "antd"; import CaretLeftOutlined from "@ant-design/icons/CaretLeftOutlined"; import LeftOutlined from "@ant-design/icons/LeftOutlined"; @@ -27,6 +27,7 @@ export default function SavesList(props) { let control = useContext(ControlContext); let saves = useSaves(props.instance.id); let [starting, setStarting] = useState(false); + let [uploadingFiles, setUploadingFiles] = useState([]); const saveTable = ; + function onChange(changeEvent) { + if (["done", "error"].includes(changeEvent.file.status)) { + if (changeEvent.file.status === "error") { + notifyErrorHandler("Error uploading file")(changeEvent.file.error); + } + + let newUploadingFiles = [...uploadingFiles]; + let index = newUploadingFiles.findIndex(f => f.uid === changeEvent.file.uid); + if (index !== -1) { + newUploadingFiles.splice(index, 1); + setUploadingFiles(newUploadingFiles); + } + return; + } + + let file = { + uid: changeEvent.file.uid, + name: changeEvent.file.name, + percent: changeEvent.file.percent, + status: changeEvent.file.status, + }; + + let newUploadingFiles = [...uploadingFiles]; + let index = newUploadingFiles.findIndex(f => f.uid === changeEvent.file.uid); + if (index !== -1) { + newUploadingFiles[index] = file; + } else { + newUploadingFiles.push(file); + } + setUploadingFiles(newUploadingFiles); + } + let uploadProps = { name: "file", accept: ".zip", @@ -118,7 +151,7 @@ export default function SavesList(props) { }, showUploadList: false, action: `${webRoot}api/upload-save`, - // TODO: show progress to user. + onChange, }; return
@@ -131,5 +164,15 @@ export default function SavesList(props) { {saveTable} + {uploadingFiles.length ? + {uploadingFiles.map(file => + {file.name} + `${Math.floor(percent)}%`} + status={file.status === "error" ? "exception" : "normal"} + /> + )} + : null}
; }