Add delete save feature

Add command and web interface button to delete saves.
This commit is contained in:
Hornwitser committed 2021-06-01 16:32:58 +02:00
1 parent 2e314ff098
commit 35f54a0ee8
6 files changed
+96 -10

No files matched your search

+14
View File
@@ -429,6 +429,20 @@ instanceCommands.add(new libCommand.Command({
},
}));
instanceCommands.add(new libCommand.Command({
definition: ["delete-save <instance> <save>", "Delete a save from an instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to delete save from", type: "string" });
yargs.positional("save", { describe: "Save to delete", type: "string" });
}],
handler: async function(args, control) {
let instanceId = await libCommand.resolveInstance(control, args.instance);
await libLink.messages.deleteSave.send(control, {
instance_id: instanceId,
save: args.save,
});
},
}));
instanceCommands.add(new libCommand.Command({
definition: ["export-data <instance>", "Export item icons and locale from instance", (yargs) => {
yargs.positional("instance", { describe: "Instance to export from", type: "string" });
+10
View File
@@ -533,6 +533,16 @@ messages.createSave = new Request({
},
});
messages.deleteSave = new Request({
type: "delete_save",
links: ["control-master", "master-slave"],
permission: "core.instance.save.delete",
forwardTo: "instance",
requestProperties: {
"save": { type: "string" },
},
});
messages.pullSave = new Request({
type: "pull_save",
links: ["master-slave"],
+5
View File
@@ -381,6 +381,11 @@ definePermission({
title: "Create new instance save",
description: "Create new savegames on instances.",
});
definePermission({
name: "core.instance.save.delete",
title: "Delete instance save",
description: "Delete savegames on instances.",
});
definePermission({
name: "core.instance.save.upload",
title: "Upload save to instance",
+36 -9
View File
@@ -1375,6 +1375,41 @@ class Slave extends libLink.Link {
await request.send(instanceConnection, message.data);
}
async sendSaveListUpdate(instance_id, savesDir) {
let instanceConnection = this.instanceConnections.get(instance_id);
let saveList;
if (instanceConnection) {
saveList = (await libLink.messages.listSaves.send(instanceConnection, { instance_id })).list;
} else {
saveList = await Instance.listSaves(savesDir, null);
}
libLink.messages.saveListUpdate.send(this, { instance_id, list: saveList });
}
async deleteSaveRequestHandler(message) {
let { instance_id, save } = message.data;
try {
checkFilename(save);
} catch (err) {
throw new libErrors.RequestError(`Save name ${err.message}`);
}
let instanceInfo = this.instanceInfos.get(instance_id);
if (!instanceInfo) {
throw new libErrors.RequestError(`Instance with ID ${instance_id} does not exist`);
}
try {
await fs.unlink(path.join(instanceInfo.path, "saves", save));
} catch (err) {
if (err.code === "ENOENT") {
throw new libErrors.RequestError(`${save} does not exist`);
}
throw err;
}
await this.sendSaveListUpdate(instance_id, path.join(instanceInfo.path, "saves"));
}
async pullSaveRequestHandler(message) {
let { instance_id, stream_id, filename } = message.data;
try {
@@ -1422,15 +1457,7 @@ class Slave extends libLink.Link {
filename = await libFileOps.findUnusedName(savesDir, filename, ".zip");
await fs.rename(path.join(savesDir, tempFilename), path.join(savesDir, filename));
let instanceConnection = this.instanceConnections.get(instance_id);
let saveList;
if (instanceConnection) {
saveList = (await libLink.messages.listSaves.send(instanceConnection, { instance_id })).list;
} else {
saveList = await Instance.listSaves(savesDir, null);
}
libLink.messages.saveListUpdate.send(this, { instance_id, list: saveList });
await this.sendSaveListUpdate(instance_id, savesDir);
return { save: filename };
}
+14 -1
View File
@@ -1,5 +1,5 @@
import React, { useContext, useState } from "react";
import { Button, Space, Table, Tooltip, Upload } from "antd";
import { Button, Popconfirm, Space, Table, Tooltip, Upload } from "antd";
import CaretLeftOutlined from "@ant-design/icons/CaretLeftOutlined";
import LeftOutlined from "@ant-design/icons/LeftOutlined";
@@ -77,6 +77,19 @@ export default function SavesList(props) {
);
}}
>Load save</Button>
<Popconfirm
title="Permanently delete save?"
okText="Delete"
placement="top"
okButtonProps={{ danger: true }}
onConfirm={() => {
libLink.messages.deleteSave.send(
control, { instance_id: props.instance.id, save: save.name }
).catch(notifyErrorHandler("Error deleting save"));
}}
>
<Button danger>Delete</Button>
</Popconfirm>
</Space>,
}}
/>;
+17
View File
@@ -377,6 +377,23 @@ describe("Integration of Clusterio", function() {
});
});
describe("instance delete-save", function() {
it("should delete a save", async function() {
await execCtl("instance delete-save 44 upload.zip");
assert(
!await fs.pathExists(path.join("temp", "test", "instances", "test", "saves", "upload.zip")),
"file not deleted"
);
});
it("should error if save does not exist", async function() {
await assert.rejects(execCtl("instance delete-save 44 upload.zip"));
});
it("should error on path traversal attacks", async function() {
await fs.outputFile(path.join("temp", "test", "upload.zip"), "a test");
await assert.rejects(execCtl("instance delete-save 44 ../../upload.zip"));
});
});
describe("instance delete", function() {
it("deletes the instance", async function() {
slowTest(this);