Merge pull request #337 from Hornwitser/alpha-2

Alpha 2 preparations
This commit is contained in:
Hornwitser authored and GitHub committed 2020-11-09 12:44:59 +01:00
commit d493c2d671
27 files changed
+347 -366

No files matched your search

+9 -2
View File
@@ -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",
@@ -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",
+1 -1
View File
@@ -9,7 +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
- npm run-script lint
+5 -5
View File
@@ -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
+31 -31
View File
@@ -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
@@ -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
@@ -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
+5 -4
View File
@@ -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");
@@ -73,7 +74,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);
},
}));
@@ -594,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.
;
@@ -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();
@@ -680,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;
}
+3 -78
View File
@@ -1,8 +1,6 @@
// Core definitions for the configuration system
"use strict";
const fs = require("fs-extra");
const classes = require("./classes");
@@ -69,9 +67,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,
});
@@ -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 <field> [value]", "Set config field")
.command("show <field>", "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,
};
+1 -1
View File
@@ -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));
+9 -5
View File
@@ -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;
}
}
-43
View File
@@ -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<string, string>} pluginList -
* Mapping of plugin name to require path for the plugins to load.
* @returns {Array<Object>} 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,
};
+52
View File
@@ -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<string, string>} pluginList -
* Mapping of plugin name to require path for the plugins to load.
* @returns {Array<Object>} 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,
};
+76
View File
@@ -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 <field> [value]", "Set config field")
.command("show <field>", "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,
};
+7
View File
@@ -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 <name>`
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 <name>`
Creates a clusterioctl config for the given user with url and token set
+17 -5
View File
@@ -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");
@@ -598,7 +599,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) {
@@ -1429,7 +1430,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);
@@ -1572,10 +1574,11 @@ 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 <name>", "Create a cluster admin")
.command("generate-user-token <name>", "Generate authentication token for the given user")
.command("create-ctl-config <name>", "Create clusterioctl config for the given user", yargs => {
yargs.option("output", {
describe: "Path to output config (- for stdout)", type: "string",
@@ -1610,7 +1613,7 @@ async function startServer() {
}
console.log("Loading Plugin info");
pluginInfos = await libPlugin.loadPluginInfos(pluginList);
pluginInfos = await libPluginLoader.loadPluginInfos(pluginList);
libConfig.registerPluginConfigGroups(pluginInfos);
libConfig.finalizeConfigs();
@@ -1639,7 +1642,7 @@ async function startServer() {
}
if (command === "config") {
await libConfig.handleConfigCommand(args, masterConfig, masterConfigPath);
await libSharedCommands.handleConfigCommand(args, masterConfig, masterConfigPath);
return;
} else if (command === "bootstrap") {
@@ -1662,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) {
+4 -3
View File
@@ -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");
@@ -1394,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()
@@ -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();
@@ -1439,7 +1440,7 @@ async function startSlave() {
}
if (command === "config") {
await libConfig.handleConfigCommand(args, slaveConfig, args.config);
await libSharedCommands.handleConfigCommand(args, slaveConfig, args.config);
return;
}
@@ -6,7 +6,7 @@ const { Content } = Layout;
export default function PageLayout(props) {
return <>
<Breadcrumb style={{ margin: "16px 0 16px 28px" }}>
<Breadcrumb className="site-breadcrumb">
{props.nav.map((part, index) => <Breadcrumb.Item key={index}>
{part.path ? <Link to={part.path}>{part.name}</Link> : part.name}
</Breadcrumb.Item>)}
@@ -48,10 +48,11 @@ export default function SiteLayout(props) {
<Menu.Item key="1">Dashboard</Menu.Item>
</Menu>
</Header>
<Layout>
<Layout className="site-layout">
<Sider
collapsible
collapsedWidth={0}
breakpoint="md"
zeroWidthTriggerStyle={{ top: 6, zIndex: -1 }}
width={250}
className="site-layout-sider"
@@ -65,7 +66,7 @@ export default function SiteLayout(props) {
{sidebar.map(({name, path}) => <Menu.Item key={path}>{name}</Menu.Item>)}
</Menu>
</Sider>
<Layout style={{ padding: "0 24px 24px" }}>
<Layout className="site-layout-content-container">
<Switch>
{sidebar.map(({path, content}) => <Route exact path={path} key={path}>
<SetSidebar path={path} />
+32
View File
@@ -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;
}
-116
View File
@@ -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],
};
};
}
+2 -2
View File
@@ -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");
+2 -2
View File
@@ -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,
+20 -1
View File
@@ -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,12 +19,31 @@ 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() {
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");
+1 -1
View File
@@ -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");
}
+1 -1
View File
@@ -199,7 +199,7 @@ describe("lib/factorio", function() {
} catch (err) { }
if (success) {
validate(modules);
successCount++;
successCount += 1;
}
}
-52
View File
@@ -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"] });
+59
View File
@@ -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` }
);
});
});
});
+4 -2
View File
@@ -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;
}
}
+2 -8
View File
@@ -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");
}
});