From 0c554cc754fe47560c0e3636fd6d5e75a7d47184 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 17:45:24 +0100 Subject: [PATCH 01/14] Use double quotes in documentation code Be consistent with the project code style which requires the use of double quotes in code. --- docs/writing-plugins.md | 60 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/writing-plugins.md b/docs/writing-plugins.md index de648946..a3d18512 100644 --- a/docs/writing-plugins.md +++ b/docs/writing-plugins.md @@ -55,7 +55,7 @@ clusterioctl. Note that it's important that the path starts with ./ or For a plugin the most important file is the `info.js` file. Without it the plugin will not recognized by Clusterio. Here's an example of it: - const libLink = require('@clusterio/lib/link'); // For messages + const libLink = require("@clusterio/lib/link"); // For messages module.exports = { name: "foo_frobber", @@ -136,7 +136,7 @@ The plugin class should derive from its respective base class defined in `lib/plugin`. For example, to define a MasterPlugin class the following code can be used: - const libPlugin = require('@clusterio/lib/plugin'); + const libPlugin = require("@clusterio/lib/plugin"); class MasterPlugin extends libPlugin.BaseMasterPlugin { async init() { @@ -257,7 +257,7 @@ plugin you listen for an event named `ipc-channel_name` in order to get data sent by `send_json`. For example in the plugin code: async init() { - this.instance.server.on('ipc-my_plugin_foo', content => + this.instance.server.on("ipc-my_plugin_foo", content => this.handleFoo(content).catch(err => console.log( "Error handling foo:", foo )) @@ -270,10 +270,10 @@ data sent by `send_json`. For example in the plugin code: And then in the module for the plugin: - local clusterio_api = require('modules/clusterio/api') + local clusterio_api = require("modules/clusterio/api") -- inside some event handler - clusterio_api.send_json('my_plugin_foo', { data = 123 }) + clusterio_api.send_json("my_plugin_foo", { data = 123 }) It's recommended to either use the plugin name as the channel name or to prefix the channel name with the name of the plugin if you need multiple @@ -324,9 +324,9 @@ the event, for example the following could be defined in `info.js`: messages: { startFrobnication: new libLink.Event({ - type: 'foo_frobber:start_frobnication', - links: ['master-slave', 'slave-instance'], - forwardTo: 'instance', + type: "foo_frobber:start_frobnication", + links: ["master-slave", "slave-instance"], + forwardTo: "instance", eventProperties: { "frobnication_type": { type: "string" }, }, @@ -351,22 +351,22 @@ appended to it. #### links An array of strings describing which links this event can be sent over. -Direction matters, `'master-slave'` means the event can be sent from the +Direction matters, `"master-slave"` means the event can be sent from the master to the slave, but can't be sent back the other way, unless -`'slave-master'` is also present in the links array. +`"slave-master"` is also present in the links array. The available endpoints are `master`, `slave`, `instance`, and `control`. Master talks with slave and control, and slave talks to instance. The full chain must be specified as the individual links in order for a message to travers multiple hops, (i.e., for a message to go -from master to instance it must have both `'master-slave'` and -`'slave-instance'` in the links array). See `forwardTo` and +from master to instance it must have both `"master-slave"` and +`"slave-instance"` in the links array). See `forwardTo` and `broadcastTo` for ways to forward an event to the next link in a chain. #### forwardTo -Target to forward an event to. Can either be `'master'`, to indicate a -slave should forward it to the master server, or `'instance'`, to +Target to forward an event to. Can either be `"master"`, to indicate a +slave should forward it to the master server, or `"instance"`, to indicate it should be forwarded to the instances specified by the `instance_id` event property. This works by using a default handler for the event at the links that forward it. @@ -390,7 +390,7 @@ are not allowed. See [this guide][guide] for an introduction to writing JSON schemas. The forwardTo and broadcastTo can be combined such that specifying -`'master'` as the forwardTo value and `'instance'` as the broadcastTo +`"master"` as the forwardTo value and `"instance"` as the broadcastTo value will cause the event to be broadcast to all instances in the cluster. For this to work, you will need to specify `instance-slave`, `slave-master`, `master-slave`, and `slave-instance` as the links. @@ -409,9 +409,9 @@ the event. For example, the following could be defined in `info.js`: messages: { reportFrobnication: new libLink.Request({ - type: 'foo_frobber:report_frobnication', - links: ['master-slave', 'slave-instance'], - forwardTo: 'instance', + type: "foo_frobber:report_frobnication", + links: ["master-slave", "slave-instance"], + forwardTo: "instance", requestProperties: { "verbosity": { type: "integer" }, }, @@ -427,7 +427,7 @@ the event. For example, the following could be defined in `info.js`: This specifies a request that can be sent from the master to a slave, and from a slave to an instance. The request data must contain the property `verbosity` with an integer number as the value, as well as the -`instance_id` property (implied by `forwardTo: 'instance'`), and the +`instance_id` property (implied by `forwardTo: "instance"`), and the response sent must contain a `report` property mapping to an array of strings. When received by a slave, it will also be forwarded to the instance specified by `instance_id`. @@ -445,24 +445,24 @@ response. #### links An array of strings describing which links this request can be sent -over. Direction matters; `'master-slave'` means the request can be sent +over. Direction matters; `"master-slave"` means the request can be sent from the master to the slave and the slave can reply to the master, but -the slave can't send a request to the master unless `'slave-master'` is +the slave can't send a request to the master unless `"slave-master"` is also present in the links array. The available endpoints are `master`, `slave`, `instance`, and `control`. Master talks with slave and control, and slave talks to instance. The full chain must be specified as the individual links in order for a message to travers multiple hops, (i.e., for a message to go -from master to instance it must have both `'master-slave'` and -`'slave-instance'` in the links array). See `forwardTo` for ways to +from master to instance it must have both `"master-slave"` and +`"slave-instance"` in the links array). See `forwardTo` for ways to forward a request to the next link in a chain. #### forwardTo -Target to forward the request to. Can either be `'master'` to indicate +Target to forward the request to. Can either be `"master"` to indicate a slave should forward it to the master server when receiving it from an -instance, or `'instance'` to indicate it should be forwarded to the +instance, or `"instance"` to indicate it should be forwarded to the instances specified by the `instance_id` request property. This works by using a default handler for the request by the links that forward it. @@ -557,10 +557,10 @@ from plugins optionally loaded at runtime on different computers. In its simplest form collecting data from plugins consists of defining the metric and updating it somewhere in the plugin code. For example: - const { Counter } = require('@clusterio/lib/prometheus'); + const { Counter } = require("@clusterio/lib/prometheus"); const fooMetric = new Counter( - 'clusterio_foo_frobber_foo_metric', "Measures the level of foo", + "clusterio_foo_frobber_foo_metric", "Measures the level of foo", ); // Somewhere in the master plugin code @@ -574,11 +574,11 @@ naming scheme. For metrics that are per-instance, you must define an `instance_id` label and set it accordingly, for example: - const { Counter } = require('@clusterio/lib/prometheus'); + const { Counter } = require("@clusterio/lib/prometheus"); const barMetric = new Gauge( - 'clusterio_foo_frobber_bar_metric', "Bar instance level", - { labels: ['instance_id'] } + "clusterio_foo_frobber_bar_metric", "Bar instance level", + { labels: ["instance_id"] } ); // Somewhere in the instance plugin code From 10cebd9e097387c295853ef168ae5d505a0fc334 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 17:46:48 +0100 Subject: [PATCH 02/14] Fix typo in request description Unlike HTTP the lib/link Requests can be initiated by both sides of the connection. --- docs/writing-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/writing-plugins.md b/docs/writing-plugins.md index a3d18512..e3a00183 100644 --- a/docs/writing-plugins.md +++ b/docs/writing-plugins.md @@ -311,7 +311,7 @@ and requests. Events are simple one-way notifications that invoke a handler on the target it's sent to. Requests are pairs of request and response messages where the request is sent to the target and the response is the reply back from the target. The requests are similar to -HTTP requests, in that both parties of a link may initiate one. +HTTP requests, except both parties of a link may initiate one. ### Defining Events From 6cb33a8b63ba93cb857403c41d753f19a90f9079 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 17:48:12 +0100 Subject: [PATCH 03/14] Fix typo in subspace_storage config --- plugins/subspace_storage/info.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/subspace_storage/info.js b/plugins/subspace_storage/info.js index bee1faff..b60fe71d 100644 --- a/plugins/subspace_storage/info.js +++ b/plugins/subspace_storage/info.js @@ -21,7 +21,7 @@ MasterConfigGroup.define({ }); MasterConfigGroup.define({ name: "log_item_transfers", - title: "Log Item Trassfers", + title: "Log Item Transfers", description: "Spam master console with item transfers done.", type: "boolean", initial_value: false, @@ -32,7 +32,7 @@ class InstanceConfigGroup extends libConfig.PluginConfigGroup {} InstanceConfigGroup.groupName = "subspace_storage"; InstanceConfigGroup.define({ name: "log_item_transfers", - title: "Log Item Trassfers", + title: "Log Item Transfers", description: "Spam slave console with item transfers done.", type: "boolean", initial_value: false, From 3ce32ba8704cb57fe1ed8e9ef5d8491a9e16c91f Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 17:53:57 +0100 Subject: [PATCH 04/14] Fix ctl slave generate-token command Fix missing link -> libLink rename in clusterioctl slave generate-token command as well as missing this reference. --- packages/ctl/ctl.js | 2 +- packages/master/master.js | 2 +- test/integration/clusterio.js | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 514cd0eb..51e94f35 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -73,7 +73,7 @@ slaveCommands.add(new libCommand.Command({ yargs.option("id", { type: "number", nargs: 1, describe: "Slave id", demandOption: true }); }], handler: async function(args, control) { - let response = await link.messages.generateSlaveToken.send(control, { slave_id: args.id }); + let response = await libLink.messages.generateSlaveToken.send(control, { slave_id: args.id }); console.log(response.token); }, })); diff --git a/packages/master/master.js b/packages/master/master.js index b8adf2fd..6268151a 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -598,7 +598,7 @@ class ControlConnection extends BaseConnection { } async generateSlaveTokenRequestHandler(message) { - return { token: generateSlaveToken(message.data.slave_id) }; + return { token: this.generateSlaveToken(message.data.slave_id) }; } async createSlaveConfigRequestHandler(message) { diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 96b90146..b84f60ff 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -25,6 +25,11 @@ describe("Integration of Clusterio", function() { await execCtl("slave list"); }); }); + describe("slave generate-token", function() { + it("runs", async function() { + await execCtl("slave generate-token --id 42"); + }); + }); describe("instance list", function() { it("runs", async function() { await execCtl("instance list"); From 5337280eec4426873e11e6aedbc6602ecc904a94 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 18:16:24 +0100 Subject: [PATCH 05/14] Remove usage of ++ outside of for loops update The usage of postfix ++ is somewhat obscure in its meaning. It's better to be explicit here. --- .eslintrc.js | 2 +- packages/lib/factorio/patch.js | 2 +- packages/lib/link/connectors.js | 14 +++++++++----- packages/master/master.js | 3 ++- plugins/global_chat/test/plugin.js | 4 ++-- test/integration/export.js | 2 +- test/lib/factorio/patch.js | 2 +- test/mock.js | 6 ++++-- 8 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index a6e7c847..f82a6194 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -177,7 +177,7 @@ module.exports = { "no-octal-escape": "error", "no-param-reassign": "off", "no-path-concat": "error", - "no-plusplus": ["warn", { "allowForLoopAfterthoughts": true }], + "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], "no-process-env": "error", "no-process-exit": "warn", "no-proto": "error", diff --git a/packages/lib/factorio/patch.js b/packages/lib/factorio/patch.js index 7e9fbec6..6b70b1d2 100644 --- a/packages/lib/factorio/patch.js +++ b/packages/lib/factorio/patch.js @@ -152,7 +152,7 @@ function reorderDependencies(modules) { // No unmet dependencies, record and continue present.set(module.name, module.version); - index++; + index += 1; if (hold.has(module.name)) { modules.splice(index, 0, ...hold.get(module.name)); diff --git a/packages/lib/link/connectors.js b/packages/lib/link/connectors.js index 37f9c05e..a8d494c9 100644 --- a/packages/lib/link/connectors.js +++ b/packages/lib/link/connectors.js @@ -34,7 +34,7 @@ class WebSocketBaseConnector extends events.EventEmitter { let dropCount = 0; for (let index = 0; index < this._sendBuffer.length; index++) { if (this._sendBuffer[index].seq <= seq) { - dropCount++; + dropCount += 1; } else { break; } @@ -102,12 +102,14 @@ class WebSocketBaseConnector extends events.EventEmitter { if (!["handshake", "connected", "closing"].includes(this._state)) { throw new libErrors.SessionLost("No session"); } - let message = { seq: this._seq, type, data }; + let seq = this._seq; + this._seq += 1; + let message = { seq, type, data }; this._sendBuffer.push(message); if (["connected", "closing"].includes(this._state)) { this._socket.send(JSON.stringify(message)); } - return this._seq++; + return seq; } /** @@ -450,8 +452,10 @@ class VirtualConnector extends events.EventEmitter { * @returns {number} the sequence number of the message sent */ send(type, data) { - this.other.emit("message", { seq: this._seq, type, data }); - return this._seq++; + let seq = this._seq; + this._seq += 1; + this.other.emit("message", { seq, type, data }); + return seq; } } diff --git a/packages/master/master.js b/packages/master/master.js index 6268151a..32827da3 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -1429,7 +1429,8 @@ async function handleHandshake(message, socket, req, attachHandler) { return; } - let sessionId = nextSessionId++; + let sessionId = nextSessionId; + nextSessionId += 1; let sessionToken = jwt.sign({ aud: masterSession, sid: sessionId }, masterConfig.get("master.auth_secret")); let connector = new WebSocketServerConnector(socket, sessionId); activeConnectors.set(sessionId, connector); diff --git a/plugins/global_chat/test/plugin.js b/plugins/global_chat/test/plugin.js index 7576a9b6..f371d360 100644 --- a/plugins/global_chat/test/plugin.js +++ b/plugins/global_chat/test/plugin.js @@ -46,7 +46,7 @@ describe("global_chat plugin", function() { instancePlugin.instance.connector.sentMessages = []; await instancePlugin.onOutput(output); assert(instancePlugin.instance.connector.sentMessages.length, "message was not sent"); - count++; + count += 1; } } assert(count > 0, "no lines were tested"); @@ -58,7 +58,7 @@ describe("global_chat plugin", function() { instancePlugin.instance.connector.sentMessages = []; await instancePlugin.onOutput(output); assert(!instancePlugin.instance.connector.sentMessages.length, "message was sent"); - count++; + count += 1; } } assert(count > 0, "no lines were tested"); diff --git a/test/integration/export.js b/test/integration/export.js index c7dc20f8..dccfbb10 100644 --- a/test/integration/export.js +++ b/test/integration/export.js @@ -24,7 +24,7 @@ describe("Integration of lib/factorio/export", function() { // Value is always a string, nearly always truthy if (!value) { - empty_keys++; + empty_keys += 1; } assert.equal(typeof value, "string"); } diff --git a/test/lib/factorio/patch.js b/test/lib/factorio/patch.js index d5d1bdfb..00850590 100644 --- a/test/lib/factorio/patch.js +++ b/test/lib/factorio/patch.js @@ -199,7 +199,7 @@ describe("lib/factorio", function() { } catch (err) { } if (success) { validate(modules); - successCount++; + successCount += 1; } } diff --git a/test/mock.js b/test/mock.js index 9b9f6cdb..c8cddfbb 100644 --- a/test/mock.js +++ b/test/mock.js @@ -58,10 +58,12 @@ class MockConnector extends events.EventEmitter { } send(type, data) { - let message = { seq: this._seq, type, data }; + let seq = this._seq; + this._seq += 1; + let message = { seq, type, data }; this.sentMessages.push(message); setImmediate(() => this.emit("send", message)); - return this._seq++; + return seq; } } From 85111e233ec72ef26c39b52197fd4f0508a19483 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 18:19:50 +0100 Subject: [PATCH 06/14] Move linebreak to be after + operator --- .eslintrc.js | 9 ++++++++- packages/lib/config/definitions.js | 6 +++--- test/slave.js | 10 ++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f82a6194..19bddc01 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -224,7 +224,14 @@ module.exports = { "one-var": "off", "one-var-declaration-per-line": "error", "operator-assignment": "off", - "operator-linebreak": "warn", + "operator-linebreak": ["error", "after", { + "overrides": { + "?": "before", + ":": "before", + "||": "before", + "&&": "before", + }, + }], "padded-blocks": "off", "padding-line-between-statements": "error", "prefer-arrow-callback": "warn", diff --git a/packages/lib/config/definitions.js b/packages/lib/config/definitions.js index 7958db7e..27edcbe7 100644 --- a/packages/lib/config/definitions.js +++ b/packages/lib/config/definitions.js @@ -69,9 +69,9 @@ MasterGroup.define({ name: "auth_secret", title: "Master Authentication Secret", description: - "Secret used to generate and verify authentication tokens." - +" Should be a long string of random letters and numbers." - +" Do not share this.", + "Secret used to generate and verify authentication tokens. " + + "Should be a long string of random letters and numbers. " + + "Do not share this.", type: "string", optional: true, }); diff --git a/test/slave.js b/test/slave.js index b7b12ee9..a14c40f1 100644 --- a/test/slave.js +++ b/test/slave.js @@ -67,19 +67,13 @@ describe("Slave testing", function() { it("should throw on CON, PRN, AUX, NUL, COM1, LPT1", function() { for (let bad of ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"]) { - check( - bad, "cannot be named any of . .." - +" CON PRN AUX NUL COM1-9 and LPT1-9" - ); + check(bad, "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9"); } }); it("should throw on . and ..", function() { for (let bad of [".", ".."]) { - check( - bad, "cannot be named any of . .." - +" CON PRN AUX NUL COM1-9 and LPT1-9" - ); + check(bad, "cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9"); } }); From df94ff1d8d0658c54d6609caed86d2589d6d9b59 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 18:24:17 +0100 Subject: [PATCH 07/14] Remove leftover file from web refactor Remove wslink.jsx which became obsolete after f67f56e reimplemented the logic. --- packages/web_ui/src/util/wslink.jsx | 116 ---------------------------- 1 file changed, 116 deletions(-) delete mode 100644 packages/web_ui/src/util/wslink.jsx diff --git a/packages/web_ui/src/util/wslink.jsx b/packages/web_ui/src/util/wslink.jsx deleted file mode 100644 index 07d4775a..00000000 --- a/packages/web_ui/src/util/wslink.jsx +++ /dev/null @@ -1,116 +0,0 @@ -import * as errors from "@clusterio/lib/errors"; -import * as link from "@clusterio/lib/link"; - -// Note: control is created as in bootstrap.jsx as a global. - -let listSlaves = createListFunction("listSlaves"); -let listInstances = createListFunction("listInstances"); -let listUsers = createListFunction("listUsers", "name"); -let listRoles = createListFunction("listRoles"); -let listPermissions = createListFunction("listPermissions", "name"); - -async function startInstance({ instance_id, save = null }) { - return await link.messages.startInstance.send(control, { instance_id, save }); -}; -async function stopInstance({ instance_id }) { - return await link.messages.stopInstance.send(control, { instance_id }); -}; -async function createInstance(serialized_config) { - return await link.messages.createInstance.send(control, { serialized_config }); -}; -async function deleteInstance({ instance_id }) { - return await link.messages.deleteInstance.send(control, { instance_id }); -}; -async function assignInstance({ instance_id, slave_id }) { - return await link.messages.assignInstanceCommand.send(control, { - instance_id, - slave_id, - }); -}; -async function createSave({ instance_id }) { - return await link.messages.createSave.send(control, { instance_id }); -}; -async function getInstanceConfig({ instance_id }) { - return await link.messages.getInstanceConfig.send(control, { instance_id }); -}; -async function setInstanceConfigField({ instance_id, field, value }) { - return await link.messages.setInstanceConfigField.send(control, { - instance_id, - field, - value: typeof value === "object" ? JSON.stringify(value) : value, - }); -}; -async function setInstanceConfigProp({ instance_id, field, prop, value }) { - return await link.messages.setInstanceConfigProp.send(control, { - instance_id, - field, - prop, - value, // JSON Object - }); -}; -async function sendRcon({ instance_id, command }) { - return await link.messages.sendRcon.send(control, { instance_id, command }); -}; -async function setInstanceOutputSubscriptions({ instance_id }) { - return await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instance_id] }); -}; - -async function createUser({ name }) { - return await link.messages.createUser.send(control, { name }); -}; -async function deleteUser({ name }) { - return await link.messages.deleteUser.send(control, { name }); -}; -async function setRoles({ name, roles }) { - return await link.messages.updateUserRoles.send(control, { - name, - roles, - }); -}; - -async function createRole({ name, description, permissions = [] }) { - return await link.messages.createRole.send(control, { name, description, permissions }); -}; -async function updateRole({ id, name, description, permissions }) { - return await link.messages.updateRole.send(control, { - id, name, description, permissions, - }); -}; - -export { - listSlaves, - listInstances, - listUsers, - createUser, - deleteUser, - listRoles, - setRoles, - createRole, - updateRole, - listPermissions, - startInstance, - stopInstance, - createInstance, - deleteInstance, - assignInstance, - createSave, - getInstanceConfig, - setInstanceConfigField, - setInstanceConfigProp, - sendRcon, - setInstanceOutputSubscriptions, -}; - -function createListFunction(name, key) { - return async () => ( - (await link.messages[name].send(control)).list.map(addKey(key || "id")) - ); -} -function addKey(key) { - return function (el) { - return { - ...el, - key: el[key], - }; - }; -} From 04948588865cd55b9b29e082843fd8209128a207 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 18:36:41 +0100 Subject: [PATCH 08/14] Move loadPluginInfos out of lib/plugin.js Move the code for loading plugins infos from Node.js packages into a separate module in order to break it out from lib/plugin.js which is also used on the web interface side. This resolves a warning of using an expression with require on the webpack build. --- packages/ctl/ctl.js | 3 +- packages/lib/plugin.js | 43 ------------------------- packages/lib/plugin_loader.js | 52 ++++++++++++++++++++++++++++++ packages/master/master.js | 3 +- packages/slave/slave.js | 3 +- test/lib/plugin.js | 52 ------------------------------ test/lib/plugin_loader.js | 59 +++++++++++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 98 deletions(-) create mode 100644 packages/lib/plugin_loader.js create mode 100644 test/lib/plugin_loader.js diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 51e94f35..7c41f0fc 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -17,6 +17,7 @@ const libLink = require("@clusterio/lib/link"); const libErrors = require("@clusterio/lib/errors"); const libConfig = require("@clusterio/lib/config"); const libPlugin = require("@clusterio/lib/plugin"); +const libPluginLoader = require("@clusterio/lib/plugin_loader"); const libCommand = require("@clusterio/lib/command"); const libSharedCommands = require("@clusterio/lib/shared_commands"); @@ -628,7 +629,7 @@ async function startControl() { rootCommands.add(debugCommands); console.log("Loading Plugin info"); - let pluginInfos = await libPlugin.loadPluginInfos(pluginList); + let pluginInfos = await libPluginLoader.loadPluginInfos(pluginList); libConfig.registerPluginConfigGroups(pluginInfos); libConfig.finalizeConfigs(); diff --git a/packages/lib/plugin.js b/packages/lib/plugin.js index 3fe5a9f3..5444ba23 100644 --- a/packages/lib/plugin.js +++ b/packages/lib/plugin.js @@ -3,9 +3,6 @@ * @module lib/plugin */ "use strict"; -const path = require("path"); - -const libErrors = require("@clusterio/lib/errors"); /** @@ -386,45 +383,6 @@ class BaseControlPlugin { async addCommands(rootCommand) { } } -/** - * Load plugin information - * - * Loads plugin info modules for the paths to the given plugins. Once - * loaded the info modules will not be reloaded should this function be - * called again. - * - * @param {Map} pluginList - - * Mapping of plugin name to require path for the plugins to load. - * @returns {Array} Array of plugin info modules. - * @static - */ -async function loadPluginInfos(pluginList) { - let plugins = []; - for (let [pluginName, pluginPath] of pluginList) { - let pluginInfo; - let pluginPackage; - - try { - pluginInfo = require(path.posix.join(pluginPath, "info")); - pluginPackage = require(path.posix.join(pluginPath, "package.json")); - - } catch (err) { - throw new libErrors.PluginError(pluginName, err); - } - - if (pluginInfo.name !== pluginName) { - throw new libErrors.EnvironmentError( - `Expected plugin at ${pluginPath} to be named ${pluginName} but got ${pluginInfo.name}` - ); - } - - pluginInfo.requirePath = pluginPath; - pluginInfo.version = pluginPackage.version; - plugins.push(pluginInfo); - } - return plugins; -} - /** * Attach plugin messages * @@ -485,7 +443,6 @@ module.exports = { BaseMasterPlugin, BaseControlPlugin, - loadPluginInfos, attachPluginMessages, invokeHook, }; diff --git a/packages/lib/plugin_loader.js b/packages/lib/plugin_loader.js new file mode 100644 index 00000000..8307eef4 --- /dev/null +++ b/packages/lib/plugin_loader.js @@ -0,0 +1,52 @@ +/** + * Helpers for loading plugins in Node.js + * @module lib/plugin_loader + */ +"use strict"; + +const libErrors = require("@clusterio/lib/errors"); +const path = require("path"); + + +/** + * Load plugin information + * + * Loads plugin info modules for the paths to the given plugins. Once + * loaded the info modules will not be reloaded should this function be + * called again. + * + * @param {Map} pluginList - + * Mapping of plugin name to require path for the plugins to load. + * @returns {Array} Array of plugin info modules. + * @static + */ +async function loadPluginInfos(pluginList) { + let plugins = []; + for (let [pluginName, pluginPath] of pluginList) { + let pluginInfo; + let pluginPackage; + + try { + pluginInfo = require(path.posix.join(pluginPath, "info")); + pluginPackage = require(path.posix.join(pluginPath, "package.json")); + + } catch (err) { + throw new libErrors.PluginError(pluginName, err); + } + + if (pluginInfo.name !== pluginName) { + throw new libErrors.EnvironmentError( + `Expected plugin at ${pluginPath} to be named ${pluginName} but got ${pluginInfo.name}` + ); + } + + pluginInfo.requirePath = pluginPath; + pluginInfo.version = pluginPackage.version; + plugins.push(pluginInfo); + } + return plugins; +} + +module.exports = { + loadPluginInfos, +}; diff --git a/packages/master/master.js b/packages/master/master.js index 32827da3..cbdbf0ac 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -49,6 +49,7 @@ const libSchema = require("@clusterio/lib/schema"); const libLink = require("@clusterio/lib/link"); const libErrors = require("@clusterio/lib/errors"); const libPlugin = require("@clusterio/lib/plugin"); +const libPluginLoader = require("@clusterio/lib/plugin_loader"); const libPrometheus = require("@clusterio/lib/prometheus"); const libConfig = require("@clusterio/lib/config"); const libUsers = require("@clusterio/lib/users"); @@ -1611,7 +1612,7 @@ async function startServer() { } console.log("Loading Plugin info"); - pluginInfos = await libPlugin.loadPluginInfos(pluginList); + pluginInfos = await libPluginLoader.loadPluginInfos(pluginList); libConfig.registerPluginConfigGroups(pluginInfos); libConfig.finalizeConfigs(); diff --git a/packages/slave/slave.js b/packages/slave/slave.js index 71974e5f..e6746b69 100755 --- a/packages/slave/slave.js +++ b/packages/slave/slave.js @@ -28,6 +28,7 @@ const libFileOps = require("@clusterio/lib/file_ops"); const libFactorio = require("@clusterio/lib/factorio"); const libLink = require("@clusterio/lib/link"); const libPlugin = require("@clusterio/lib/plugin"); +const libPluginLoader = require("@clusterio/lib/plugin_loader"); const libErrors = require("@clusterio/lib/errors"); const libPrometheus = require("@clusterio/lib/prometheus"); const libLuaTools = require("@clusterio/lib/lua_tools"); @@ -1419,7 +1420,7 @@ async function startSlave() { } console.log("Loading Plugin info"); - let pluginInfos = await libPlugin.loadPluginInfos(pluginList); + let pluginInfos = await libPluginLoader.loadPluginInfos(pluginList); libConfig.registerPluginConfigGroups(pluginInfos); libConfig.finalizeConfigs(); diff --git a/test/lib/plugin.js b/test/lib/plugin.js index 5ef3b34f..2ca64415 100644 --- a/test/lib/plugin.js +++ b/test/lib/plugin.js @@ -1,7 +1,5 @@ "use strict"; const assert = require("assert").strict; -const fs = require("fs-extra"); -const path = require("path"); const mock = require("../mock"); const libLink = require("@clusterio/lib/link"); @@ -41,56 +39,6 @@ describe("lib/plugin", function() { }); }); - describe("loadPluginInfos()", function() { - let baseDir = path.join("temp", "test", "plugin"); - let missingPlugin = path.join(baseDir, "missing_plugin"); - let testPlugin = path.join(baseDir, "test_plugin"); - let brokenPlugin = path.join(baseDir, "broken_plugin"); - let invalidPlugin = path.join(baseDir, "invalid_plugin"); - before(async function() { - async function writePlugin(pluginPath, infoName) { - await fs.outputFile( - path.join(pluginPath, "info.js"), - `module.exports = { name: "${infoName}" };` - ); - await fs.outputFile( - path.join(pluginPath, "package.json"), - '{ "version": "0.0.1" }' - ); - } - - await writePlugin(testPlugin, "test"); - await writePlugin(brokenPlugin, "broken"); - await fs.outputFile(path.join(brokenPlugin, "info.js"), "Syntax Error"); - await writePlugin(invalidPlugin, "wrong"); - }); - - it("should throw on missing plugin", async function() { - await assert.rejects( - libPlugin.loadPluginInfos(new Map([["missing", missingPlugin]]), []), - new RegExp(`^Error: PluginError: Cannot find module '${missingPlugin}/info'`) - ); - }); - it("should load test plugin", async function() { - assert.deepEqual( - await libPlugin.loadPluginInfos(new Map([["test", path.resolve(testPlugin)]])), - [{ name: "test", version: "0.0.1", requirePath: path.resolve(testPlugin) }] - ); - }); - it("should reject on broken plugin", async function() { - await assert.rejects( - libPlugin.loadPluginInfos(new Map([["broken", path.resolve(brokenPlugin)]])), - { message: "PluginError: Unexpected identifier" } - ); - }); - it("should reject on invalid plugin", async function() { - await assert.rejects( - libPlugin.loadPluginInfos(new Map([["invalid", path.resolve(invalidPlugin)]])), - { message: `Expected plugin at ${path.resolve(invalidPlugin)} to be named invalid but got wrong` } - ); - }); - }); - describe("attachPluginMessages()", function() { let mockLink = new libLink.Link("source", "target", new mock.MockConnector()); let mockEvent = new libLink.Event({ type: "test:test", links: ["target-source"] }); diff --git a/test/lib/plugin_loader.js b/test/lib/plugin_loader.js new file mode 100644 index 00000000..c5ce7332 --- /dev/null +++ b/test/lib/plugin_loader.js @@ -0,0 +1,59 @@ +"use strict"; +const assert = require("assert").strict; +const fs = require("fs-extra"); +const path = require("path"); + +const libPluginLoader = require("@clusterio/lib/plugin_loader"); + + +describe("lib/plugin_loader", function() { + describe("loadPluginInfos()", function() { + let baseDir = path.join("temp", "test", "plugin"); + let missingPlugin = path.join(baseDir, "missing_plugin"); + let testPlugin = path.join(baseDir, "test_plugin"); + let brokenPlugin = path.join(baseDir, "broken_plugin"); + let invalidPlugin = path.join(baseDir, "invalid_plugin"); + before(async function() { + async function writePlugin(pluginPath, infoName) { + await fs.outputFile( + path.join(pluginPath, "info.js"), + `module.exports = { name: "${infoName}" };` + ); + await fs.outputFile( + path.join(pluginPath, "package.json"), + '{ "version": "0.0.1" }' + ); + } + + await writePlugin(testPlugin, "test"); + await writePlugin(brokenPlugin, "broken"); + await fs.outputFile(path.join(brokenPlugin, "info.js"), "Syntax Error"); + await writePlugin(invalidPlugin, "wrong"); + }); + + it("should throw on missing plugin", async function() { + await assert.rejects( + libPluginLoader.loadPluginInfos(new Map([["missing", missingPlugin]]), []), + new RegExp(`^Error: PluginError: Cannot find module '${missingPlugin}/info'`) + ); + }); + it("should load test plugin", async function() { + assert.deepEqual( + await libPluginLoader.loadPluginInfos(new Map([["test", path.resolve(testPlugin)]])), + [{ name: "test", version: "0.0.1", requirePath: path.resolve(testPlugin) }] + ); + }); + it("should reject on broken plugin", async function() { + await assert.rejects( + libPluginLoader.loadPluginInfos(new Map([["broken", path.resolve(brokenPlugin)]])), + { message: "PluginError: Unexpected identifier" } + ); + }); + it("should reject on invalid plugin", async function() { + await assert.rejects( + libPluginLoader.loadPluginInfos(new Map([["invalid", path.resolve(invalidPlugin)]])), + { message: `Expected plugin at ${path.resolve(invalidPlugin)} to be named invalid but got wrong` } + ); + }); + }); +}); From d6623f89aa04f82725670fd906fccc011d72eba8 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 18:39:56 +0100 Subject: [PATCH 09/14] Run build scripts on Travis Run the build scripts of all the packages when testing integrations on Travis. This should help catch errors caused by bad web interface code. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 52d21e37..993c4a61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ script: - npx lerna bootstrap --no-ci - node packages/lib/build_mod --source-dir packages/slave/lua/clusterio_lib --bump-patch --factorio-version 0.18 --output-dir temp/test/sharedMods - curl -o temp/test/sharedMods/subspace_storage_1.99.1.zip -L https://github.com/clusterio/factorioClusterioMod/releases/download/1.99.1/subspace_storage_1.99.1.zip + - npx lerna run build - npm run-script ci-cover - cat temp/test/integration/log.txt - codecov -f coverage/lcov.info From bd1414dd1cf6273f6d0feec4624cea2de9c8fb0a Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 18:42:11 +0100 Subject: [PATCH 10/14] Remove CI failure debug code Remove the echoing out of the test log from test/integration/server.js used for debugging weird hang on shutdown immediately after startup that would only happen on Travis. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 993c4a61..b75fb52d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,5 @@ script: - curl -o temp/test/sharedMods/subspace_storage_1.99.1.zip -L https://github.com/clusterio/factorioClusterioMod/releases/download/1.99.1/subspace_storage_1.99.1.zip - npx lerna run build - npm run-script ci-cover - - cat temp/test/integration/log.txt - codecov -f coverage/lcov.info - npm run-script lint From bcd1290301de60fb8eba6bc6f737fb4b7c843405 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 20:21:57 +0100 Subject: [PATCH 11/14] Move config command to lib/shared_commands Move the definition and implementation of the config command to lib/shared_comhmands.js, this removes the need for lib config to depend on the fs module, which is not accessible in web build. --- packages/ctl/ctl.js | 4 +- packages/lib/config/definitions.js | 75 ----------------------------- packages/lib/shared_commands.js | 76 ++++++++++++++++++++++++++++++ packages/master/master.js | 4 +- packages/slave/slave.js | 4 +- 5 files changed, 82 insertions(+), 81 deletions(-) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index 7c41f0fc..3966abb8 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -595,7 +595,7 @@ async function startControl() { type: "string", }) .command("plugin", "Manage available plugins", libSharedCommands.pluginCommand) - .command("control-config", "Manage Control config", libConfig.configCommand) + .command("control-config", "Manage Control config", libSharedCommands.configCommand) .wrap(yargs.terminalWidth()) .help(false) // Disable help to avoid triggering it on the first parse. ; @@ -681,7 +681,7 @@ async function startControl() { // Handle the control-config command before trying to connect. if (args._[0] === "control-config") { - await libConfig.handleConfigCommand(args, controlConfig, args.config); + await libSharedCommands.handleConfigCommand(args, controlConfig, args.config); return; } diff --git a/packages/lib/config/definitions.js b/packages/lib/config/definitions.js index 27edcbe7..c95c0fb3 100644 --- a/packages/lib/config/definitions.js +++ b/packages/lib/config/definitions.js @@ -1,8 +1,6 @@ // Core definitions for the configuration system "use strict"; -const fs = require("fs-extra"); - const classes = require("./classes"); @@ -379,77 +377,6 @@ function finalizeConfigs() { ControlConfig.finalize(); } -/** - * Yargs config command - * - * Can be passed to yargs.command to implement a config command. Use - * handleConfigCommand to do the requested action. - * - * @param {Object} yargs - yargs command builder. - * @memberof module:lib/config - */ -function configCommand(yargs) { - yargs - .command("set [value]", "Set config field") - .command("show ", "Show value of the given config field") - .command("list", "List all configuration fields and their values") - .demandCommand(1, "You need to specify a command to run") - .help() - .strict() - ; -} - -/** - * Handle yargs command - * - * Handle the actions that are made available by configCommand. - * - * @param {Object} args - yargs args object. - * @param {module:lib/config.Config} instance - Config instance. - * @param {string} configPath - Path to configuration file. - * @memberof module:lib/config - */ -async function handleConfigCommand(args, instance, configPath) { - let command = args._[1]; - - if (command === "list") { - for (let GroupClass of instance.constructor.groups.values()) { - for (let def of GroupClass.definitions.values()) { - let value = instance.get(def.fullName); - console.log(`${def.fullName} ${JSON.stringify(value)}`); - } - } - - } else if (command === "show") { - try { - console.log(instance.get(args.field)); - } catch (err) { - if (err instanceof classes.InvalidField) { - console.error(err.message); - } else { - throw err; - } - } - - } else if (command === "set") { - if (args.value === undefined) { - args.value = null; - } - - try { - instance.set(args.field, args.value); - await fs.outputFile(configPath, JSON.stringify(instance.serialize(), null, 4)); - } catch (err) { - if (err instanceof classes.InvalidField || err instanceof classes.InvalidValue) { - console.error(err.message); - } else { - throw err; - } - } - } -} - - module.exports = { MasterGroup, SlaveGroup, @@ -464,6 +391,4 @@ module.exports = { registerPluginConfigGroups, finalizeConfigs, - configCommand, - handleConfigCommand, }; diff --git a/packages/lib/shared_commands.js b/packages/lib/shared_commands.js index 29550cb6..0bd370ce 100644 --- a/packages/lib/shared_commands.js +++ b/packages/lib/shared_commands.js @@ -6,6 +6,9 @@ const fs = require("fs-extra"); const path = require("path"); +const libConfig = require("./config"); + + /** * Yargs plugin command * @@ -80,7 +83,80 @@ async function handlePluginCommand(args, pluginList, pluginListPath) { } } + +/** + * Yargs config command + * + * Can be passed to yargs.command to implement a config command. Use + * handleConfigCommand to do the requested action. + * + * @param {Object} yargs - yargs command builder. + */ +function configCommand(yargs) { + yargs + .command("set [value]", "Set config field") + .command("show ", "Show value of the given config field") + .command("list", "List all configuration fields and their values") + .demandCommand(1, "You need to specify a command to run") + .help() + .strict() + ; +} + +/** + * Handle yargs command + * + * Handle the actions that are made available by configCommand. + * + * @param {Object} args - yargs args object. + * @param {module:lib/config.Config} instance - Config instance. + * @param {string} configPath - Path to configuration file. + */ +async function handleConfigCommand(args, instance, configPath) { + let command = args._[1]; + + if (command === "list") { + for (let GroupClass of instance.constructor.groups.values()) { + for (let def of GroupClass.definitions.values()) { + let value = instance.get(def.fullName); + console.log(`${def.fullName} ${JSON.stringify(value)}`); + } + } + + } else if (command === "show") { + try { + console.log(instance.get(args.field)); + } catch (err) { + if (err instanceof libConfig.InvalidField) { + console.error(err.message); + } else { + throw err; + } + } + + } else if (command === "set") { + if (args.value === undefined) { + args.value = null; + } + + try { + instance.set(args.field, args.value); + await fs.outputFile(configPath, JSON.stringify(instance.serialize(), null, 4)); + } catch (err) { + if (err instanceof libConfig.InvalidField || err instanceof libConfig.InvalidValue) { + console.error(err.message); + } else { + throw err; + } + } + } +} + + module.exports = { pluginCommand, handlePluginCommand, + + configCommand, + handleConfigCommand, }; diff --git a/packages/master/master.js b/packages/master/master.js index cbdbf0ac..f880e59d 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -1574,7 +1574,7 @@ async function startServer() { type: "string", }) .command("plugin", "Manage available plugins", libSharedCommands.pluginCommand) - .command("config", "Manage Master config", libConfig.configCommand) + .command("config", "Manage Master config", libSharedCommands.configCommand) .command("bootstrap", "Bootstrap access to cluster", yargs => { yargs .command("create-admin ", "Create a cluster admin") @@ -1641,7 +1641,7 @@ async function startServer() { } if (command === "config") { - await libConfig.handleConfigCommand(args, masterConfig, masterConfigPath); + await libSharedCommands.handleConfigCommand(args, masterConfig, masterConfigPath); return; } else if (command === "bootstrap") { diff --git a/packages/slave/slave.js b/packages/slave/slave.js index e6746b69..37c13d7b 100755 --- a/packages/slave/slave.js +++ b/packages/slave/slave.js @@ -1395,7 +1395,7 @@ async function startSlave() { type: "string", }) .command("plugin", "Manage available plugins", libSharedCommands.pluginCommand) - .command("config", "Manage Slave config", libConfig.configCommand) + .command("config", "Manage Slave config", libSharedCommands.configCommand) .command("run", "Run slave") .demandCommand(1, "You need to specify a command to run") .strict() @@ -1440,7 +1440,7 @@ async function startSlave() { } if (command === "config") { - await libConfig.handleConfigCommand(args, slaveConfig, args.config); + await libSharedCommands.handleConfigCommand(args, slaveConfig, args.config); return; } From 325c08389e865877c18959526ba788d9ad9d1d7e Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 7 Nov 2020 20:24:25 +0100 Subject: [PATCH 12/14] Fix typo in README.md Replace does with doesn't as the latter was intended. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 85a86b4d..b7b2d1af 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ cloud storage of items via teleporter chests. But this functionality has been pulled out of Clusterio into its own plugin for Clusterio named [Subspace Storage](https://github.com/clusterio/factorioClusterioMod). -By itself Clusterio does change the gameplay in any way, you could even -use Clusterio to manage completely vanilla Factorio servers. Plugins do -the work of modding in the visible changes into the game, see the -[Plugins section](#plugins) for ready made plugins you can install into -a Clusterio cluster. +By itself Clusterio doesn't change the gameplay in any way, you could +even use Clusterio to manage completely vanilla Factorio servers. +Plugins do the work of modding in the visible changes into the game, see +the [Plugins section](#plugins) for ready made plugins you can install +into a Clusterio cluster. ## Features From 6a4875c5ec17bb48200b7a340ce4be8f0dc25699 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sun, 8 Nov 2020 18:27:24 +0100 Subject: [PATCH 13/14] Add bootstrap generate-user-token command Add command to generate a user token for connecting to the master server web interface. This servers as an alternative to generating a control config for use with clusterioctl. --- packages/master/README.md | 7 +++++++ packages/master/master.js | 10 ++++++++++ test/integration/clusterio.js | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/master/README.md b/packages/master/README.md index 99e76433..8e22c08c 100644 --- a/packages/master/README.md +++ b/packages/master/README.md @@ -110,6 +110,13 @@ exist in the cluster and grant it full cluster admin permission. You should be using your own Factorio multiplayer username here. +#### `bootstrap generate-user-token ` + +Generate an access token for logging in and possibly managing the +cluster from the given user account. This can be used both in the web +interface and as the `master_token` to connect with clusterioctl. + + #### `bootstrap create-ctl-config ` Creates a clusterioctl config for the given user with url and token set diff --git a/packages/master/master.js b/packages/master/master.js index f880e59d..37efa82c 100755 --- a/packages/master/master.js +++ b/packages/master/master.js @@ -1578,6 +1578,7 @@ async function startServer() { .command("bootstrap", "Bootstrap access to cluster", yargs => { yargs .command("create-admin ", "Create a cluster admin") + .command("generate-user-token ", "Generate authentication token for the given user") .command("create-ctl-config ", "Create clusterioctl config for the given user", yargs => { yargs.option("output", { describe: "Path to output config (- for stdout)", type: "string", @@ -1664,6 +1665,15 @@ async function startServer() { admin.isAdmin = true; await saveUsers(masterConfig.get("master.database_directory"), "users.json"); + } else if (subCommand === "generate-user-token") { + let user = db.users.get(args.name); + if (!user) { + console.error(`No user named '${args.name}'`); + process.exitCode = 1; + return; + } + console.log(user.createToken(masterConfig.get("master.auth_secret"))); + } else if (subCommand === "create-ctl-config") { let admin = db.users.get(args.name); if (!admin) { diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index b84f60ff..e9d80333 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -5,7 +5,7 @@ const path = require("path"); const libLink = require("@clusterio/lib/link"); -const { slowTest, get, execCtl, sendRcon, getControl, instancesDir } = require("./index"); +const { slowTest, get, exec, execCtl, sendRcon, getControl, instancesDir } = require("./index"); async function getInstances() { @@ -19,6 +19,20 @@ async function checkInstanceStatus(id, status) { } describe("Integration of Clusterio", function() { + describe("clusteriomaster", function() { + describe("bootstrap generate-user-token", function() { + it("work for existing user", async function() { + await exec("node ../../packages/master bootstrap generate-user-token test"); + }); + + it("fails if user does not exist", async function() { + await assert.rejects( + exec("node ../../packages/master bootstrap generate-user-token invalid") + ); + }); + }); + }); + describe("clusterioctl", function() { describe("slave list", function() { it("runs", async function() { From 2a0ae128291ed2bdcf635f9a522e412e93dc9767 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sun, 8 Nov 2020 20:02:59 +0100 Subject: [PATCH 14/14] Improve the responsiveness of the web interface Set breakpoint for the sidebar so that it hides by default when the page width is too small to show it with the content and keep the width of the content constant on small screens while sliding the sidebar out to prevent it getting crushed. --- packages/web_ui/src/components/PageLayout.jsx | 2 +- packages/web_ui/src/components/SiteLayout.jsx | 5 +-- packages/web_ui/src/index.css | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/web_ui/src/components/PageLayout.jsx b/packages/web_ui/src/components/PageLayout.jsx index dda21b32..f4373d32 100644 --- a/packages/web_ui/src/components/PageLayout.jsx +++ b/packages/web_ui/src/components/PageLayout.jsx @@ -6,7 +6,7 @@ const { Content } = Layout; export default function PageLayout(props) { return <> - + {props.nav.map((part, index) => {part.path ? {part.name} : part.name} )} diff --git a/packages/web_ui/src/components/SiteLayout.jsx b/packages/web_ui/src/components/SiteLayout.jsx index 88756b87..d82ef9e5 100644 --- a/packages/web_ui/src/components/SiteLayout.jsx +++ b/packages/web_ui/src/components/SiteLayout.jsx @@ -48,10 +48,11 @@ export default function SiteLayout(props) { Dashboard - + {name})} - + {sidebar.map(({path, content}) => diff --git a/packages/web_ui/src/index.css b/packages/web_ui/src/index.css index 692bef64..246613cb 100644 --- a/packages/web_ui/src/index.css +++ b/packages/web_ui/src/index.css @@ -26,6 +26,38 @@ body { flex-grow: 0; } +.site-layout { + overflow-x: hidden; +} + +.site-layout-content-container { + min-width: 100%; +} + +.site-breadcrumb { + margin: 16px 0 16px 52px; +} + +@media (min-width: 576px) { + .site-layout-content-container { + padding: 0 24px 24px; + } + + .site-breadcrumb { + margin-left: 28px; + } +} + +@media (min-width: 768px) { + .site-layout { + overflow-x: initial; + } + + .site-layout-content-container { + min-width: initial; + } +} + .site-layout-sider { z-index: 0; }