From 68b8ac6b8cf8476768a00fa622e22ccec26ea365 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 10 Feb 2022 10:29:47 +0100 Subject: [PATCH 1/8] Add onMasterConfigFieldChanged hook Add hook for master plugin code to react to the master config changing. --- packages/lib/plugin.js | 13 +++++++++++++ packages/master/src/Master.js | 3 +++ test/lib/plugin.js | 2 ++ 3 files changed, 18 insertions(+) diff --git a/packages/lib/plugin.js b/packages/lib/plugin.js index 796b9b6c..9d4e7e6f 100644 --- a/packages/lib/plugin.js +++ b/packages/lib/plugin.js @@ -355,6 +355,19 @@ class BaseMasterPlugin { */ async onInstanceStatusChanged(instance, prev) { } + /** + * Called when the value of a master config field changed. + * + * Invoked after the value of the config field given by `field` has + * changed on the master. + * + * @param {module:lib/config.ConfigGroup} group - + * The group who's field got changed on. + * @param {string} field - Name of the field that changed. + * @param {*} prev - The previous value of the field. + */ + async onMasterConfigFieldChanged(group, field, prev) { } + /** * Called when the value of an instance config field changed. * diff --git a/packages/master/src/Master.js b/packages/master/src/Master.js index 28809002..3d83e849 100644 --- a/packages/master/src/Master.js +++ b/packages/master/src/Master.js @@ -162,6 +162,9 @@ class Master { this.userManager = new UserManager(this.config); await this.userManager.load(path.join(databaseDirectory, "users.json")); + this.config.on("fieldChanged", (group, field, prev) => { + libPlugin.invokeHook(this.plugins, "onMasterConfigFieldChanged", group, field, prev); + }); for (let instance of this.instances.values()) { this.addInstanceHooks(instance); } diff --git a/test/lib/plugin.js b/test/lib/plugin.js index 55159f4f..017385ae 100644 --- a/test/lib/plugin.js +++ b/test/lib/plugin.js @@ -62,6 +62,8 @@ describe("lib/plugin", function() { }); it("should define defaults for hooks", async function() { await masterPlugin.onInstanceStatusChanged({}, "running", "initialized"); + await masterPlugin.onMasterConfigFieldChanged({}, "foo", null); + await masterPlugin.onInstanceConfigFieldChanged({}, {}, "foo", null); await masterPlugin.onMetrics(); await masterPlugin.onShutdown(); masterPlugin.onSlaveConnectionEvent({}, "connect"); From 5319ff39070db0f37cf9eb45d622e799f351abec Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 10 Feb 2022 10:31:25 +0100 Subject: [PATCH 2/8] Add hook to add extra content to Web UI pages Allow plugins to extend Web UI pages provided by Clusterio by adding components in the componentExtra property of the plugin. --- packages/lib/plugin.js | 36 +++++++++++++++++++ .../src/components/InstanceViewPage.jsx | 3 ++ .../web_ui/src/components/InstancesPage.jsx | 2 ++ packages/web_ui/src/components/MasterPage.jsx | 4 ++- .../web_ui/src/components/PluginExtra.jsx | 17 +++++++++ .../web_ui/src/components/RoleViewPage.jsx | 2 ++ packages/web_ui/src/components/RolesPage.jsx | 2 ++ .../web_ui/src/components/SlaveViewPage.jsx | 2 ++ packages/web_ui/src/components/SlavesPage.jsx | 2 ++ .../web_ui/src/components/UserViewPage.jsx | 2 ++ packages/web_ui/src/components/UsersPage.jsx | 2 ++ 11 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 packages/web_ui/src/components/PluginExtra.jsx diff --git a/packages/lib/plugin.js b/packages/lib/plugin.js index 9d4e7e6f..01be7c82 100644 --- a/packages/lib/plugin.js +++ b/packages/lib/plugin.js @@ -653,6 +653,42 @@ class BaseWebPlugin { * @type {Array} */ this.pages = []; + + /** + * Extra react component to add to core components + * + * Interface to augment core components of the web UI. Setting a + * component as one of the supported properties of this object will + * cause the web UI to render it when displaying that component, + * usually at the end. Each component will receive a `plugin` param + * which is the instance of the web plugin that contained the + * component extra. + * + * @type {object} + * @property {React.ComponentType} MasterPage - + * Placed at the end of the master page. + * @property {React.ComponentType} SlavesPage - + * Placed at the end of the slaves list page. + * @property {React.ComponentType} SlaveViewPage - + * Placed at the end of each slave page. Takes a `slave` param + * which is the slave the page is displayed for. + * @property {React.ComponentType} InstancesPage - + * Placed at the end of the instance list page. + * @property {React.ComponentType} InstanceViewPage - + * Placed at the end of each instance page. Takes an `instance` + * param which is the instance the page is displayed for. + * @property {React.ComponentType} UsersPage - + * Placed at the end of the users list page. + * @property {React.ComponentType} UserViewPage - + * Placed at the end of each user page. Takes a `user` param + * which is the user object the page is displayed for. + * @property {React.ComponentType} RolesPage - + * Placed at the end of the roles list page. + * @property {React.ComponentType} RoleViewPage - + * Placed at the end of each role page. Takes a `role` param + * which is the role object the page is displayed for. + */ + this.componentExtra = {}; } /** diff --git a/packages/web_ui/src/components/InstanceViewPage.jsx b/packages/web_ui/src/components/InstanceViewPage.jsx index c56be6bd..0ad1031e 100644 --- a/packages/web_ui/src/components/InstanceViewPage.jsx +++ b/packages/web_ui/src/components/InstanceViewPage.jsx @@ -8,6 +8,7 @@ import { libLink } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; import InstanceConfigTree from "./InstanceConfigTree"; import LogConsole from "./LogConsole"; import InstanceRcon from "./InstanceRcon"; @@ -147,5 +148,7 @@ export default function InstanceViewPage(props) { } {account.hasPermission("core.instance.get_config") && } + + ; } diff --git a/packages/web_ui/src/components/InstancesPage.jsx b/packages/web_ui/src/components/InstancesPage.jsx index 3195c439..f8a988eb 100644 --- a/packages/web_ui/src/components/InstancesPage.jsx +++ b/packages/web_ui/src/components/InstancesPage.jsx @@ -7,6 +7,7 @@ import { libConfig, libLink } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; import { useInstanceList } from "../model/instance"; import InstanceList from "./InstanceList"; import { notifyErrorHandler } from "../util/notify"; @@ -94,5 +95,6 @@ export default function InstancesPage() { /> + ; } diff --git a/packages/web_ui/src/components/MasterPage.jsx b/packages/web_ui/src/components/MasterPage.jsx index 8ba92848..b3637238 100644 --- a/packages/web_ui/src/components/MasterPage.jsx +++ b/packages/web_ui/src/components/MasterPage.jsx @@ -1,6 +1,7 @@ import React from "react"; import { Typography } from "antd"; +import PluginExtra from "./PluginExtra"; import LogConsole from "./LogConsole"; import { useAccount } from "../model/account"; import MasterConfigTree from "./MasterConfigTree"; @@ -9,7 +10,7 @@ import PageLayout from "./PageLayout"; const { Title } = Typography; -export default function SlavesPage() { +export default function MasterPage() { let account = useAccount(); return @@ -19,5 +20,6 @@ export default function SlavesPage() { } {account.hasPermission("core.master.get_config") && } + ; }; diff --git a/packages/web_ui/src/components/PluginExtra.jsx b/packages/web_ui/src/components/PluginExtra.jsx new file mode 100644 index 00000000..d862b856 --- /dev/null +++ b/packages/web_ui/src/components/PluginExtra.jsx @@ -0,0 +1,17 @@ +import React, { useContext } from "react"; + +import ControlContext from "./ControlContext"; + + +export default function PluginExtra(params) { + let control = useContext(ControlContext); + + let components = []; + for (let [name, plugin] of control.plugins) { + if (Object.prototype.hasOwnProperty.call(plugin.componentExtra, params.component)) { + let ComponentExtra = plugin.componentExtra[params.component]; + components.push(); + } + } + return components; +} diff --git a/packages/web_ui/src/components/RoleViewPage.jsx b/packages/web_ui/src/components/RoleViewPage.jsx index ddd7a9f0..892beca6 100644 --- a/packages/web_ui/src/components/RoleViewPage.jsx +++ b/packages/web_ui/src/components/RoleViewPage.jsx @@ -8,6 +8,7 @@ import { libLink, libUsers } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; import { notifyErrorHandler } from "../util/notify"; @@ -149,5 +150,6 @@ export default function RoleViewPage() { ))} + ; } diff --git a/packages/web_ui/src/components/RolesPage.jsx b/packages/web_ui/src/components/RolesPage.jsx index fc1ba59a..60221ebf 100644 --- a/packages/web_ui/src/components/RolesPage.jsx +++ b/packages/web_ui/src/components/RolesPage.jsx @@ -8,6 +8,7 @@ import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import { notifyErrorHandler } from "../util/notify"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare; @@ -99,5 +100,6 @@ export default function RolesPage() { }, })} /> + ; } diff --git a/packages/web_ui/src/components/SlaveViewPage.jsx b/packages/web_ui/src/components/SlaveViewPage.jsx index 12544ad0..14cdf423 100644 --- a/packages/web_ui/src/components/SlaveViewPage.jsx +++ b/packages/web_ui/src/components/SlaveViewPage.jsx @@ -8,6 +8,7 @@ import { useAccount } from "../model/account"; import { useInstanceList } from "../model/instance"; import { useSlave } from "../model/slave"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; const { Title } = Typography; @@ -60,5 +61,6 @@ export default function SlaveViewPage(props) { Console } + ; } diff --git a/packages/web_ui/src/components/SlavesPage.jsx b/packages/web_ui/src/components/SlavesPage.jsx index 24cb17ed..77e2fd9c 100644 --- a/packages/web_ui/src/components/SlavesPage.jsx +++ b/packages/web_ui/src/components/SlavesPage.jsx @@ -7,6 +7,7 @@ import { libLink } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; import { useSlaveList } from "../model/slave"; import { notifyErrorHandler } from "../util/notify"; @@ -129,5 +130,6 @@ export default function SlavesPage() { }, })} /> + ; }; diff --git a/packages/web_ui/src/components/UserViewPage.jsx b/packages/web_ui/src/components/UserViewPage.jsx index 4a9b2f3b..f2730fa4 100644 --- a/packages/web_ui/src/components/UserViewPage.jsx +++ b/packages/web_ui/src/components/UserViewPage.jsx @@ -8,6 +8,7 @@ import { libLink } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; import { notifyErrorHandler } from "../util/notify"; @@ -258,5 +259,6 @@ export default function UserViewPage() { + ; } diff --git a/packages/web_ui/src/components/UsersPage.jsx b/packages/web_ui/src/components/UsersPage.jsx index 296fd1ce..397db3d9 100644 --- a/packages/web_ui/src/components/UsersPage.jsx +++ b/packages/web_ui/src/components/UsersPage.jsx @@ -8,6 +8,7 @@ import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import { notifyErrorHandler } from "../util/notify"; import PageLayout from "./PageLayout"; +import PluginExtra from "./PluginExtra"; const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare; @@ -121,5 +122,6 @@ export default function UsersPage() { }, })} /> + ; } From a93f04881480188ba83c6197015bf5061510b518 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 10 Feb 2022 13:43:11 +0100 Subject: [PATCH 3/8] Add support for sidebar groups Allow sidebar entries to be grouped together by specifiying a name under which they will be grouped. To avoid confusion groups are rendered at the bottom of the list. --- packages/lib/plugin.js | 5 +++- packages/web_ui/src/components/SiteLayout.jsx | 29 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/lib/plugin.js b/packages/lib/plugin.js index 01be7c82..a0d94805 100644 --- a/packages/lib/plugin.js +++ b/packages/lib/plugin.js @@ -588,11 +588,14 @@ class BaseControlPlugin { /** * Plugin supplied pages - * @typedef {Object} module:Lib/plugin~Page + * @typedef {Object} module:lib/plugin~Page * @property {string} path - URL path to this page. * @property {string=} sidebarPath - * If present and this path matches one of the pages in the sidebar it * will cause that sidebar entry to be highlighted as active. + * @property {string=} sidebarGroup - + * If present group this entry under a group of the given name in the + * sidebar. * @property {string=} sidebarName - * If present creates an entry in the sidebar for this page with the * given text. diff --git a/packages/web_ui/src/components/SiteLayout.jsx b/packages/web_ui/src/components/SiteLayout.jsx index 089b605d..5a547205 100644 --- a/packages/web_ui/src/components/SiteLayout.jsx +++ b/packages/web_ui/src/components/SiteLayout.jsx @@ -37,6 +37,27 @@ export default function SiteLayout(props) { combinedPages.push(...plugin.pages); } + let menuItems = []; + let menuGroups = new Map(); + for (let { sidebarName, sidebarGroup, permission, path } of combinedPages) { + if (!sidebarName || permission && !account.hasPermission(permission)) { + continue; + } + if (sidebarGroup) { + let group = menuGroups.get(sidebarGroup); + if (!group) { + group = []; + menuGroups.set(sidebarGroup, group); + } + group.push({sidebarName}); + } else { + menuItems.push({sidebarName}); + } + } + for (let [name, group] of menuGroups) { + menuItems.push({group}); + } + return
@@ -65,16 +86,12 @@ export default function SiteLayout(props) { > history.push(key)} > - {combinedPages.map(({sidebarName, permission, path}) => { - if (!sidebarName || permission && !account.hasPermission(permission)) { - return null; - } - return {sidebarName}; - })} + {menuItems} From 1e4d709a0644b25c5ce2cbd9aa7cdb7eec61a85b Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 25 Mar 2022 18:07:14 +0100 Subject: [PATCH 4/8] Implement kill instance Add ability to kill running instances in case it is desirable to stop the server without saving over the current save file. --- docs/managing-a-cluster.md | 9 ++ packages/ctl/ctl.js | 13 ++ packages/lib/factorio/server.js | 43 +++-- packages/lib/link/messages.js | 7 + packages/lib/users.js | 5 + packages/slave/slave.js | 17 +- .../src/components/InstanceViewPage.jsx | 148 +++++++++++------- test/integration/clusterio.js | 34 ++-- 8 files changed, 186 insertions(+), 90 deletions(-) diff --git a/docs/managing-a-cluster.md b/docs/managing-a-cluster.md index 205a548d..dca36cff 100644 --- a/docs/managing-a-cluster.md +++ b/docs/managing-a-cluster.md @@ -42,6 +42,15 @@ List instances. Create a new instance. `name` is the instance name. `--id` is the optional id. +### Kill instance + + ctl> instance kill + +Terminates the running Factorio server without giving it a chance to save or cleanup after itself. + +Note: This may cause loss of data. + + ### Config management #### List Configuration diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 9e48860f..b4cc3e5d 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -755,6 +755,19 @@ instanceCommands.add(new libCommand.Command({ }, })); +instanceCommands.add(new libCommand.Command({ + definition: ["kill ", "Kill instance", (yargs) => { + yargs.positional("instance", { describe: "Instance to kill", type: "string" }); + }], + handler: async function(args, control) { + let instanceId = await libCommand.resolveInstance(control, args.instance); + await control.setLogSubscriptions({ instance_ids: [instanceId] }); + await libLink.messages.killInstance.send(control, { + instance_id: instanceId, + }); + }, +})); + instanceCommands.add(new libCommand.Command({ definition: ["delete ", "Delete instance", (yargs) => { yargs.positional("instance", { describe: "Instance to delete", type: "string" }); diff --git a/packages/lib/factorio/server.js b/packages/lib/factorio/server.js index 60070550..787b32d2 100644 --- a/packages/lib/factorio/server.js +++ b/packages/lib/factorio/server.js @@ -487,6 +487,7 @@ class FactorioServer extends events.EventEmitter { // Array of possible causes for an unexpected shutdown this._unexpected = []; + this._killed = false; // Track autosaving this._runningAutosave = null; @@ -694,28 +695,30 @@ class FactorioServer extends events.EventEmitter { _watchExit() { this._server.on("exit", (code, signal) => { if (this._state !== "stopping") { - if (this._unexpected.length === 0) { - if (signal === "SIGKILL") { + if (signal === "SIGKILL") { + if (this._killed) { + this.emit("error", new libErrors.EnvironmentError("Factorio server was killed")); + } else { this.emit("error", new libErrors.EnvironmentError( "Factorio server was unexpectedly killed, is the system low on memory?" )); - - } else { - let msg; - if (code !== null) { - msg = `Factorio server unexpectedly shut down with code ${code}`; - } else { - msg = `Factorio server was unexpectedly shut by down by signal ${signal}`; - } - - this.emit("error", new Error(msg)); } + } else if (this._unexpected.length === 0) { + let msg; + if (code !== null) { + msg = `Factorio server unexpectedly shut down with code ${code}`; + } else { + msg = `Factorio server was unexpectedly shut by down by signal ${signal}`; + } + + this.emit("error", new libErrors.EnvironmentError(msg)); + } else if (this._unexpected.length === 1) { - this.emit("error", new Error(this._unexpected[0])); + this.emit("error", new libErrors.EnvironmentError(this._unexpected[0])); } else { - this.emit("error", new Error( + this.emit("error", new libErrors.EnvironmentError( "Factorio server unexpectedly shut down. Possible causes:"+ `\n- ${this._unexpected.join("\n- ")}` )); @@ -1004,12 +1007,18 @@ class FactorioServer extends events.EventEmitter { * Kill the server * * Terminates the server without any cleanup or saving. + * @param {boolean} [unexpected=false] - + * If true raise an error event as a result of killing the Factorio + * process. */ - async kill() { + async kill(unexpected = false) { this._check(["running", "stopping"]); - this._state = "stopping"; + this._killed = true; + if (!unexpected) { + this._state = "stopping"; + } this._server.kill("SIGKILL"); - await events.once(this._server, "exit"); + await new Promise(resolve => this._server.once("exit", resolve)); } /** diff --git a/packages/lib/link/messages.js b/packages/lib/link/messages.js index ea3d0aae..06135ff4 100644 --- a/packages/lib/link/messages.js +++ b/packages/lib/link/messages.js @@ -636,6 +636,13 @@ messages.stopInstance = new Request({ forwardTo: "instance", }); +messages.killInstance = new Request({ + type: "kill_instance", + links: ["control-master", "master-slave", "slave-instance"], + permission: "core.instance.kill", + forwardTo: "instance", +}); + messages.deleteInstance = new Request({ type: "delete_instance", links: ["control-master", "master-slave"], diff --git a/packages/lib/users.js b/packages/lib/users.js index 4b42425b..f5f59f1a 100644 --- a/packages/lib/users.js +++ b/packages/lib/users.js @@ -427,6 +427,11 @@ definePermission({ title: "Stop instance", description: "Stop instances.", }); +definePermission({ + name: "core.instance.kill", + title: "Kill instance", + description: "Terminate running instances without saving or cleanup.", +}); definePermission({ name: "core.instance.delete", title: "Delete instance", diff --git a/packages/slave/slave.js b/packages/slave/slave.js index cbcb0098..ebdf00c4 100755 --- a/packages/slave/slave.js +++ b/packages/slave/slave.js @@ -186,7 +186,11 @@ class Instance extends libLink.Link { }); this.server.on("error", err => { - this.logger.error(`Error in instance ${this.name}:\n${err.stack}`); + if (err instanceof libErrors.EnvironmentError) { + this.logger.error(err.message); + } else { + this.logger.error(`${this.name}:\n${err.stack}`); + } }); this.server.on("autosave-finished", name => { @@ -697,6 +701,13 @@ class Instance extends libLink.Link { } } + async kill() { + if (this._status === "stopped") { + return; + } + await this.server.kill(true); + } + async masterConnectionEventEventHandler(message) { await libPlugin.invokeHook(this.plugins, "onMasterConnectionEvent", message.data.event); } @@ -833,6 +844,10 @@ class Instance extends libLink.Link { await this.stop(); } + async killInstanceRequestHandler() { + await this.kill(); + } + async sendRconRequestHandler(message) { let result = await this.sendRcon(message.data.command); return { result }; diff --git a/packages/web_ui/src/components/InstanceViewPage.jsx b/packages/web_ui/src/components/InstanceViewPage.jsx index 0ad1031e..3b3e26c0 100644 --- a/packages/web_ui/src/components/InstanceViewPage.jsx +++ b/packages/web_ui/src/components/InstanceViewPage.jsx @@ -1,7 +1,8 @@ import React, { useContext, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; -import { Alert, Button, Descriptions, Popconfirm, Space, Spin, Typography } from "antd"; +import { Alert, Button, Descriptions, Dropdown, Menu, Modal, PageHeader, Space, Spin, Typography } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; +import DownOutlined from "@ant-design/icons/DownOutlined"; import { libLink } from "@clusterio/lib"; @@ -24,6 +25,36 @@ import InstanceStatusTag from "./InstanceStatusTag"; const { Title } = Typography; +function InstanceDescription(props) { + let account = useAccount(); + + const { slave, instance } = props; + let assigned = instance["assigned_slave"] !== null; + return + + {!assigned + ? Unassigned + : slave["name"] || instance["assigned_slave"] + } + {account.hasPermission("core.instance.assign") && } + + + ; +} + export default function InstanceViewPage(props) { let params = useParams(); let instanceId = Number(params.id); @@ -61,16 +92,9 @@ export default function InstanceViewPage(props) { ; } - let instanceButtons = - { - account.hasAnyPermission("core.instance.start", "core.instance.stop") - && - } - {account.hasPermission("core.instance.load_scenario") && } - {account.hasPermission("core.instance.export_data") && } - {account.hasPermission("core.instance.delete") && { - libLink.messages.deleteInstance.send( + + } else if (key === "kill") { + libLink.messages.killInstance.send( control, { instance_id: instanceId } - ).then(() => { - history.push("/instances"); - }).catch(notifyErrorHandler("Error deleting instance")); - }} - > - - } + danger + key="delete" + icon={} + >Delete + } + ; + let instanceButtons = + { + account.hasAnyPermission("core.instance.start", "core.instance.stop") + && + } + {account.hasPermission("core.instance.load_scenario") && } + {account.hasAnyPermission( + "core.instance.export_data", + "core.instance.kill", + "core.instance.delete", + ) && + + } ; - let assigned = instance["assigned_slave"] !== null; return - - - {!assigned - ? Unassigned - : slave["name"] || instance["assigned_slave"] - } - {account.hasPermission("core.instance.assign") && } - - - + /> + { account.hasAllPermission("core.instance.save.list", "core.instance.save.list_subscribe") diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 072f0180..ee8ea515 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -481,22 +481,26 @@ describe("Integration of Clusterio", function() { await execCtl("instance config set 44 research_sync.load_plugin false"); await execCtl("instance config set 44 statistics_exporter.load_plugin false"); await execCtl("instance config set 44 subspace_storage.load_plugin false"); - try { - let exchangeString = testStrings.modified.replace(/[\n\r]+/g, ""); - let args = `base/freeplay --seed 1234 --map-exchange-string "${exchangeString}"`; - await execCtl(`instance load-scenario test ${args}`); - await checkInstanceStatus(44, "running"); - await sendRcon(44, '/c game.print("disable achievements")'); - await sendRcon(44, '/c game.print("disable achievements")'); - assert.equal(await sendRcon(44, "/c rcon.print(game.default_map_gen_settings.seed)"), "1234\n"); - assert.equal(await sendRcon(44, "/c rcon.print(game.map_settings.pollution.ageing)"), "1.5\n"); - assert.equal( - await sendRcon(44, "/c rcon.print(game.difficulty_settings.research_queue_setting)"), "never\n" - ); - } finally { - await execCtl("instance stop test"); - } + let exchangeString = testStrings.modified.replace(/[\n\r]+/g, ""); + let args = `base/freeplay --seed 1234 --map-exchange-string "${exchangeString}"`; + await execCtl(`instance load-scenario test ${args}`); + await checkInstanceStatus(44, "running"); + await sendRcon(44, '/c game.print("disable achievements")'); + await sendRcon(44, '/c game.print("disable achievements")'); + assert.equal(await sendRcon(44, "/c rcon.print(game.default_map_gen_settings.seed)"), "1234\n"); + assert.equal(await sendRcon(44, "/c rcon.print(game.map_settings.pollution.ageing)"), "1.5\n"); + assert.equal( + await sendRcon(44, "/c rcon.print(game.difficulty_settings.research_queue_setting)"), "never\n" + ); + }); + }); + + describe("instance kill", function() { + it("kills the instance", async function() { + slowTest(this); + await execCtl("instance kill test"); + await checkInstanceStatus(44, "stopped"); }); }); From e5a63a3508d309bb1f4ee9a6296789797e00d783 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 25 Mar 2022 18:45:28 +0100 Subject: [PATCH 5/8] Add start-stop button to instance list --- .../web_ui/src/components/InstanceList.jsx | 19 ++++++++++++++++--- .../components/StartStopInstanceButton.jsx | 3 ++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/web_ui/src/components/InstanceList.jsx b/packages/web_ui/src/components/InstanceList.jsx index 7163608a..19f17b32 100644 --- a/packages/web_ui/src/components/InstanceList.jsx +++ b/packages/web_ui/src/components/InstanceList.jsx @@ -1,10 +1,11 @@ import React from "react"; import { useHistory } from "react-router-dom"; -import { message, Button, Table } from "antd"; +import { message, Button, Space, Table } from "antd"; import CopyOutlined from "@ant-design/icons/CopyOutlined"; import { useSlaveList } from "../model/slave"; import InstanceStatusTag from "./InstanceStatusTag"; +import StartStopInstanceButton from "./StartStopInstanceButton"; const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare; @@ -43,6 +44,7 @@ export default function InstanceList(props) { key: "assigned_slave", render: instance => slaveName(instance["assigned_slave"]), sorter: (a, b) => strcmp(slaveName(a["assigned_slave"]), slaveName(b["assigned_slave"])), + responsive: ["sm"], }, { title: "Public address", @@ -63,13 +65,24 @@ export default function InstanceList(props) { : ""; }, sorter: (a, b) => strcmp(slavePublicAddress(a["assigned_slave"]), slavePublicAddress(b["assigned_slave"])), + responsive: ["lg"], }, { title: "Status", - dataIndex: "status", - render: status => , + key: "status", + render: instance => , sorter: (a, b) => strcmp(a["status"], b["status"]), }, + { + key: "action", + render: instance => , + responsive: ["sm"], + align: "right", + width: 100, + }, ]; if (props.hideAssignedSlave) { diff --git a/packages/web_ui/src/components/StartStopInstanceButton.jsx b/packages/web_ui/src/components/StartStopInstanceButton.jsx index 89a58eac..00def970 100644 --- a/packages/web_ui/src/components/StartStopInstanceButton.jsx +++ b/packages/web_ui/src/components/StartStopInstanceButton.jsx @@ -11,7 +11,8 @@ export default function StartStopInstanceButton(props) { let control = useContext(ControlContext); let [switching, setSwitching] = useState(false); - function onClick() { + function onClick(event) { + event.stopPropagation(); setSwitching(true); let action; if (props.instance["status"] === "stopped") { From ce95f868fd776d329c6949cad91a62c888670041 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 25 Mar 2022 18:52:15 +0100 Subject: [PATCH 6/8] Fix curently typo --- README.md | 2 +- packages/slave/modules/clusterio/serialize.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f508d3a..2017c1be 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Otherwise see below for OS specific instructions. **Ubuntu with Docker** -The Docker support for Clusterio is curently broken. +The Docker support for Clusterio is currently broken. If you need it open an issue about it.