mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23:24:52 +09:00
Fix the remaning linter warnings (#353)
Refactor the code base to remove all of the remaining warnings from ESLint and convert them into errors.
This commit is contained in:
1 parent
9243731646
commit
1b901f5d1c
33 files changed
+718
-609
No files matched your search
+20
-20
@@ -45,7 +45,7 @@ module.exports = {
|
||||
"arrow-spacing": ["error", { "after": true, "before": true }],
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }],
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
"capitalized-comments": "off",
|
||||
@@ -60,18 +60,18 @@ module.exports = {
|
||||
"functions": "only-multiline",
|
||||
},
|
||||
],
|
||||
"comma-spacing": "warn",
|
||||
"comma-spacing": "error",
|
||||
"comma-style": ["error", "last"],
|
||||
"complexity": "warn",
|
||||
"complexity": "error",
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
"consistent-return": "error",
|
||||
"consistent-this": "error",
|
||||
"curly": "warn",
|
||||
"curly": "error",
|
||||
"default-case": "error",
|
||||
"default-case-last": "error",
|
||||
"default-param-last": "error",
|
||||
"dot-location": ["error", "property"],
|
||||
"dot-notation": ["warn", { "allowPattern": "^[a-z]+(_[a-z]+)*$" }],
|
||||
"dot-notation": ["error", { "allowPattern": "^[a-z]+(_[a-z]+)*$" }],
|
||||
"eol-last": "error",
|
||||
"eqeqeq": "error",
|
||||
"func-call-spacing": "error",
|
||||
@@ -81,7 +81,7 @@ module.exports = {
|
||||
"function-call-argument-newline": "off",
|
||||
"function-paren-newline": "off",
|
||||
"generator-star-spacing": ["error", { "before": false, "after": true }],
|
||||
"global-require": "warn",
|
||||
"global-require": "error",
|
||||
"grouped-accessor-pairs": "error",
|
||||
"guard-for-in": "error",
|
||||
"handle-callback-err": "error",
|
||||
@@ -94,22 +94,22 @@ module.exports = {
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "error",
|
||||
"keyword-spacing": "warn",
|
||||
"keyword-spacing": "error",
|
||||
"line-comment-position": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"lines-around-comment": "off",
|
||||
"lines-around-directive": "off",
|
||||
"lines-between-class-members": ["error", "always"],
|
||||
"max-classes-per-file": "off",
|
||||
"max-depth": "warn",
|
||||
"max-len": ["warn", { "code": 120 }],
|
||||
"max-depth": "error",
|
||||
"max-len": ["error", { "code": 120 }],
|
||||
"max-lines": "off",
|
||||
"max-lines-per-function": "off",
|
||||
"max-nested-callbacks": "error",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"multiline-comment-style": ["warn", "separate-lines"],
|
||||
"multiline-comment-style": ["error", "separate-lines"],
|
||||
"multiline-ternary": "off",
|
||||
"new-cap": "error",
|
||||
"new-parens": "error",
|
||||
@@ -130,7 +130,7 @@ module.exports = {
|
||||
"no-continue": "off",
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": "warn",
|
||||
"no-else-return": "error",
|
||||
"no-empty-function": "off",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
@@ -143,7 +143,7 @@ module.exports = {
|
||||
"no-implicit-globals": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "off",
|
||||
"no-invalid-this": "warn",
|
||||
"no-invalid-this": "error",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-lone-blocks": "error",
|
||||
@@ -179,7 +179,7 @@ module.exports = {
|
||||
"no-path-concat": "error",
|
||||
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
|
||||
"no-process-env": "error",
|
||||
"no-process-exit": "warn",
|
||||
"no-process-exit": "error",
|
||||
"no-proto": "error",
|
||||
"no-restricted-exports": "error",
|
||||
"no-restricted-globals": "error",
|
||||
@@ -205,7 +205,7 @@ module.exports = {
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-use-before-define": "warn",
|
||||
"no-use-before-define": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
@@ -213,7 +213,7 @@ module.exports = {
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-var": "warn",
|
||||
"no-var": "error",
|
||||
"no-void": ["error", { "allowAsStatement": true }],
|
||||
"no-warning-comments": "off",
|
||||
"no-whitespace-before-property": "error",
|
||||
@@ -234,7 +234,7 @@ module.exports = {
|
||||
}],
|
||||
"padded-blocks": "off",
|
||||
"padding-line-between-statements": "error",
|
||||
"prefer-arrow-callback": "warn",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"prefer-exponentiation-operator": "error",
|
||||
@@ -246,7 +246,7 @@ module.exports = {
|
||||
"prefer-regex-literals": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "warn",
|
||||
"prefer-template": "error",
|
||||
"quote-props": "off",
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"radix": ["error", "always"],
|
||||
@@ -261,12 +261,12 @@ module.exports = {
|
||||
"sort-imports": "off",
|
||||
"sort-keys": "off",
|
||||
"sort-vars": "error",
|
||||
"space-before-blocks": "warn",
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": "off",
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": "off",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": ["warn", "always"],
|
||||
"spaced-comment": ["error", "always"],
|
||||
"strict": "error",
|
||||
"switch-colon-spacing": "error",
|
||||
"symbol-description": "error",
|
||||
@@ -274,7 +274,7 @@ module.exports = {
|
||||
"template-tag-spacing": "error",
|
||||
"unicode-bom": ["error", "never"],
|
||||
"valid-jsdoc": ["error", { "requireReturn": false }],
|
||||
"vars-on-top": "warn",
|
||||
"vars-on-top": "error",
|
||||
"wrap-iife": "error",
|
||||
"wrap-regex": "off",
|
||||
"yield-star-spacing": "error",
|
||||
|
||||
+49
-33
@@ -12,6 +12,7 @@ const asTable = require("as-table").configure({ delimiter: " | " });
|
||||
const events = require("events");
|
||||
const path = require("path");
|
||||
const winston = require("winston");
|
||||
const setBlocking = require("set-blocking");
|
||||
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libErrors = require("@clusterio/lib/errors");
|
||||
@@ -627,6 +628,47 @@ class Control extends libLink.Link {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlugins(pluginList) {
|
||||
let pluginInfos = await libPluginLoader.loadPluginInfos(pluginList);
|
||||
libConfig.registerPluginConfigGroups(pluginInfos);
|
||||
libConfig.finalizeConfigs();
|
||||
|
||||
let controlPlugins = new Map();
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
if (!pluginInfo.controlEntrypoint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ControlPluginClass = await libPluginLoader.loadControlPluginClass(pluginInfo);
|
||||
let controlPlugin = new ControlPluginClass(pluginInfo, logger);
|
||||
controlPlugins.set(pluginInfo.name, controlPlugin);
|
||||
await controlPlugin.init();
|
||||
}
|
||||
return controlPlugins;
|
||||
}
|
||||
|
||||
async function registerCommands(controlPlugins, yargs) {
|
||||
const rootCommands = new libCommand.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
|
||||
rootCommands.add(slaveCommands);
|
||||
rootCommands.add(instanceCommands);
|
||||
rootCommands.add(permissionCommands);
|
||||
rootCommands.add(roleCommands);
|
||||
rootCommands.add(userCommands);
|
||||
rootCommands.add(logCommands);
|
||||
rootCommands.add(debugCommands);
|
||||
|
||||
for (let controlPlugin of controlPlugins.values()) {
|
||||
await controlPlugin.addCommands(rootCommands);
|
||||
}
|
||||
|
||||
for (let [name, command] of rootCommands.subCommands) {
|
||||
if (name === command.name) {
|
||||
command.register(yargs);
|
||||
}
|
||||
}
|
||||
|
||||
return rootCommands;
|
||||
}
|
||||
|
||||
async function startControl() {
|
||||
yargs
|
||||
@@ -684,39 +726,11 @@ async function startControl() {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.verbose("Loading Plugins");
|
||||
let controlPlugins = await loadPlugins(pluginList);
|
||||
|
||||
// Add all cluster management commands including ones from plugins
|
||||
const rootCommands = new libCommand.CommandTree({ name: "clusterioctl", description: "Manage cluster" });
|
||||
rootCommands.add(slaveCommands);
|
||||
rootCommands.add(instanceCommands);
|
||||
rootCommands.add(permissionCommands);
|
||||
rootCommands.add(roleCommands);
|
||||
rootCommands.add(userCommands);
|
||||
rootCommands.add(logCommands);
|
||||
rootCommands.add(debugCommands);
|
||||
|
||||
logger.verbose("Loading Plugin info");
|
||||
let pluginInfos = await libPluginLoader.loadPluginInfos(pluginList);
|
||||
libConfig.registerPluginConfigGroups(pluginInfos);
|
||||
libConfig.finalizeConfigs();
|
||||
|
||||
let controlPlugins = new Map();
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
if (!pluginInfo.controlEntrypoint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let { ControlPlugin } = require(path.posix.join(pluginInfo.requirePath, pluginInfo.controlEntrypoint));
|
||||
let controlPlugin = new ControlPlugin(pluginInfo, logger);
|
||||
controlPlugins.set(pluginInfo.name, controlPlugin);
|
||||
await controlPlugin.init();
|
||||
await controlPlugin.addCommands(rootCommands);
|
||||
}
|
||||
|
||||
for (let [name, command] of rootCommands.subCommands) {
|
||||
if (name === command.name) {
|
||||
command.register(yargs);
|
||||
}
|
||||
}
|
||||
let rootCommands = await registerCommands(controlPlugins, yargs);
|
||||
|
||||
// Reparse after commands have been added with help and strict checking.
|
||||
args = yargs
|
||||
@@ -780,7 +794,7 @@ async function startControl() {
|
||||
let control = new Control(controlConnector, controlPlugins);
|
||||
try {
|
||||
await controlConnector.connect();
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
if (err instanceof libErrors.AuthenticationFailed) {
|
||||
throw new libErrors.StartupError(err.message);
|
||||
}
|
||||
@@ -790,7 +804,9 @@ async function startControl() {
|
||||
process.on("SIGINT", () => {
|
||||
logger.info("Caught interrupt signal, closing connection");
|
||||
control.shutdown().catch(err => {
|
||||
setBlocking(true);
|
||||
logger.error(err.stack);
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ class RateLimiter {
|
||||
* Callback invoked at maste `maxRate` per second on activation.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
/*
|
||||
/**
|
||||
* Maximum rate of activations per second.
|
||||
* @type {number}
|
||||
*/
|
||||
|
||||
+59
-58
@@ -8,64 +8,6 @@ const path = require("path");
|
||||
const yargs = require("yargs");
|
||||
|
||||
|
||||
async function main() {
|
||||
const args = yargs
|
||||
.scriptName("build")
|
||||
.options({
|
||||
"clean": { describe: "Remove previous builds", type: "boolean", default: false },
|
||||
"build": { describe: "Build mod(s)", type: "boolean", default: true },
|
||||
"pack": { describe: "Pack into zip file", type: "boolean", default: true },
|
||||
"source-dir": { describe: "Path to mod source files", nargs: 1, type: "string", demandOption: true },
|
||||
"output-dir": { describe: "Path to output built mod(s)", nargs: 1, type: "string", default: "dist" },
|
||||
"factorio-version": { describe: "Override factorio_version", type: "string" },
|
||||
})
|
||||
.strict()
|
||||
.argv
|
||||
;
|
||||
|
||||
await build(args);
|
||||
}
|
||||
|
||||
async function build(args) {
|
||||
let info = JSON.parse(await fs.readFile(path.join(args.sourceDir, "info.json")));
|
||||
|
||||
if (args.factorioVersion) {
|
||||
info.factorio_version = args.factorioVersion;
|
||||
}
|
||||
|
||||
if (args.dependencies) {
|
||||
info.dependencies = args.dependencies;
|
||||
}
|
||||
|
||||
if (args.clean) {
|
||||
let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/;
|
||||
for (let entry of await fs.readdir(args.outputDir)) {
|
||||
let match = splitter.exec(entry);
|
||||
if (match) {
|
||||
let [, name, version] = match;
|
||||
if (name === info.name) {
|
||||
let modPath = path.join(args.outputDir, entry);
|
||||
console.log(`Removing ${modPath}`);
|
||||
await fs.remove(modPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.variants) {
|
||||
for (let [variant, variantOverrides] of Object.entries(info.variants)) {
|
||||
let variantInfo = {
|
||||
...info,
|
||||
...variantOverrides,
|
||||
};
|
||||
delete variantInfo.variants;
|
||||
await buildMod(args, variantInfo);
|
||||
}
|
||||
} else {
|
||||
await buildMod(args, info);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildMod(args, info) {
|
||||
if (args.build) {
|
||||
await fs.ensureDir(args.outputDir);
|
||||
@@ -121,6 +63,65 @@ async function buildMod(args, info) {
|
||||
}
|
||||
}
|
||||
|
||||
async function build(args) {
|
||||
let info = JSON.parse(await fs.readFile(path.join(args.sourceDir, "info.json")));
|
||||
|
||||
if (args.factorioVersion) {
|
||||
info.factorio_version = args.factorioVersion;
|
||||
}
|
||||
|
||||
if (args.dependencies) {
|
||||
info.dependencies = args.dependencies;
|
||||
}
|
||||
|
||||
if (args.clean) {
|
||||
let splitter = /^(.*)_(\d+\.\d+\.\d+)(\.zip)?$/;
|
||||
for (let entry of await fs.readdir(args.outputDir)) {
|
||||
let match = splitter.exec(entry);
|
||||
if (match) {
|
||||
let [, name, version] = match;
|
||||
if (name === info.name) {
|
||||
let modPath = path.join(args.outputDir, entry);
|
||||
console.log(`Removing ${modPath}`);
|
||||
await fs.remove(modPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.variants) {
|
||||
for (let [variant, variantOverrides] of Object.entries(info.variants)) {
|
||||
let variantInfo = {
|
||||
...info,
|
||||
...variantOverrides,
|
||||
};
|
||||
delete variantInfo.variants;
|
||||
await buildMod(args, variantInfo);
|
||||
}
|
||||
} else {
|
||||
await buildMod(args, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
const args = yargs
|
||||
.scriptName("build")
|
||||
.options({
|
||||
"clean": { describe: "Remove previous builds", type: "boolean", default: false },
|
||||
"build": { describe: "Build mod(s)", type: "boolean", default: true },
|
||||
"pack": { describe: "Pack into zip file", type: "boolean", default: true },
|
||||
"source-dir": { describe: "Path to mod source files", nargs: 1, type: "string", demandOption: true },
|
||||
"output-dir": { describe: "Path to output built mod(s)", nargs: 1, type: "string", default: "dist" },
|
||||
"factorio-version": { describe: "Override factorio_version", type: "string" },
|
||||
})
|
||||
.strict()
|
||||
.argv
|
||||
;
|
||||
|
||||
await build(args);
|
||||
}
|
||||
|
||||
if (module === require.main) {
|
||||
main().catch(err => { console.log(err); });
|
||||
}
|
||||
|
||||
@@ -386,8 +386,11 @@ class ConfigGroup {
|
||||
|
||||
if (basicType(value) === "string") {
|
||||
if (def.type === "boolean") {
|
||||
if (value === "true") { value = true; }
|
||||
else if (value === "false") { value = false; }
|
||||
if (value === "true") {
|
||||
value = true;
|
||||
} else if (value === "false") {
|
||||
value = false;
|
||||
}
|
||||
|
||||
} else if (def.type === "number") {
|
||||
let numberRegExp = /^[+-]?(Infinity|\d+\.(\d+)?([eE][+-]?\d+)?|\.?\d+([eE][+-]?\d+)?)$/;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable global-require */
|
||||
/**
|
||||
* Configuration System
|
||||
* @author Hornwitser
|
||||
|
||||
@@ -59,9 +59,8 @@ async function loadJsonAsMap(filePath) {
|
||||
// File does not exist, return empty map
|
||||
return new Map();
|
||||
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +120,8 @@ async function loadJsonArrayAsMap(filePath) {
|
||||
// File does not exist, return empty map
|
||||
return new Map();
|
||||
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+101
-88
@@ -99,6 +99,105 @@ async function loadFile(server, modVersions, modPath) {
|
||||
return await file.async("nodebuffer");
|
||||
}
|
||||
|
||||
async function loadIcon(server, modVersions, iconPath, iconSize, iconMipmaps, iconCache) {
|
||||
let icon = iconCache.get(iconPath);
|
||||
if (icon === undefined) {
|
||||
let fileContent = await loadFile(server, modVersions, iconPath);
|
||||
if (fileContent) {
|
||||
icon = await Jimp.read(fileContent);
|
||||
icon.crop(0, 0, iconSize, iconSize);
|
||||
iconCache.set(iconPath, icon);
|
||||
} else {
|
||||
icon = null;
|
||||
server._logger.warn(`${iconPath} not found`);
|
||||
}
|
||||
iconCache.set(iconPath, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
async function loadSimpleIcon(server, modVersions, item, size, iconCache) {
|
||||
let icon = await loadIcon(server, modVersions, item.icon, item.icon_size, 0, iconCache);
|
||||
if (icon) {
|
||||
let iconScale = size / item.icon_size;
|
||||
if (iconScale !== 1) {
|
||||
icon = icon.clone();
|
||||
icon.scale(iconScale);
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
async function loadLayeredIcon(server, modVersions, item, size, iconCache) {
|
||||
let baseLayerSize = item.icons[0].icon_size || item.icon_size;
|
||||
let icon = await Jimp.create(size, size);
|
||||
|
||||
// The scaling factor of the base layer
|
||||
let baseLayerScale = item.icons[0].scale || 32 / baseLayerSize;
|
||||
|
||||
// The size in pixels of one unit
|
||||
let baseUnit = size / (baseLayerSize * baseLayerScale);
|
||||
|
||||
for (let layer of item.icons) {
|
||||
let layerSize = layer.icon_size || item.icon_size;
|
||||
let iconLayer = await loadIcon(server, modVersions, layer.icon, layerSize, layer.icon_mipmaps || 0, iconCache);
|
||||
|
||||
if (!iconLayer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
iconLayer = iconLayer.clone();
|
||||
|
||||
let tint;
|
||||
if (layer.tint) {
|
||||
let divisor = (layer.tint.r > 1 || layer.tint.g > 1 || layer.tint.b > 1) ? 255 : 1;
|
||||
tint = {
|
||||
r: (layer.tint.r || 0) / divisor,
|
||||
g: (layer.tint.g || 0) / divisor,
|
||||
b: (layer.tint.b || 0) / divisor,
|
||||
a: layer.tint.a || 1,
|
||||
};
|
||||
} else {
|
||||
tint = { r: 1, b: 1, g: 1, a: 1 };
|
||||
}
|
||||
|
||||
let layerScale = layer.scale || 32 / layerSize;
|
||||
let [xs, ys] = layer.shift || [0, 0];
|
||||
let realScale = layerScale * baseUnit;
|
||||
let sizeShift = (size - layerSize * realScale) / 2;
|
||||
|
||||
xs = xs * baseUnit + sizeShift;
|
||||
ys = ys * baseUnit + sizeShift;
|
||||
|
||||
if (realScale !== 1) {
|
||||
iconLayer.scale(realScale);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
iconLayer.scan(0, 0, iconLayer.bitmap.width, iconLayer.bitmap.height, (x, y, sidx) => {
|
||||
x += xs;
|
||||
y += ys;
|
||||
if (x < 0 || x >= size || y < 0 || y >= size) {
|
||||
return;
|
||||
}
|
||||
let sdata = iconLayer.bitmap.data;
|
||||
let ddata = icon.bitmap.data;
|
||||
let didx = icon.getPixelIndex(x, y);
|
||||
|
||||
let sa = sdata[sidx + 3] / 255;
|
||||
let da = ddata[didx + 3] / 255;
|
||||
let db = da * (1 - sa * tint.a);
|
||||
let cb = sa + da * (1 - sa);
|
||||
ddata[didx + 0] = Math.min(255, (sdata[sidx + 0] * sa * tint.r + ddata[didx + 0] * db) / cb);
|
||||
ddata[didx + 1] = Math.min(255, (sdata[sidx + 1] * sa * tint.g + ddata[didx + 1] * db) / cb);
|
||||
ddata[didx + 2] = Math.min(255, (sdata[sidx + 2] * sa * tint.b + ddata[didx + 2] * db) / cb);
|
||||
ddata[didx + 3] = Math.min(255, sdata[sidx + 3] + ddata[didx + 3] * (1 - sa));
|
||||
});
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export item icons and data
|
||||
*
|
||||
@@ -130,107 +229,21 @@ async function exportItems(server, modVersions, items) {
|
||||
let pos = 0;
|
||||
|
||||
let iconCache = new Map();
|
||||
async function loadIcon(iconPath, iconSize, iconMipmaps) {
|
||||
let icon = iconCache.get(iconPath);
|
||||
if (icon === undefined) {
|
||||
let fileContent = await loadFile(server, modVersions, iconPath);
|
||||
if (fileContent) {
|
||||
icon = await Jimp.read(fileContent);
|
||||
icon.crop(0, 0, iconSize, iconSize);
|
||||
iconCache.set(iconPath, icon);
|
||||
} else {
|
||||
icon = null;
|
||||
server._logger.warn(`${iconPath} not found`);
|
||||
}
|
||||
iconCache.set(iconPath, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
let simpleIcons = new Map();
|
||||
for (let item of items) {
|
||||
let icon;
|
||||
let iconPos;
|
||||
if (item.icons) {
|
||||
let baseLayerSize = item.icons[0].icon_size || item.icon_size;
|
||||
icon = await Jimp.create(size, size);
|
||||
icon = await loadLayeredIcon(server, modVersions, item, size, iconCache);
|
||||
iconPos = pos;
|
||||
|
||||
// The scaling factor of the base layer
|
||||
let baseLayerScale = item.icons[0].scale || 32 / baseLayerSize;
|
||||
|
||||
// The size in pixels of one unit
|
||||
let baseUnit = size / (baseLayerSize * baseLayerScale);
|
||||
|
||||
for (let layer of item.icons) {
|
||||
let layerSize = layer.icon_size || item.icon_size;
|
||||
let iconLayer = await loadIcon(layer.icon, layerSize, layer.icon_mipmaps || 0);
|
||||
|
||||
if (!iconLayer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
iconLayer = iconLayer.clone();
|
||||
|
||||
let tint;
|
||||
if (layer.tint) {
|
||||
let divisor = (layer.tint.r > 1 || layer.tint.g > 1 || layer.tint.b > 1) ? 255 : 1;
|
||||
tint = {
|
||||
r: (layer.tint.r || 0) / divisor,
|
||||
g: (layer.tint.g || 0) / divisor,
|
||||
b: (layer.tint.b || 0) / divisor,
|
||||
a: layer.tint.a || 1,
|
||||
};
|
||||
} else {
|
||||
tint = { r: 1, b: 1, g: 1, a: 1 };
|
||||
}
|
||||
|
||||
let layerScale = layer.scale || 32 / layerSize;
|
||||
let [xs, ys] = layer.shift || [0, 0];
|
||||
let realScale = layerScale * baseUnit;
|
||||
let sizeShift = (size - layerSize * realScale) / 2;
|
||||
|
||||
xs = xs * baseUnit + sizeShift;
|
||||
ys = ys * baseUnit + sizeShift;
|
||||
|
||||
if (realScale !== 1) {
|
||||
iconLayer.scale(realScale);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
iconLayer.scan(0, 0, iconLayer.bitmap.width, iconLayer.bitmap.height, function(x, y, sidx) {
|
||||
x += xs;
|
||||
y += ys;
|
||||
if (x < 0 || x >= size || y < 0 || y >= size) {
|
||||
return;
|
||||
}
|
||||
let sdata = this.bitmap.data;
|
||||
let ddata = icon.bitmap.data;
|
||||
let didx = icon.getPixelIndex(x, y);
|
||||
|
||||
let sa = sdata[sidx + 3] / 255;
|
||||
let da = ddata[didx + 3] / 255;
|
||||
let db = da * (1 - sa * tint.a);
|
||||
let cb = sa + da * (1 - sa);
|
||||
ddata[didx + 0] = Math.min(255, (sdata[sidx + 0] * sa * tint.r + ddata[didx + 0] * db) / cb);
|
||||
ddata[didx + 1] = Math.min(255, (sdata[sidx + 1] * sa * tint.g + ddata[didx + 1] * db) / cb);
|
||||
ddata[didx + 2] = Math.min(255, (sdata[sidx + 2] * sa * tint.b + ddata[didx + 2] * db) / cb);
|
||||
ddata[didx + 3] = Math.min(255, sdata[sidx + 3] + ddata[didx + 3] * (1 - sa));
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
iconPos = simpleIcons.get(item.icon);
|
||||
if (iconPos === undefined) {
|
||||
icon = await loadIcon(item.icon, item.icon_size, 0);
|
||||
icon = await loadSimpleIcon(server, modVersions, item, size, iconCache);
|
||||
if (icon) {
|
||||
iconPos = pos;
|
||||
simpleIcons.set(item.icon, pos);
|
||||
let iconScale = size / item.icon_size;
|
||||
if (iconScale !== 1) {
|
||||
icon = icon.clone();
|
||||
icon.scale(iconScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable global-require */
|
||||
/**
|
||||
* Library for interfacing with Factorio Servers and saves.
|
||||
* @module lib/factorio
|
||||
|
||||
@@ -78,7 +78,7 @@ function generateLoader(patchInfo) {
|
||||
];
|
||||
|
||||
for (let moduleName of patchInfo["scenario"]["modules"]) {
|
||||
lines.push('event_handler.add_lib(require("'+moduleName+'"))');
|
||||
lines.push(`event_handler.add_lib(require("${moduleName}"))`);
|
||||
}
|
||||
|
||||
lines.push(...[
|
||||
@@ -90,10 +90,10 @@ function generateLoader(patchInfo) {
|
||||
for (let file of module["files"]) {
|
||||
let requirePath = `modules/${module.name}/${file["path"].slice(0, -4)}`;
|
||||
if (file["load"]) {
|
||||
lines.push('event_handler.add_lib(require("'+requirePath+'"))');
|
||||
lines.push(`event_handler.add_lib(require("${requirePath}"))`);
|
||||
}
|
||||
if (file["require"]) {
|
||||
lines.push('require("'+requirePath+'")');
|
||||
lines.push(`require("${requirePath}")`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,12 +578,12 @@ class FactorioServer extends events.EventEmitter {
|
||||
|
||||
// Track autosaving
|
||||
this._runningAutosave = null;
|
||||
this.on("_autosave", function(name) {
|
||||
this.on("_autosave", name => {
|
||||
this.emit("autosave-start", name);
|
||||
this._runningAutosave = name;
|
||||
});
|
||||
|
||||
this.on("_saved", function() {
|
||||
this.on("_saved", () => {
|
||||
if (this._runningAutosave) {
|
||||
this.emit("autosave-finished", this._runningAutosave);
|
||||
this._runningAutosave = null;
|
||||
@@ -798,8 +798,8 @@ class FactorioServer extends events.EventEmitter {
|
||||
|
||||
} else {
|
||||
this.emit("error", new Error(
|
||||
"Factorio server unexpectedly shut down. Possible causes:\n- "+
|
||||
this._unexpected.join("\n- ")
|
||||
"Factorio server unexpectedly shut down. Possible causes:"+
|
||||
`\n- ${this._unexpected.join("\n- ")}`
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ const fs = require("fs");
|
||||
* @returns {Promise<string>} hash of the stream.
|
||||
*/
|
||||
function hashStream(stream) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let hasher = crypto.createHash("sha1");
|
||||
hasher.setEncoding("hex");
|
||||
hasher.on("finish", function() {
|
||||
hasher.on("finish", () => {
|
||||
resolve(hasher.read());
|
||||
});
|
||||
|
||||
stream.on("error", function(error) {
|
||||
stream.on("error", (error) => {
|
||||
// the docs doesn't say anything about what's passed on error
|
||||
reject(error);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable global-require */
|
||||
"use strict";
|
||||
// eslint-disable-next-line no-process-env
|
||||
if (process.env.APP_ENV === "browser") {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable global-require */
|
||||
/**
|
||||
* Library for message based communication between nodes
|
||||
* @module lib/link
|
||||
|
||||
@@ -124,9 +124,8 @@ class Link {
|
||||
if (handler) {
|
||||
handler(message);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,8 +88,8 @@ class Request extends Message {
|
||||
this.forwardTo = forwardTo;
|
||||
this.handlerSuffix = "RequestHandler";
|
||||
|
||||
this.requestType = type + "_request";
|
||||
this.responseType = type + "_response";
|
||||
this.requestType = `${type}_request`;
|
||||
this.responseType = `${type}_response`;
|
||||
|
||||
if (permission === undefined && links.includes("control-master")) {
|
||||
throw new Error(`permission is required for ${this.type} request over control-master links`);
|
||||
@@ -195,8 +195,11 @@ class Request extends Message {
|
||||
if (this.permission !== null && `${link.source}-${link.target}` === "master-control") {
|
||||
let origHandler = handler;
|
||||
handler = async function(message, request) {
|
||||
this.user.checkPermission(request.permission);
|
||||
return await origHandler.call(this, message, request);
|
||||
// Abuse this binding to get a hold of the ControlConnection instance
|
||||
// eslint-disable-next-line no-invalid-this, consistent-this
|
||||
let controlConnection = this;
|
||||
controlConnection.user.checkPermission(request.permission);
|
||||
return await origHandler.call(controlConnection, message, request);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -783,7 +786,7 @@ class Event extends Message {
|
||||
throw new Error(`Invalid broadcastTo value ${broadcastTo}`);
|
||||
}
|
||||
|
||||
this.eventType = type + "_event";
|
||||
this.eventType = `${type}_event`;
|
||||
this._eventValidator = libSchema.compile({
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
properties: {
|
||||
|
||||
@@ -21,9 +21,9 @@ const libErrors = require("@clusterio/lib/errors");
|
||||
function formatServerOutput(parsed) {
|
||||
let time = "";
|
||||
if (parsed.format === "seconds") {
|
||||
time = chalk.yellow(parsed.time.padStart(8)) + " ";
|
||||
time = `${chalk.yellow(parsed.time.padStart(8))} `;
|
||||
} else if (parsed.format === "date") {
|
||||
time = chalk.yellow(parsed.time) + " ";
|
||||
time = `${chalk.yellow(parsed.time)} `;
|
||||
}
|
||||
|
||||
let info = "";
|
||||
@@ -41,10 +41,10 @@ function formatServerOutput(parsed) {
|
||||
level = chalk.bold.redBright(level);
|
||||
}
|
||||
|
||||
info = level + " " + chalk.gray(parsed.file) + ": ";
|
||||
info = `${level} ${chalk.gray(parsed.file)}: `;
|
||||
|
||||
} else if (parsed.type === "action") {
|
||||
info = "[" + chalk.yellow(parsed.action) + "] ";
|
||||
info = `[${chalk.yellow(parsed.action)}] `;
|
||||
}
|
||||
|
||||
return time + info + parsed.message;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"node-forge": "^0.10.0",
|
||||
"rcon-client": "^4.2.0",
|
||||
"semver": "^7.1.1",
|
||||
"set-blocking": "^2.0.0",
|
||||
"triple-beam": "^1.3.0",
|
||||
"winston": "^3.3.3",
|
||||
"winston-transport": "^4.4.0",
|
||||
|
||||
@@ -439,9 +439,7 @@ function attachPluginMessages(link, pluginInfo, plugin) {
|
||||
messageFormat.attach(link);
|
||||
|
||||
} else {
|
||||
messageFormat.attach(link, async function(message, format) {
|
||||
return await plugin[handler](message, format, this);
|
||||
});
|
||||
messageFormat.attach(link, async (message, format) => await plugin[handler](message, format));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable global-require */
|
||||
/**
|
||||
* Helpers for loading plugins in Node.js
|
||||
* @module lib/plugin_loader
|
||||
@@ -47,6 +48,51 @@ async function loadPluginInfos(pluginList) {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load master plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* module:lib/plugin_loader.loadPluginInfos} to load class from.
|
||||
* @returns {function} plugin class
|
||||
* @static
|
||||
*/
|
||||
async function loadMasterPluginClass(pluginInfo) {
|
||||
let entrypoint = require(path.posix.join(pluginInfo.requirePath, pluginInfo.masterEntrypoint));
|
||||
return entrypoint.MasterPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load instance plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* module:lib/plugin_loader.loadPluginInfos} to load class from.
|
||||
* @returns {function} plugin class
|
||||
* @static
|
||||
*/
|
||||
async function loadInstancePluginClass(pluginInfo) {
|
||||
let entrypoint = require(path.posix.join(pluginInfo.requirePath, pluginInfo.instanceEntrypoint));
|
||||
return entrypoint.InstancePlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load control plugin class of a plugin
|
||||
*
|
||||
* @param {Object} pluginInfo -
|
||||
* Plugin info object returned from {@link
|
||||
* module:lib/plugin_loader.loadPluginInfos} to load class from.
|
||||
* @returns {function} plugin class
|
||||
* @static
|
||||
*/
|
||||
async function loadControlPluginClass(pluginInfo) {
|
||||
let entrypoint = require(path.posix.join(pluginInfo.requirePath, pluginInfo.controlEntrypoint));
|
||||
return entrypoint.ControlPlugin;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadPluginInfos,
|
||||
loadMasterPluginClass,
|
||||
loadInstancePluginClass,
|
||||
loadControlPluginClass,
|
||||
};
|
||||
@@ -3,7 +3,8 @@
|
||||
* @module lib/schema
|
||||
*/
|
||||
"use strict";
|
||||
const ajv = new (require("ajv"))({
|
||||
const Ajv = require("ajv");
|
||||
const ajv = new Ajv({
|
||||
verbose: true,
|
||||
format: "full",
|
||||
extendRefs: "fail",
|
||||
|
||||
@@ -54,6 +54,7 @@ async function handlePluginCommand(args, pluginList, pluginListPath) {
|
||||
|
||||
let pluginInfo;
|
||||
try {
|
||||
// eslint-disable-next-line global-require
|
||||
pluginInfo = require(path.posix.join(pluginPath, "info"));
|
||||
} catch (err) {
|
||||
logger.error(`Unable to import plugin info from ${args.path}:\n${err.stack}`);
|
||||
|
||||
+190
-164
@@ -31,6 +31,8 @@ const JSZip = require("jszip");
|
||||
const version = require("./package").version;
|
||||
const util = require("util");
|
||||
const winston = require("winston");
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
|
||||
// ugly globals
|
||||
let masterConfig;
|
||||
@@ -46,6 +48,10 @@ let db = {
|
||||
instances: new Map(),
|
||||
slaves: new Map(),
|
||||
};
|
||||
let slaveConnections = new Map();
|
||||
let controlConnections = [];
|
||||
let activeConnectors = new Map();
|
||||
let pendingSockets = new Set();
|
||||
|
||||
// homebrew modules
|
||||
const HttpCloser = require("./src/HttpCloser");
|
||||
@@ -179,6 +185,22 @@ const wsActiveSlavesGauge = new libPrometheus.Gauge(
|
||||
"How many slaves are currently connected to the master"
|
||||
);
|
||||
|
||||
// Merges samples from sourceResult to destinationResult
|
||||
function mergeSamples(destinationResult, sourceResult) {
|
||||
// Merge metrics received by multiple slaves
|
||||
let receivedSamples = new Map(sourceResult.samples);
|
||||
for (let [suffix, suffixSamples] of destinationResult.samples) {
|
||||
if (receivedSamples.has(suffix)) {
|
||||
suffixSamples.push(...receivedSamples.get(suffix));
|
||||
receivedSamples.delete(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
for (let entry of receivedSamples) {
|
||||
sourceResult.samples.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Prometheus polling endpoint
|
||||
async function getMetrics(req, res, next) {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
@@ -214,17 +236,7 @@ async function getMetrics(req, res, next) {
|
||||
|
||||
} else {
|
||||
// Merge metrics received by multiple slaves
|
||||
let receivedSamples = new Map(result.samples);
|
||||
for (let [suffix, suffixSamples] of resultMap.get(result.metric.name).samples) {
|
||||
if (receivedSamples.has(suffix)) {
|
||||
suffixSamples.push(...receivedSamples.get(suffix));
|
||||
receivedSamples.delete(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
for (let entry of receivedSamples) {
|
||||
result.samples.push(entry);
|
||||
}
|
||||
mergeSamples(resultMap.get(result.metric.name), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,6 +334,23 @@ const masterConnectedClientsCount = new libPrometheus.Gauge(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the URL needed to connect to the master server.
|
||||
*
|
||||
* @returns {string} master URL.
|
||||
*/
|
||||
function getMasterUrl() {
|
||||
let url = masterConfig.get("master.external_address");
|
||||
if (!url) {
|
||||
if (masterConfig.get("master.https_port")) {
|
||||
url = `https://localhost:${masterConfig.get("master.https_port")}/`;
|
||||
} else {
|
||||
url = `http://localhost:${masterConfig.get("master.http_port")}/`;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Map from JSON file in the database directory.
|
||||
*
|
||||
@@ -504,7 +533,7 @@ async function shutdown() {
|
||||
|
||||
logger.info(`Clusterio cleanly exited in ${Date.now() - exitStartTime}ms`);
|
||||
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
setBlocking(true);
|
||||
logger.fatal(`
|
||||
+--------------------------------------------------------------------+
|
||||
@@ -513,6 +542,7 @@ async function shutdown() {
|
||||
+--------------------------------------------------------------------+
|
||||
${err.stack}`
|
||||
);
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -606,7 +636,6 @@ const lastQueryLogTime = new libPrometheus.Gauge(
|
||||
"Time in seconds the last log query took to execute."
|
||||
);
|
||||
|
||||
let controlConnections = [];
|
||||
class ControlConnection extends BaseConnection {
|
||||
constructor(registerData, connector, user) {
|
||||
super("control", connector);
|
||||
@@ -1078,7 +1107,6 @@ class ControlConnection extends BaseConnection {
|
||||
}
|
||||
}
|
||||
|
||||
let slaveConnections = new Map();
|
||||
/**
|
||||
* Represents the connection to a slave
|
||||
*
|
||||
@@ -1446,67 +1474,6 @@ class WebSocketServerConnector extends libLink.WebSocketBaseConnector {
|
||||
}
|
||||
}
|
||||
|
||||
let pendingSockets = new Set();
|
||||
let activeConnectors = new Map();
|
||||
|
||||
wss.on("connection", function (socket, req) {
|
||||
logger.verbose(`SOCKET | new connection from ${req.socket.remoteAddress}`);
|
||||
|
||||
// Track statistics
|
||||
wsConnectionsCounter.inc();
|
||||
socket.on("message", (message) => {
|
||||
wsMessageCounter.labels("in").inc();
|
||||
if (!socket.clusterio_ignore_dump) {
|
||||
debugEvents.emit("message", { direction: "in", content: message });
|
||||
}
|
||||
});
|
||||
let originalSend = socket.send;
|
||||
socket.send = (...args) => {
|
||||
wsMessageCounter.labels("out").inc();
|
||||
if (typeof args[0] === "string" && !socket.clusterio_ignore_dump) {
|
||||
debugEvents.emit("message", { direction: "out", content: args[0] });
|
||||
}
|
||||
return originalSend.call(socket, ...args);
|
||||
};
|
||||
|
||||
// Start connection handshake.
|
||||
socket.send(JSON.stringify({ seq: null, type: "hello", data: {
|
||||
version,
|
||||
plugins: loadedPlugins,
|
||||
}}));
|
||||
|
||||
function attachHandler() {
|
||||
pendingSockets.add(socket);
|
||||
|
||||
let timeoutId = setTimeout(() => {
|
||||
logger.verbose(`SOCKET | closing ${req.socket.remoteAddress} after timing out on handshake`);
|
||||
wsRejectedConnectionsCounter.inc();
|
||||
socket.close(1008, "Handshake timeout");
|
||||
pendingSockets.delete(socket);
|
||||
}, 30*1000);
|
||||
|
||||
socket.once("message", (message) => {
|
||||
clearTimeout(timeoutId);
|
||||
pendingSockets.delete(socket);
|
||||
handleHandshake(
|
||||
message, socket, req, attachHandler
|
||||
).catch(err => {
|
||||
logger.error(`
|
||||
+----------------------------------------------------------------+
|
||||
| Unexpected error occured in WebSocket handshake, please report |
|
||||
| it to https://github.com/clusterio/factorioClusterio/issues |
|
||||
+----------------------------------------------------------------+
|
||||
${err.stack}`
|
||||
);
|
||||
wsRejectedConnectionsCounter.inc();
|
||||
socket.close(1011, "Unexpected error");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
attachHandler();
|
||||
});
|
||||
|
||||
let nextSessionId = 1;
|
||||
|
||||
// Unique string for the session token audience
|
||||
@@ -1546,7 +1513,7 @@ async function handleHandshake(message, socket, req, attachHandler) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
socket.send(JSON.stringify({ seq: null, type: "invalidate", data: {}}));
|
||||
attachHandler();
|
||||
return;
|
||||
@@ -1626,12 +1593,63 @@ async function handleHandshake(message, socket, req, attachHandler) {
|
||||
connector.ready(sessionToken);
|
||||
}
|
||||
|
||||
// handle plugins on the master
|
||||
async function pluginManagement() {
|
||||
let startPluginLoad = Date.now();
|
||||
masterPlugins = await loadPlugins();
|
||||
logger.info(`All plugins loaded in ${Date.now() - startPluginLoad}ms`);
|
||||
}
|
||||
wss.on("connection", (socket, req) => {
|
||||
logger.verbose(`SOCKET | new connection from ${req.socket.remoteAddress}`);
|
||||
|
||||
// Track statistics
|
||||
wsConnectionsCounter.inc();
|
||||
socket.on("message", (message) => {
|
||||
wsMessageCounter.labels("in").inc();
|
||||
if (!socket.clusterio_ignore_dump) {
|
||||
debugEvents.emit("message", { direction: "in", content: message });
|
||||
}
|
||||
});
|
||||
let originalSend = socket.send;
|
||||
socket.send = (...args) => {
|
||||
wsMessageCounter.labels("out").inc();
|
||||
if (typeof args[0] === "string" && !socket.clusterio_ignore_dump) {
|
||||
debugEvents.emit("message", { direction: "out", content: args[0] });
|
||||
}
|
||||
return originalSend.call(socket, ...args);
|
||||
};
|
||||
|
||||
// Start connection handshake.
|
||||
socket.send(JSON.stringify({ seq: null, type: "hello", data: {
|
||||
version,
|
||||
plugins: loadedPlugins,
|
||||
}}));
|
||||
|
||||
function attachHandler() {
|
||||
pendingSockets.add(socket);
|
||||
|
||||
let timeoutId = setTimeout(() => {
|
||||
logger.verbose(`SOCKET | closing ${req.socket.remoteAddress} after timing out on handshake`);
|
||||
wsRejectedConnectionsCounter.inc();
|
||||
socket.close(1008, "Handshake timeout");
|
||||
pendingSockets.delete(socket);
|
||||
}, 30*1000);
|
||||
|
||||
socket.once("message", (message) => {
|
||||
clearTimeout(timeoutId);
|
||||
pendingSockets.delete(socket);
|
||||
handleHandshake(
|
||||
message, socket, req, attachHandler
|
||||
).catch(err => {
|
||||
logger.error(`
|
||||
+----------------------------------------------------------------+
|
||||
| Unexpected error occured in WebSocket handshake, please report |
|
||||
| it to https://github.com/clusterio/factorioClusterio/issues |
|
||||
+----------------------------------------------------------------+
|
||||
${err.stack}`
|
||||
);
|
||||
wsRejectedConnectionsCounter.inc();
|
||||
socket.close(1011, "Unexpected error");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
attachHandler();
|
||||
});
|
||||
|
||||
async function loadPlugins() {
|
||||
let plugins = new Map();
|
||||
@@ -1643,13 +1661,13 @@ async function loadPlugins() {
|
||||
loadedPlugins[pluginInfo.name] = pluginInfo.version;
|
||||
|
||||
let pluginLoadStarted = Date.now();
|
||||
let MasterPlugin = libPlugin.BaseMasterPlugin;
|
||||
let MasterPluginClass = libPlugin.BaseMasterPlugin;
|
||||
try {
|
||||
if (pluginInfo.masterEntrypoint) {
|
||||
({ MasterPlugin } = require(path.posix.join(pluginInfo.requirePath, pluginInfo.masterEntrypoint)));
|
||||
MasterPluginClass = await libPluginLoader.loadMasterPluginClass(pluginInfo);
|
||||
}
|
||||
|
||||
let masterPlugin = new MasterPlugin(
|
||||
let masterPlugin = new MasterPluginClass(
|
||||
pluginInfo, { app, config: masterConfig, db, slaveConnections }, { endpointHitCounter }, logger
|
||||
);
|
||||
await masterPlugin.init();
|
||||
@@ -1664,6 +1682,13 @@ async function loadPlugins() {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
// handle plugins on the master
|
||||
async function pluginManagement() {
|
||||
let startPluginLoad = Date.now();
|
||||
masterPlugins = await loadPlugins();
|
||||
logger.info(`All plugins loaded in ${Date.now() - startPluginLoad}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls listen on server capturing any errors that occurs
|
||||
* binding to the port. Also adds handler for WebSocket
|
||||
@@ -1703,28 +1728,61 @@ function _setConfig(config) {
|
||||
masterConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL needed to connect to the master server.
|
||||
*
|
||||
* @returns {string} master URL.
|
||||
*/
|
||||
function getMasterUrl() {
|
||||
let url = masterConfig.get("master.external_address");
|
||||
if (!url) {
|
||||
if (masterConfig.get("master.https_port")) {
|
||||
url = `https://localhost:${masterConfig.get("master.https_port")}/`;
|
||||
async function handleBootstrapCommand(args) {
|
||||
let subCommand = args._[1];
|
||||
await loadUsers(masterConfig.get("master.database_directory"), "users.json");
|
||||
if (subCommand === "create-admin") {
|
||||
if (!args.name) {
|
||||
logger.error("name cannot be blank");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let admin = db.users.get(args.name);
|
||||
if (!admin) {
|
||||
admin = createUser(args.name);
|
||||
}
|
||||
|
||||
let adminRole = libUsers.ensureDefaultAdminRole(db.roles);
|
||||
admin.roles.add(adminRole);
|
||||
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) {
|
||||
logger.error(`No user named '${args.name}'`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(user.createToken(masterConfig.get("master.auth_secret")));
|
||||
|
||||
} else if (subCommand === "create-ctl-config") {
|
||||
let admin = db.users.get(args.name);
|
||||
if (!admin) {
|
||||
logger.error(`No user named '${args.name}'`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
let controlConfig = new libConfig.ControlConfig();
|
||||
await controlConfig.init();
|
||||
|
||||
controlConfig.set("control.master_url", getMasterUrl());
|
||||
controlConfig.set("control.master_token", admin.createToken(masterConfig.get("master.auth_secret")));
|
||||
|
||||
let content = JSON.stringify(controlConfig.serialize(), null, 4);
|
||||
if (args.output === "-") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(content);
|
||||
} else {
|
||||
url = `http://localhost:${masterConfig.get("master.http_port")}/`;
|
||||
logger.info(`Writing ${args.output}`);
|
||||
await fs.outputFile(args.output, content);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async function startServer() {
|
||||
// Set the process title, shows up as the title of the CMD window on windows
|
||||
// and as the process name in ps/top on linux.
|
||||
process.title = "clusterioMaster";
|
||||
|
||||
async function initialize() {
|
||||
// argument parsing
|
||||
let args = yargs
|
||||
.scriptName("master")
|
||||
@@ -1795,9 +1853,6 @@ async function startServer() {
|
||||
}));
|
||||
}
|
||||
|
||||
// add better stack traces on promise rejection
|
||||
process.on("unhandledRejection", err => logger.error(`Unhandled rejection:\n${err.stack}`));
|
||||
|
||||
logger.info(`Loading available plugins from ${args.pluginList}`);
|
||||
let pluginList = new Map();
|
||||
try {
|
||||
@@ -1812,7 +1867,7 @@ async function startServer() {
|
||||
let command = args._[0];
|
||||
if (command === "plugin") {
|
||||
await libSharedCommands.handlePluginCommand(args, pluginList, args.pluginList);
|
||||
return;
|
||||
return [false, args];
|
||||
}
|
||||
|
||||
logger.info("Loading Plugin info");
|
||||
@@ -1846,72 +1901,26 @@ async function startServer() {
|
||||
|
||||
if (command === "config") {
|
||||
await libSharedCommands.handleConfigCommand(args, masterConfig, masterConfigPath);
|
||||
return;
|
||||
return [false, args];
|
||||
|
||||
} else if (command === "bootstrap") {
|
||||
let subCommand = args._[1];
|
||||
await loadUsers(masterConfig.get("master.database_directory"), "users.json");
|
||||
if (subCommand === "create-admin") {
|
||||
if (!args.name) {
|
||||
logger.error("name cannot be blank");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let admin = db.users.get(args.name);
|
||||
if (!admin) {
|
||||
admin = createUser(args.name);
|
||||
}
|
||||
|
||||
let adminRole = libUsers.ensureDefaultAdminRole(db.roles);
|
||||
admin.roles.add(adminRole);
|
||||
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) {
|
||||
logger.error(`No user named '${args.name}'`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(user.createToken(masterConfig.get("master.auth_secret")));
|
||||
|
||||
} else if (subCommand === "create-ctl-config") {
|
||||
let admin = db.users.get(args.name);
|
||||
if (!admin) {
|
||||
logger.error(`No user named '${args.name}'`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
let controlConfig = new libConfig.ControlConfig();
|
||||
await controlConfig.init();
|
||||
|
||||
controlConfig.set("control.master_url", getMasterUrl());
|
||||
controlConfig.set("control.master_token", admin.createToken(masterConfig.get("master.auth_secret")));
|
||||
|
||||
let content = JSON.stringify(controlConfig.serialize(), null, 4);
|
||||
if (args.output === "-") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(content);
|
||||
} else {
|
||||
logger.info(`Writing ${args.output}`);
|
||||
await fs.outputFile(args.output, content);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
await handleBootstrapCommand(args);
|
||||
return [false, args];
|
||||
}
|
||||
|
||||
// If we get here the command was run
|
||||
return [true, args];
|
||||
}
|
||||
|
||||
async function startServer(args) {
|
||||
// Start webpack development server if enabled
|
||||
if (args.dev) {
|
||||
logger.warn("Webpack development mode enabled");
|
||||
/* eslint-disable global-require */
|
||||
const webpack = require("webpack");
|
||||
const webpackDevMiddleware = require("webpack-dev-middleware");
|
||||
const webpackConfig = require("./webpack.config");
|
||||
/* eslint-enable global-require */
|
||||
|
||||
const compiler = webpack(webpackConfig({}));
|
||||
devMiddleware = webpackDevMiddleware(compiler, {});
|
||||
@@ -1922,6 +1931,7 @@ async function startServer() {
|
||||
process.on("SIGINT", () => {
|
||||
if (secondSigint) {
|
||||
logger.fatal("Caught second interrupt, terminating immediately");
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -1934,6 +1944,7 @@ async function startServer() {
|
||||
process.on("SIGHUP", () => {
|
||||
// No graceful cleanup, no warning out (stdout is likely closed.)
|
||||
// Don't close the terminal with the clusterio master in it.
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1949,7 +1960,8 @@ async function startServer() {
|
||||
let bindAddress = masterConfig.get("master.bind_address") || "";
|
||||
if (!httpPort && !httpsPort) {
|
||||
logger.fatal("Error: at least one of http_port and https_port must be configured");
|
||||
process.exit(1);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let tls_cert = masterConfig.get("master.tls_certificate");
|
||||
@@ -1980,7 +1992,7 @@ async function startServer() {
|
||||
|
||||
// Only start listening for connections after all plugins have loaded
|
||||
if (httpPort) {
|
||||
httpServer = new (require("http").Server)(app);
|
||||
httpServer = http.createServer(app);
|
||||
httpServerCloser = new HttpCloser(httpServer);
|
||||
await listen(httpServer, httpPort, bindAddress);
|
||||
logger.info(`Listening for HTTP on port ${httpServer.address().port}`);
|
||||
@@ -1998,7 +2010,7 @@ async function startServer() {
|
||||
);
|
||||
}
|
||||
|
||||
httpsServer = require("https").createServer({
|
||||
httpsServer = https.createServer({
|
||||
key: privateKey,
|
||||
cert: certificate,
|
||||
}, app);
|
||||
@@ -2008,6 +2020,20 @@ async function startServer() {
|
||||
}
|
||||
}
|
||||
|
||||
async function startup() {
|
||||
// Set the process title, shows up as the title of the CMD window on windows
|
||||
// and as the process name in ps/top on linux.
|
||||
process.title = "clusterioMaster";
|
||||
|
||||
// add better stack traces on promise rejection
|
||||
process.on("unhandledRejection", err => logger.error(`Unhandled rejection:\n${err.stack}`));
|
||||
|
||||
let [shouldRun, args] = await initialize();
|
||||
if (shouldRun) {
|
||||
await startServer(args);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
app,
|
||||
|
||||
@@ -2030,7 +2056,7 @@ I version of clusterio. Expect things to break. I
|
||||
+==========================================================+
|
||||
`
|
||||
);
|
||||
startServer().catch(err => {
|
||||
startup().catch(err => {
|
||||
if (err instanceof libErrors.StartupError) {
|
||||
logger.fatal(`
|
||||
+----------------------------------+
|
||||
|
||||
+76
-78
@@ -118,7 +118,7 @@ const serverSettingsActions = {
|
||||
/**
|
||||
* Keeps track of the runtime parameters of an instance
|
||||
*/
|
||||
class Instance extends libLink.Link{
|
||||
class Instance extends libLink.Link {
|
||||
constructor(slave, connector, dir, factorioDir, instanceConfig) {
|
||||
super("instance", "slave", connector);
|
||||
libLink.attachAllMessages(this);
|
||||
@@ -250,8 +250,8 @@ class Instance extends libLink.Link{
|
||||
|
||||
async _loadPlugin(pluginInfo, slave) {
|
||||
let pluginLoadStarted = Date.now();
|
||||
let { InstancePlugin } = require(path.posix.join(pluginInfo.requirePath, pluginInfo.instanceEntrypoint));
|
||||
let instancePlugin = new InstancePlugin(pluginInfo, this, slave);
|
||||
let InstancePluginClass = await libPluginLoader.loadInstancePluginClass(pluginInfo);
|
||||
let instancePlugin = new InstancePluginClass(pluginInfo, this, slave);
|
||||
this.plugins.set(pluginInfo.name, instancePlugin);
|
||||
await instancePlugin.init();
|
||||
libPlugin.attachPluginMessages(this, pluginInfo, instancePlugin);
|
||||
@@ -380,16 +380,19 @@ class Instance extends libLink.Link{
|
||||
|
||||
this.logger.verbose("Rotating old logs...");
|
||||
// clean old log file to avoid crash
|
||||
try{
|
||||
try {
|
||||
let logPath = this.path("factorio-current.log");
|
||||
let stat = await fs.stat(logPath);
|
||||
if(stat.isFile()){
|
||||
if (stat.isFile()) {
|
||||
let logFilename = `factorio-${Math.floor(Date.parse(stat.mtime)/1000)}.log`;
|
||||
await fs.rename(logPath, this.path(logFilename));
|
||||
this.logger.verbose(`Log rotated as ${logFilename}`);
|
||||
}
|
||||
}catch(e){}
|
||||
} catch (err) {
|
||||
this.logger.error(`Error rotating logs:\n${err.stack}`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
await symlinkMods(this, "sharedMods");
|
||||
}
|
||||
|
||||
@@ -698,6 +701,7 @@ class Instance extends libLink.Link{
|
||||
await this.writeServerSettings();
|
||||
|
||||
this.logger.verbose("Creating save .....");
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
await symlinkMods(this, "sharedMods");
|
||||
|
||||
} catch (err) {
|
||||
@@ -717,6 +721,7 @@ class Instance extends libLink.Link{
|
||||
await this.writeServerSettings();
|
||||
|
||||
this.logger.info("Exporting data .....");
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
await symlinkMods(this, "sharedMods");
|
||||
let zip = await libFactorio.exportData(this.server);
|
||||
|
||||
@@ -918,6 +923,46 @@ class SlaveConnector extends libLink.WebSocketClientConnector {
|
||||
}
|
||||
}
|
||||
|
||||
function checkFilename(name) {
|
||||
// All of these are bad in Windows only, except for /, . and ..
|
||||
// See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||
const badChars = /[<>:"\/\\|?*\x00-\x1f]/g;
|
||||
const badEnd = /[. ]$/;
|
||||
|
||||
const oneToNine = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
const badNames = [
|
||||
// Relative path components
|
||||
".", "..",
|
||||
|
||||
// Reserved filenames in Windows
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
...oneToNine.map(n => `COM${n}`),
|
||||
...oneToNine.map(n => `LPT${n}`),
|
||||
];
|
||||
|
||||
if (typeof name !== "string") {
|
||||
throw new Error("must be a string");
|
||||
}
|
||||
|
||||
if (name === "") {
|
||||
throw new Error("cannot be empty");
|
||||
}
|
||||
|
||||
if (badChars.test(name)) {
|
||||
throw new Error('cannot contain <>:"\\/|=* or control characters');
|
||||
}
|
||||
|
||||
if (badNames.includes(name.toUpperCase())) {
|
||||
throw new Error(
|
||||
"cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9"
|
||||
);
|
||||
}
|
||||
|
||||
if (badEnd.test(name)) {
|
||||
throw new Error("cannot end with . or space");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles running the slave
|
||||
*
|
||||
@@ -962,11 +1007,6 @@ class Slave extends libLink.Link {
|
||||
this.updateInstances().catch((err) => {
|
||||
logger.fatal(`Unexpected error updating instances:\n${err.stack}`);
|
||||
return this.shutdown();
|
||||
|
||||
}).catch((err) => {
|
||||
setBlocking(true);
|
||||
logger.fatal(`Unexpected error during shutdown:\n${err.stack}`);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -980,20 +1020,11 @@ class Slave extends libLink.Link {
|
||||
this.connector.connect().catch((err) => {
|
||||
logger.fatal(`Unexpected error reconnecting to master:\n${err.stack}`);
|
||||
return this.shutdown();
|
||||
|
||||
}).catch((err) => {
|
||||
setBlocking(true);
|
||||
logger.fatal(`Unexpected error during shutdown:\n${err.stack}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
} else {
|
||||
logger.fatal("Master connection was unexpectedly closed");
|
||||
this.shutdown().catch((err) => {
|
||||
setBlocking(true);
|
||||
logger.fatal(`Unexpected error during shutdown:\n${err.stack}`);
|
||||
process.exit(1);
|
||||
});
|
||||
this.shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1364,53 +1395,25 @@ class Slave extends libLink.Link {
|
||||
}
|
||||
}
|
||||
|
||||
for (let instanceId of this.instanceConnections.keys()) {
|
||||
await this.stopInstance(instanceId);
|
||||
try {
|
||||
for (let instanceId of this.instanceConnections.keys()) {
|
||||
await this.stopInstance(instanceId);
|
||||
}
|
||||
await this.connector.close(1001, "Slave Shutdown");
|
||||
|
||||
// Clear silly interval in pidfile library.
|
||||
pidusage.clear();
|
||||
} catch (err) {
|
||||
logger.error(`
|
||||
+--------------------------------------------------------------------+
|
||||
| Unexpected error occured while shutting down slave, please report |
|
||||
| it to https://github.com/clusterio/factorioClusterio/issues |
|
||||
+--------------------------------------------------------------------+
|
||||
${err.stack}`
|
||||
);
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
await this.connector.close(1001, "Slave Shutdown");
|
||||
|
||||
// Clear silly interval in pidfile library.
|
||||
pidusage.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function checkFilename(name) {
|
||||
// All of these are bad in Windows only, except for /, . and ..
|
||||
// See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||
const badChars = /[<>:"\/\\|?*\x00-\x1f]/g;
|
||||
const badEnd = /[. ]$/;
|
||||
|
||||
const oneToNine = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
const badNames = [
|
||||
// Relative path components
|
||||
".", "..",
|
||||
|
||||
// Reserved filenames in Windows
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
...oneToNine.map(n => `COM${n}`),
|
||||
...oneToNine.map(n => `LPT${n}`),
|
||||
];
|
||||
|
||||
if (typeof name !== "string") {
|
||||
throw new Error("must be a string");
|
||||
}
|
||||
|
||||
if (name === "") {
|
||||
throw new Error("cannot be empty");
|
||||
}
|
||||
|
||||
if (badChars.test(name)) {
|
||||
throw new Error('cannot contain <>:"\\/|=* or control characters');
|
||||
}
|
||||
|
||||
if (badNames.includes(name.toUpperCase())) {
|
||||
throw new Error(
|
||||
"cannot be named any of . .. CON PRN AUX NUL COM1-9 and LPT1-9"
|
||||
);
|
||||
}
|
||||
|
||||
if (badEnd.test(name)) {
|
||||
throw new Error("cannot end with . or space");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1453,6 +1456,7 @@ async function symlinkMods(instance, sharedMods) {
|
||||
let target = path.join(sharedMods, entry.name);
|
||||
let link = instance.path("mods", entry.name);
|
||||
|
||||
/* eslint-disable max-depth */
|
||||
if (process.platform !== "win32") {
|
||||
await fs.symlink(path.relative(path.dirname(link), target), link);
|
||||
|
||||
@@ -1464,6 +1468,7 @@ async function symlinkMods(instance, sharedMods) {
|
||||
} else {
|
||||
await fs.link(target, link);
|
||||
}
|
||||
/* eslint-enable max-depth */
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1610,22 +1615,15 @@ async function startSlave() {
|
||||
let secondSigint = false;
|
||||
process.on("SIGINT", () => {
|
||||
if (secondSigint) {
|
||||
setBlocking(true);
|
||||
logger.fatal("Caught second interrupt, terminating immediately");
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
secondSigint = true;
|
||||
logger.info("Caught interrupt signal, shutting down");
|
||||
slave.shutdown().catch(err => {
|
||||
logger.error(`
|
||||
+---------------------------------------------------------------+
|
||||
| Unexpected error occured while stopping slave, please report |
|
||||
| it to https://github.com/clusterio/factorioClusterio/issues |
|
||||
+---------------------------------------------------------------+
|
||||
${err.stack}`
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
slave.shutdown();
|
||||
});
|
||||
|
||||
slaveConnector.once("connect", () => {
|
||||
@@ -1675,6 +1673,6 @@ ${err.stack}`
|
||||
);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
@@ -16,12 +16,16 @@ const { Panel } = Collapse;
|
||||
* Editable - Boolean - Allow editing
|
||||
* AddRecord - Boolean - Allow adding new records
|
||||
* CustomDataFilterFunction - function(tableData) - Filter tabledata and return results
|
||||
* RecordIdField - String - By default we assume the column "Id" is a unique identifier for a record. If that isn't the case, supply the column name here
|
||||
* RecordIdField - String -
|
||||
* By default we assume the column "Id" is a unique identifier for a record. If
|
||||
* that isn't the case, supply the column name here
|
||||
*/
|
||||
class DataTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (!this.props.DataFunction) throw new Error("DataFunction not defined");
|
||||
if (!this.props.DataFunction) {
|
||||
throw new Error("DataFunction not defined");
|
||||
}
|
||||
this.state = {
|
||||
pagination: {
|
||||
pageSize: 100,
|
||||
@@ -40,32 +44,21 @@ class DataTable extends Component {
|
||||
}
|
||||
|
||||
async getData(pagination, filters, sorter) {
|
||||
/*
|
||||
pagination = {
|
||||
pageSize: 10, // items per page
|
||||
current: 1, // page number
|
||||
total: 123, // Total amount of items
|
||||
}
|
||||
filters = {
|
||||
ReportId: 123
|
||||
}
|
||||
sorter = {}
|
||||
*/
|
||||
// pagination = {
|
||||
// pageSize: 10, // items per page
|
||||
// current: 1, // page number
|
||||
// total: 123, // Total amount of items
|
||||
// }
|
||||
// filters = {
|
||||
// ReportId: 123
|
||||
// }
|
||||
// sorter = {}
|
||||
pagination = pagination || this.state.pagination;
|
||||
filters = filters || this.state.filters;
|
||||
sorter = sorter || this.state.sorter;
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
let data = await this.props.DataFunction();
|
||||
// await fetchApi(this.state.dataUrl, "post", {
|
||||
// database: this.props.Database,
|
||||
// table: this.props.Table,
|
||||
// dataSource: this.props.DataSource,
|
||||
// pagination: this.props.RecordCount? {...pagination, pageSize: this.props.RecordCount, current: 1}:pagination,
|
||||
// filters,
|
||||
// sorter,
|
||||
// recordIdField: this.props.RecordIdField || "Id", // Sort by this
|
||||
// })
|
||||
data = data || {};
|
||||
this.setState({
|
||||
// eslint-disable-next-line new-cap
|
||||
@@ -110,20 +103,16 @@ class DataTable extends Component {
|
||||
|
||||
async deleteRecord(record) {
|
||||
delete record.key;
|
||||
// Delete "null" values as they seem to cause deletes to fail silently when the value is null and field type is float
|
||||
// Delete "null" values as they seem to cause deletes to fail silently when
|
||||
// the value is null and field type is float
|
||||
for (let key in record) {
|
||||
if (record[key] === null) delete record[key];
|
||||
if (record[key] === null) { delete record[key]; }
|
||||
}
|
||||
var status = {};
|
||||
// await fetchApi(`/api/${this.props.Server}/tableData/record`, "delete", {
|
||||
// database: this.props.Database,
|
||||
// table: this.props.Table,
|
||||
// dataSource: this.props.DataSource,
|
||||
// record,
|
||||
// }, undefined, true)
|
||||
let status = {};
|
||||
// TODO: Delete data here
|
||||
|
||||
if (status.ok) notify("success", status.msg, "success");
|
||||
if (!status.ok) notify("error", status.msg, "error");
|
||||
if (status.ok) { notify("success", status.msg, "success"); }
|
||||
if (!status.ok) { notify("error", status.msg, "error"); }
|
||||
|
||||
// Force refresh of data
|
||||
this.getData();
|
||||
@@ -133,60 +122,66 @@ class DataTable extends Component {
|
||||
let tableProps = {
|
||||
...this.props.TableProps || {},
|
||||
onRow: (record, rowIndex) => {
|
||||
/*
|
||||
To allow for extending functionality, we take a TableProps argument from props and pass it through to the Table component.
|
||||
Since we are also using onRow internaly here, we handle the externally provided events ourselves by passing along the event
|
||||
object. This way the end user isn't affected by our event handlers.
|
||||
*/
|
||||
// To allow for extending functionality, we take a TableProps argument
|
||||
// from props and pass it through to the Table component. Since we
|
||||
// are also using onRow internaly here, we handle the externally
|
||||
// provided events ourselves by passing along the event object. This
|
||||
// way the end user isn't affected by our event handlers.
|
||||
let externalOnRow = {
|
||||
...(this.props.TableProps && this.props.TableProps.onRow && this.props.TableProps.onRow(record, rowIndex)),
|
||||
...(
|
||||
this.props.TableProps
|
||||
&& this.props.TableProps.onRow
|
||||
&& this.props.TableProps.onRow(record, rowIndex)
|
||||
),
|
||||
};
|
||||
return {
|
||||
onClick: event => {
|
||||
if (event.target.type !== "button") {
|
||||
if (this.props.Editable) this.showEditModal(record, rowIndex);
|
||||
if (externalOnRow.onClick) externalOnRow.onClick(event);
|
||||
if (this.props.Editable) { this.showEditModal(record, rowIndex); }
|
||||
if (externalOnRow.onClick) { externalOnRow.onClick(event); }
|
||||
}
|
||||
},
|
||||
onDoubleClick: event => {
|
||||
if (externalOnRow.onDoubleClick) externalOnRow.onDoubleClick(event);
|
||||
if (externalOnRow.onDoubleClick) { externalOnRow.onDoubleClick(event); }
|
||||
},
|
||||
onContextMenu: event => {
|
||||
if (externalOnRow.onContextMenu) externalOnRow.onContextMenu(event);
|
||||
if (externalOnRow.onContextMenu) { externalOnRow.onContextMenu(event); }
|
||||
},
|
||||
onMouseEnter: event => {
|
||||
if (externalOnRow.onMouseEnter) externalOnRow.onMouseEnter(event);
|
||||
if (externalOnRow.onMouseEnter) { externalOnRow.onMouseEnter(event); }
|
||||
},
|
||||
onMouseLeave: event => {
|
||||
if (externalOnRow.onMouseLeave) externalOnRow.onMouseLeave(event);
|
||||
if (externalOnRow.onMouseLeave) { externalOnRow.onMouseLeave(event); }
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
let columns = this.props.columns || this.inferColumns(this.state.tableData);
|
||||
if (this.props.DeleteRecord && !columns.find(x => x.dataIndex === "notInUse")) columns.push({
|
||||
// Add delete button on the right
|
||||
dataIndex: "notInUse",
|
||||
title: "",
|
||||
render: (text, record) => <Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={() => {
|
||||
this.deleteRecord(record);
|
||||
}}
|
||||
>
|
||||
Slett permanent
|
||||
if (this.props.DeleteRecord && !columns.find(x => x.dataIndex === "notInUse")) {
|
||||
columns.push({
|
||||
// Add delete button on the right
|
||||
dataIndex: "notInUse",
|
||||
title: "",
|
||||
render: (text, record) => <Popover
|
||||
content={
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={() => {
|
||||
this.deleteRecord(record);
|
||||
}}
|
||||
>
|
||||
Slett permanent
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
}
|
||||
trigger="click"
|
||||
>
|
||||
<Button danger style={{ float: "right", fontSize: "16px" }}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Popover>,
|
||||
});
|
||||
</Popover>,
|
||||
});
|
||||
}
|
||||
|
||||
return <>
|
||||
<Row>
|
||||
@@ -203,7 +198,9 @@ class DataTable extends Component {
|
||||
{...tableProps}
|
||||
columns={columns.map(x => ({
|
||||
...x, // Add a 4th parameter to the stock render function with all of our table data
|
||||
render: x.render ? (...originalParams) => x.render(...originalParams, this.state.tableData) : undefined,
|
||||
render: x.render
|
||||
? (...originalParams) => x.render(...originalParams, this.state.tableData)
|
||||
: undefined,
|
||||
}))}
|
||||
dataSource={this.state.tableData}
|
||||
pagination={this.state.pagination.total > 10 ? this.state.pagination : false}
|
||||
@@ -262,7 +259,8 @@ class DataTable extends Component {
|
||||
}
|
||||
|
||||
inferColumns(data) {
|
||||
// Look at the data and find columns and how to display them. Can be overridden by props.Columns if more customization is needed
|
||||
// Look at the data and find columns and how to display them. Can be
|
||||
// overridden by props.Columns if more customization is needed
|
||||
let columns = [];
|
||||
data.forEach(row => {
|
||||
Object.keys(row).filter(x => x !== "key").forEach(key => {
|
||||
|
||||
@@ -25,7 +25,7 @@ module.exports = (env = {}) => ({
|
||||
// Make sure ant-design icons use the ES variant
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@ant-design\/icons\/[A-Z]/,
|
||||
function(resource) {
|
||||
resource => {
|
||||
resource.request = resource.request.replace(/@ant-design\/icons/, "$&/es/icons");
|
||||
}
|
||||
),
|
||||
|
||||
@@ -6,6 +6,28 @@ const libPlugin = require("@clusterio/lib/plugin");
|
||||
const RateLimiter = require("@clusterio/lib/RateLimiter");
|
||||
|
||||
|
||||
async function loadTechnologies(masterConfig, logger) {
|
||||
let filePath = path.join(masterConfig.get("master.database_directory"), "technologies.json");
|
||||
logger.verbose(`Loading ${filePath}`);
|
||||
try {
|
||||
return new Map(JSON.parse(await fs.readFile(filePath)));
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.verbose("Creating new technologies database");
|
||||
return new Map();
|
||||
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTechnologies(masterConfig, technologies, logger) {
|
||||
let filePath = path.join(masterConfig.get("master.database_directory"), "technologies.json");
|
||||
logger.verbose(`writing ${filePath}`);
|
||||
await fs.outputFile(filePath, JSON.stringify([...technologies.entries()], null, 4));
|
||||
}
|
||||
|
||||
class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
async init() {
|
||||
this.technologies = await loadTechnologies(this.master.config, this.logger);
|
||||
@@ -92,9 +114,8 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
let match = /-(\d+)$/.exec(name);
|
||||
if (!match) {
|
||||
return 1;
|
||||
} else {
|
||||
return Number.parseInt(match[1], 10);
|
||||
}
|
||||
return Number.parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
for (let instanceTech of message.data.technologies) {
|
||||
@@ -145,29 +166,6 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTechnologies(masterConfig, logger) {
|
||||
let filePath = path.join(masterConfig.get("master.database_directory"), "technologies.json");
|
||||
logger.verbose(`Loading ${filePath}`);
|
||||
try {
|
||||
return new Map(JSON.parse(await fs.readFile(filePath)));
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.verbose("Creating new technologies database");
|
||||
return new Map();
|
||||
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTechnologies(masterConfig, technologies, logger) {
|
||||
let filePath = path.join(masterConfig.get("master.database_directory"), "technologies.json");
|
||||
logger.verbose(`writing ${filePath}`);
|
||||
await fs.outputFile(filePath, JSON.stringify([...technologies.entries()], null, 4));
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
MasterPlugin,
|
||||
|
||||
@@ -5,19 +5,23 @@ const libHelpers = require("@clusterio/lib/helpers");
|
||||
|
||||
|
||||
const instancePlayerCount = new Gauge(
|
||||
"clusterio_statistics_exporter_instance_player_count", "Amount of players connected to this cluster",
|
||||
"clusterio_statistics_exporter_instance_player_count",
|
||||
"Amount of players connected to this cluster",
|
||||
{ labels: ["instance_id"] }
|
||||
);
|
||||
const instanceGameTicksTotal = new Gauge(
|
||||
"clusterio_statistics_exporter_instance_game_ticks_total", "Game tick an instance has progressed to",
|
||||
"clusterio_statistics_exporter_instance_game_ticks_total",
|
||||
"Game tick an instance has progressed to",
|
||||
{ labels: ["instance_id"] }
|
||||
);
|
||||
const instanceForceFlowStatistics = new Gauge(
|
||||
"clusterio_statistics_exporter_instance_force_flow_statistics", "Items/fluids/enemies/buildings produced/built/killed by a force",
|
||||
"clusterio_statistics_exporter_instance_force_flow_statistics",
|
||||
"Items/fluids/enemies/buildings produced/built/killed by a force",
|
||||
{ labels: ["instance_id", "force", "statistic", "direction", "name"] },
|
||||
);
|
||||
const instanceGameFlowStatistics = new Gauge(
|
||||
"clusterio_statistics_exporter_instance_game_flow_statistics", "Pollution produced/consumed in the game",
|
||||
"clusterio_statistics_exporter_instance_game_flow_statistics",
|
||||
"Pollution produced/consumed in the game",
|
||||
{ labels: ["instance_id", "statistic", "direction", "name"] },
|
||||
);
|
||||
|
||||
|
||||
@@ -27,6 +27,33 @@ const masterInventoryGauge = new Gauge(
|
||||
);
|
||||
|
||||
|
||||
async function loadDatabase(config, logger) {
|
||||
let itemsPath = path.resolve(config.get("master.database_directory"), "items.json");
|
||||
logger.verbose(`Loading ${itemsPath}`);
|
||||
try {
|
||||
let content = await fs.readFile(itemsPath);
|
||||
return new libDatabase.ItemDatabase(JSON.parse(content));
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.verbose("Creating new item database");
|
||||
return new libDatabase.ItemDatabase();
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDatabase(masterConfig, items, logger) {
|
||||
if (items && items.size < 50000) {
|
||||
let file = path.resolve(masterConfig.get("master.database_directory"), "items.json");
|
||||
logger.verbose(`writing ${file}`);
|
||||
let content = JSON.stringify(items.serialize());
|
||||
await fs.outputFile(file, content);
|
||||
} else if (items) {
|
||||
logger.error(`Item database too large, not saving (${items.size})`);
|
||||
}
|
||||
}
|
||||
|
||||
class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
async init() {
|
||||
|
||||
@@ -169,35 +196,6 @@ class MasterPlugin extends libPlugin.BaseMasterPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDatabase(config, logger) {
|
||||
let itemsPath = path.resolve(config.get("master.database_directory"), "items.json");
|
||||
logger.verbose(`Loading ${itemsPath}`);
|
||||
try {
|
||||
let content = await fs.readFile(itemsPath);
|
||||
return new libDatabase.ItemDatabase(JSON.parse(content));
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.verbose("Creating new item database");
|
||||
return new libDatabase.ItemDatabase();
|
||||
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDatabase(masterConfig, items, logger) {
|
||||
if (items && items.size < 50000) {
|
||||
let file = path.resolve(masterConfig.get("master.database_directory"), "items.json");
|
||||
logger.verbose(`writing ${file}`);
|
||||
let content = JSON.stringify(items.serialize());
|
||||
await fs.outputFile(file, content);
|
||||
} else if (items) {
|
||||
logger.error(`Item database too large, not saving (${items.size})`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MasterPlugin,
|
||||
};
|
||||
@@ -12,12 +12,12 @@ function addApiRoutes(app, items, endpointHitCounter) {
|
||||
* @alias api/inventory
|
||||
* @returns {object[]} JSON [{name:"iron-plate", count:100},{name:"copper-plate",count:5}]
|
||||
*/
|
||||
app.get("/api/inventory", function(req, res) {
|
||||
app.get("/api/inventory", (req, res) => {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
// Check it and send it
|
||||
var inventory = [];
|
||||
let inventory = [];
|
||||
for (let [name, count] of items._items) {
|
||||
inventory.push({ name, count });
|
||||
}
|
||||
@@ -33,7 +33,7 @@ function addApiRoutes(app, items, endpointHitCounter) {
|
||||
* @alias api/inventoryAsObject
|
||||
* @returns {object} JSON {"iron-plate":100, "copper-plate":5}
|
||||
*/
|
||||
app.get("/api/inventoryAsObject", function(req, res) {
|
||||
app.get("/api/inventoryAsObject", (req, res) => {
|
||||
endpointHitCounter.labels(req.route.path).inc();
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
|
||||
@@ -173,7 +173,7 @@ describe("Integration of Clusterio", function() {
|
||||
],
|
||||
];
|
||||
|
||||
for (let [prop, value, ,] of testConfigs) {
|
||||
for (let [prop, value] of testConfigs) {
|
||||
value = `"'${JSON.stringify(value).replace(/"/g, process.platform === "win32" ? '""' : '\\"')}'"`;
|
||||
let args = `test factorio.settings ${prop} ${value}`;
|
||||
await execCtl(`instance config set-prop ${args}`);
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("Integration of lib/factorio/server", function() {
|
||||
await fs.ensureDir(writePath);
|
||||
logFile = fs.createWriteStream(path.join(writePath, "log.txt"), "utf8");
|
||||
server.on("output", function(output) {
|
||||
logFile.write(JSON.stringify(output) + "\n");
|
||||
logFile.write(`${JSON.stringify(output)}\n`);
|
||||
});
|
||||
|
||||
// Initialize sever.
|
||||
@@ -71,7 +71,7 @@ describe("Integration of lib/factorio/server", function() {
|
||||
});
|
||||
|
||||
function log(message) {
|
||||
logFile.write("=================== " + message + "\n");
|
||||
logFile.write(`=================== ${message}\n`);
|
||||
}
|
||||
|
||||
describe(".create()", function() {
|
||||
|
||||
@@ -351,8 +351,11 @@ describe("lib/config/classes", function() {
|
||||
assert.deepEqual(testInstance.get("json"), { json: true });
|
||||
|
||||
let errMsg;
|
||||
try { JSON.parse("blah"); }
|
||||
catch (err) { errMsg = err.message; }
|
||||
try {
|
||||
JSON.parse("blah");
|
||||
} catch (err) {
|
||||
errMsg = err.message;
|
||||
}
|
||||
assert.throws(
|
||||
() => testInstance.set("json", "blah"),
|
||||
new classes.InvalidValue(`Error parsing value for json: ${errMsg}`)
|
||||
|
||||
Reference in new issue
Block a user