From a96a8a6d25f868325f3af19fa215a719951f6c58 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 26 Jul 2023 16:03:22 +0200 Subject: [PATCH 1/9] Bump minimum supported Node.js version to v18 The LTS releases for v12 and v14 are already end of life and the v16 is end of life in under two months from now on. Bump supported version up to v18 as it does not make sense to continue to support EOL releases. Even if we wanted to keep supporting them, our dependencies will not. Note that Node.js v18 is currently not avaliable via the NodeSource PPA on Ubuntu 19.10 and below or Debian 9 and below. --- .github/workflows/node.js.yml | 8 ++++---- Dockerfile | 4 ++-- README.md | 10 +++++----- docs/contributing.md | 4 ++-- package.json | 2 +- packages/controller/package.json | 2 +- packages/create/package.json | 2 +- packages/ctl/package.json | 2 +- packages/host/package.json | 2 +- plugins/global_chat/package.json | 2 +- plugins/inventory_sync/package.json | 2 +- plugins/player_auth/package.json | 2 +- plugins/research_sync/package.json | 2 +- plugins/statistics_exporter/package.json | 2 +- plugins/subspace_storage/package.json | 2 +- 15 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 4ac09a14..5cab94b7 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [18.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: @@ -29,13 +29,13 @@ jobs: - run: node packages/lib/build_mod --source-dir packages/host/lua/clusterio_lib --output-dir temp/test/mods - run: wget -q -O temp/test/mods/subspace_storage_1.99.8.zip -L https://github.com/clusterio/subspace_storage/releases/download/1.99.6/subspace_storage_1.99.8.zip - name: Run tests - if: ${{ matrix.node-version != '12.x' }} + if: ${{ matrix.node-version != '18.x' }} run: pnpm test - name: Run coverage - if: ${{ matrix.node-version == '12.x' }} + if: ${{ matrix.node-version == '18.x' }} run: pnpm run ci-cover - name : Upload coverage to Codecov - if: ${{ matrix.node-version == '12.x' }} + if: ${{ matrix.node-version == '18.x' }} uses: codecov/codecov-action@v2 with: files: ./coverage/lcov.info diff --git a/Dockerfile b/Dockerfile index babd6bfa..4f4832f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 as subspace_storage_builder +FROM node:20 as subspace_storage_builder RUN apt update && apt install -y git WORKDIR / RUN git clone https://github.com/clusterio/subspace_storage.git @@ -6,7 +6,7 @@ WORKDIR /subspace_storage RUN npm install \ && node build -FROM node:12 as clusterio_builder +FROM node:20 as clusterio_builder RUN apt update \ && apt install -y wget \ && mkdir /clusterio diff --git a/README.md b/README.md index 7de44352..ee76ddfa 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Check out the documentation on [Writing Plugins](/docs/writing-plugins.md) for w ## Installation -Clusterio runs on Node.js v12 and up, it's distributed via npm and comes with a guided install script. +Clusterio runs on Node.js v18 and up, it's distributed via npm and comes with a guided install script. If you already have a recent Node.js installed, you can set it up in a new directory with: npm init "@clusterio" @@ -99,11 +99,11 @@ Otherwise see below for OS specific instructions. ### Ubuntu setup -1. Install Node.js v12 or higher. - For 20.04 LTS or below the version of Node.js provided by the Ubuntu repos are too old and you will have to use the nodesource PPA, otherwise you may skip the first line. +1. Install Node.js v18 or higher. + Note the version of Node.js provided by the Ubuntu/debian repos are too old and you will have to use the nodesource PPA - wget -qO - https://deb.nodesource.com/setup_14.x | sudo -E bash - - sudo apt install nodejs + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - &&\ + sudo apt-get install -y nodejs 2. Log in as the user you wish to install Clusterio as (do not use root), and then create a new directory and run the Clusterio installer: diff --git a/docs/contributing.md b/docs/contributing.md index 0d767e02..22c92a2b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -181,8 +181,8 @@ The format of this log should be self explanatory, please follow it carefully. ## Supported Node.js and Factorio Versions -Clusterio run on Node.js v12 and up, make sure to check that the Node builtin API's and npm packages you use are supported on these versions. -The GitHub Actions tests are run on whatever version of Node.js v12, v14, and v16 is the default. +Clusterio run on Node.js v16 and up, make sure to check that the Node builtin API's and npm packages you use are supported on these versions. +The GitHub Actions tests are run on whatever version of Node.js v16 and v18 is the default. For Factorio Clusterio 2.0 aims to support version 0.17.69 and up, including the latest experimental release. It's recommended that you use the lua API reference for 0.17.69, as there's no information on what version Factorio API's were introduced in. diff --git a/package.json b/package.json index e5f81591..2fb56dfa 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ ] }, "engines": { - "node": ">=12" + "node": ">=18" }, "devDependencies": { "@clusterio/controller": "workspace:*", diff --git a/packages/controller/package.json b/packages/controller/package.json index c5a94a4d..0d2973d7 100644 --- a/packages/controller/package.json +++ b/packages/controller/package.json @@ -16,7 +16,7 @@ "factorio" ], "engines": { - "node": ">=12" + "node": ">=18" }, "dependencies": { "@clusterio/lib": "^2.0.0-alpha.13", diff --git a/packages/create/package.json b/packages/create/package.json index 2a953042..6a828ccf 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -13,7 +13,7 @@ "clusterio" ], "engines": { - "node": ">=12" + "node": ">=18" }, "dependencies": { "chalk": "^4.1.0", diff --git a/packages/ctl/package.json b/packages/ctl/package.json index b7870b6e..880aae1e 100644 --- a/packages/ctl/package.json +++ b/packages/ctl/package.json @@ -15,7 +15,7 @@ "factorio" ], "engines": { - "node": ">=12" + "node": ">=18" }, "dependencies": { "@clusterio/lib": "^2.0.0-alpha.13", diff --git a/packages/host/package.json b/packages/host/package.json index dbc0a401..0f47fa28 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -15,7 +15,7 @@ "factorio" ], "engines": { - "node": ">=12" + "node": ">=18" }, "dependencies": { "@clusterio/lib": "^2.0.0-alpha.13", diff --git a/plugins/global_chat/package.json b/plugins/global_chat/package.json index 9f571c07..5f558666 100644 --- a/plugins/global_chat/package.json +++ b/plugins/global_chat/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/clusterio/clusterio/issues" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { "@clusterio/lib": "^2.0.0-alpha.0" diff --git a/plugins/inventory_sync/package.json b/plugins/inventory_sync/package.json index ffd6117a..9247f1e4 100644 --- a/plugins/inventory_sync/package.json +++ b/plugins/inventory_sync/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/clusterio/factorioClusterio/issues" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { "@clusterio/lib": "^2.0.0-alpha.0" diff --git a/plugins/player_auth/package.json b/plugins/player_auth/package.json index cc7bf708..1973728d 100644 --- a/plugins/player_auth/package.json +++ b/plugins/player_auth/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/clusterio/clusterio/issues" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { "@clusterio/lib": "^2.0.0-alpha.0" diff --git a/plugins/research_sync/package.json b/plugins/research_sync/package.json index 0200ed37..ff84249d 100644 --- a/plugins/research_sync/package.json +++ b/plugins/research_sync/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/clusterio/clusterio/issues" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { "@clusterio/lib": "^2.0.0-alpha.0" diff --git a/plugins/statistics_exporter/package.json b/plugins/statistics_exporter/package.json index 58d5b51e..d945a612 100644 --- a/plugins/statistics_exporter/package.json +++ b/plugins/statistics_exporter/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/clusterio/clusterio/issues" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { "@clusterio/lib": "^2.0.0-alpha.0" diff --git a/plugins/subspace_storage/package.json b/plugins/subspace_storage/package.json index c523de98..079deb52 100644 --- a/plugins/subspace_storage/package.json +++ b/plugins/subspace_storage/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/clusterio/clusterio/issues" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { "@clusterio/lib": "^2.0.0-alpha.0" From 1a9cbd26b3aa158df91c01b5eb133244920445ba Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 26 Jul 2023 23:24:04 +0200 Subject: [PATCH 2/9] Update non-web dependencies Update non-web related dependencies to their latest version were possible. It appears yargs has changed its parsing of command line options to no longer remove a layer of quotes. --- package.json | 14 +++---- packages/controller/package.json | 28 +++++++------- packages/controller/src/routes.js | 48 ++++++++++++------------ packages/create/package.json | 10 ++--- packages/ctl/package.json | 10 ++--- packages/ctl/src/commands.js | 3 ++ packages/host/package.json | 12 +++--- packages/lib/link/connectors.js | 1 + packages/lib/package.json | 30 +++++++-------- packages/lib/schema.js | 6 +-- packages/web_ui/package.json | 4 +- packages/web_ui/src/bootstrap.jsx | 2 - plugins/global_chat/package.json | 6 +-- plugins/inventory_sync/package.json | 6 +-- plugins/player_auth/package.json | 8 ++-- plugins/research_sync/package.json | 6 +-- plugins/statistics_exporter/package.json | 6 +-- plugins/subspace_storage/package.json | 6 +-- test/integration/clusterio.js | 4 +- 19 files changed, 105 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index 2fb56dfa..67323b9e 100644 --- a/package.json +++ b/package.json @@ -26,16 +26,16 @@ "@clusterio/ctl": "workspace:*", "@clusterio/host": "workspace:*", "@clusterio/lib": "workspace:*", - "eslint": "^8.18.0", + "eslint": "^8.45.0", "eslint-plugin-node": "^11.1.0", - "express": "^4.16.2", + "express": "^4.18.2", "form-data": "^4.0.0", - "fs-extra": "^8.1.0", + "fs-extra": "^11.1.1", "jsdoc": "^4.0.2", - "jsonwebtoken": "^8.2.1", - "jszip": "^3.2.2", - "mocha": "^9.2.2", + "jsonwebtoken": "^9.0.1", + "jszip": "^3.10.0", + "mocha": "^10.2.0", "nyc": "^15.1.0", - "phin": "^3.5.1" + "phin": "^3.7.0" } } diff --git a/packages/controller/package.json b/packages/controller/package.json index 0d2973d7..e78fe6b3 100644 --- a/packages/controller/package.json +++ b/packages/controller/package.json @@ -20,27 +20,27 @@ }, "dependencies": { "@clusterio/lib": "^2.0.0-alpha.13", - "busboy": "^0.3.1", - "compression": "^1.7.2", - "express": "^4.16.2", - "fs-extra": "^8.1.0", - "jsonwebtoken": "^8.2.1", - "jszip": "^3.2.2", + "busboy": "^1.6.0", + "compression": "^1.7.4", + "express": "^4.18.2", + "fs-extra": "^11.1.1", + "jsonwebtoken": "^9.0.1", + "jszip": "^3.10.1", "set-blocking": "^2.0.0", - "webpack-cli": "^4.9.2", - "winston": "^3.3.3", - "winston-daily-rotate-file": "^4.5.5", - "ws": "^7.2.3", - "yargs": "^14.2.0" + "webpack-cli": "^5.1.4", + "winston": "^3.10.0", + "winston-daily-rotate-file": "^4.7.1", + "ws": "^8.13.0", + "yargs": "^17.7.2" }, "devDependencies": { "@clusterio/web_ui": "^2.0.0-alpha.13", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "^5.2.0", - "webpack": "^5.73.0", - "webpack-dev-middleware": "^5.3.1", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-dev-middleware": "^6.1.1", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/controller/src/routes.js b/packages/controller/src/routes.js index 04a98bf1..7f6a6ace 100644 --- a/packages/controller/src/routes.js +++ b/packages/controller/src/routes.js @@ -1,5 +1,5 @@ "use strict"; -const Busboy = require("busboy"); +const busboy = require("busboy"); const crypto = require("crypto"); const events = require("events"); const fs = require("fs-extra"); @@ -304,7 +304,7 @@ async function getStream(req, res) { } stream.source.pipe(res); stream.flowing = true; - res.on("finish", () => { + res.on("close", () => { stream.events.emit("close"); }); clearTimeout(stream.timeout); @@ -342,7 +342,7 @@ async function uploadSave(req, res) { res.status(415).json({ request_errors: ["invalid Content-Type"] }); return; } - let mimeType = match[1].toLowerCase(); + let contentMime = match[1].toLowerCase(); let tasks = []; let errors = []; @@ -381,16 +381,16 @@ async function uploadSave(req, res) { } } - if (mimeType === "multipart/form-data") { + if (contentMime === "multipart/form-data") { await new Promise(resolve => { let fields = {}; - let busboy = new Busboy({ headers: req.headers }); - busboy.on("file", (fieldname, stream, filename, transferEncoding, fileMime) => { + let parser = busboy({ headers: req.headers }); + parser.on("file", (name, stream, { filename, mimeType }) => { if (fields.instanceId === undefined) { requestErrors.push("instance_id must come before files uploaded"); } - if (!zipMimes.includes(fileMime)) { + if (!zipMimes.includes(mimeType)) { requestErrors.push("invalid file Content-Type"); } @@ -403,25 +403,25 @@ async function uploadSave(req, res) { return; } - tasks.push(handleFile(fields.instanceId, stream, filename, fileMime)); + tasks.push(handleFile(fields.instanceId, stream, filename, mimeType)); }); - busboy.on("field", (fieldname, value, fieldnameTruncated, valueTruncated, transferEncoding, valueMime) => { - if (fieldname === "instance_id") { + parser.on("field", (name, value, info) => { + if (name === "instance_id") { fields.instanceId = Number.parseInt(value, 10); if (Number.isNaN(fields.instanceId)) { requestErrors.push("invalid instance_id"); } } }); - busboy.on("finish", resolve); - busboy.on("error", (err) => { + parser.on("close", resolve); + parser.on("error", (err) => { logger.error(`Error parsing multipart request in upload-save:\n${err.stack}`); errors.push(err.message); }); - req.pipe(busboy); + req.pipe(parser); }); - } else if (zipMimes.includes(mimeType)) { + } else if (zipMimes.includes(contentMime)) { let filename = req.query.filename; if (typeof filename !== "string") { requestErrors.push("Missing or invalid filename parameter"); @@ -436,7 +436,7 @@ async function uploadSave(req, res) { if (errors.length || requestErrors.length) { req.resume(); } else { - tasks.push(handleFile(instanceId, req, filename, mimeType)); + tasks.push(handleFile(instanceId, req, filename, contentMime)); } } else { @@ -482,7 +482,7 @@ async function uploadMod(req, res) { res.status(415).json({ request_errors: ["invalid Content-Type"] }); return; } - let mimeType = match[1].toLowerCase(); + let contentMime = match[1].toLowerCase(); let tasks = []; let errors = []; @@ -544,11 +544,11 @@ async function uploadMod(req, res) { } } - if (mimeType === "multipart/form-data") { + if (contentMime === "multipart/form-data") { await new Promise(resolve => { - let busboy = new Busboy({ headers: req.headers }); - busboy.on("file", (fieldname, stream, filename, transferEncoding, fileMime) => { - if (!zipMimes.includes(fileMime)) { + let parser = busboy({ headers: req.headers }); + parser.on("file", (name, stream, { filename, mimeType }) => { + if (!zipMimes.includes(mimeType)) { requestErrors.push("invalid file Content-Type"); } @@ -563,15 +563,15 @@ async function uploadMod(req, res) { tasks.push(handleFile(stream, filename)); }); - busboy.on("finish", resolve); - busboy.on("error", (err) => { + parser.on("close", resolve); + parser.on("error", (err) => { logger.error(`Error parsing multipart request in upload-mod:\n${err.stack}`); errors.push(err.message); }); - req.pipe(busboy); + req.pipe(parser); }); - } else if (zipMimes.includes(mimeType)) { + } else if (zipMimes.includes(contentMime)) { let filename = req.query.filename; if (typeof filename !== "string") { requestErrors.push("Missing or invalid filename parameter"); diff --git a/packages/create/package.json b/packages/create/package.json index 6a828ccf..59f53d62 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -16,11 +16,11 @@ "node": ">=18" }, "dependencies": { - "chalk": "^4.1.0", - "fs-extra": "^8.1.0", - "inquirer": "^8.1.0", - "phin": "^3.5.1", - "yargs": "^14.2.0" + "chalk": "^4.1.2", + "fs-extra": "^11.1.1", + "inquirer": "^8.2.4", + "phin": "^3.7.0", + "yargs": "^17.7.2" }, "publishConfig": { "access": "public" diff --git a/packages/ctl/package.json b/packages/ctl/package.json index 880aae1e..de09a5d0 100644 --- a/packages/ctl/package.json +++ b/packages/ctl/package.json @@ -19,12 +19,12 @@ }, "dependencies": { "@clusterio/lib": "^2.0.0-alpha.13", - "as-table": "^1.0.31", - "fs-extra": "^8.1.0", - "phin": "^3.5.1", + "as-table": "^1.0.55", + "fs-extra": "^11.1.1", + "phin": "^3.7.0", "set-blocking": "^2.0.0", - "winston": "^3.3.3", - "yargs": "^14.2.0" + "winston": "^3.10.0", + "yargs": "^17.7.2" }, "publishConfig": { "access": "public" diff --git a/packages/ctl/src/commands.js b/packages/ctl/src/commands.js index c3a437e5..c4c908bb 100644 --- a/packages/ctl/src/commands.js +++ b/packages/ctl/src/commands.js @@ -381,6 +381,9 @@ instanceConfigCommands.add(new libCommand.Command({ // with the all the layers of quote removal at play is difficult. // See the following table for how to pass "That's a \" quote" in // different environments: + // ----------------------- WARNING ------------------------------- + // This table is most likely invalid due a change in yargs parsing + // --------------------------------------------------------------- // cmd : """""That's a \\"" quote""""" // cmd + npx : """""""""""That's a \\\\"""" quote""""""""""" // PowerShell : '"""""That''s a \\"" quote"""""' diff --git a/packages/host/package.json b/packages/host/package.json index 0f47fa28..71337771 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -19,13 +19,13 @@ }, "dependencies": { "@clusterio/lib": "^2.0.0-alpha.13", - "fs-extra": "^8.1.0", - "phin": "^3.5.1", - "pidusage": "^2.0.18", + "fs-extra": "^11.1.1", + "phin": "^3.7.0", + "pidusage": "^3.0.2", "set-blocking": "^2.0.0", - "winston": "^3.3.3", - "winston-daily-rotate-file": "^4.5.5", - "yargs": "^14.2.0" + "winston": "^3.10.0", + "winston-daily-rotate-file": "^4.7.1", + "yargs": "^17.7.2" }, "publishConfig": { "access": "public" diff --git a/packages/lib/link/connectors.js b/packages/lib/link/connectors.js index a083e2c8..1cb8d2ee 100644 --- a/packages/lib/link/connectors.js +++ b/packages/lib/link/connectors.js @@ -432,6 +432,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector { _doConnect() { let url = new URL(this._url); url.pathname += "api/socket"; + url.protocol = url.protocol.replace("http", "ws"); // Open WebSocket to controller logger.verbose(`Connector | connecting to ${url}`); diff --git a/packages/lib/package.json b/packages/lib/package.json index c6e82c8e..ec67bd67 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -12,23 +12,23 @@ "node": ">=12" }, "dependencies": { - "ajv": "^6.12.6", - "chalk": "^4.1.0", + "ajv": "^8.12.0", + "chalk": "^4.1.2", "fast-deep-equal": "^3.1.3", - "fs-extra": "^8.1.0", - "isomorphic-ws": "^4.0.1", - "jimp": "^0.12.1", - "jsonwebtoken": "^8.2.1", - "jszip": "^3.2.2", - "klaw": "^3.0.0", - "rcon-client": "^4.2.0", - "semver": "^7.1.1", + "fs-extra": "^11.1.1", + "isomorphic-ws": "^5.0.0", + "jimp": "^0.22.8", + "jsonwebtoken": "^9.0.1", + "jszip": "^3.10.1", + "klaw": "^4.1.0", + "rcon-client": "^4.2.3", + "semver": "^7.5.4", "set-blocking": "^2.0.0", - "triple-beam": "^1.3.0", - "winston": "^3.3.3", - "winston-transport": "^4.4.0", - "ws": "^7.2.3", - "yargs": "^14.2.0" + "triple-beam": "^1.4.1", + "winston": "^3.10.0", + "winston-transport": "^4.5.0", + "ws": "^8.13.0", + "yargs": "^17.7.2" }, "publishConfig": { "access": "public" diff --git a/packages/lib/schema.js b/packages/lib/schema.js index a9e362c7..694bfceb 100644 --- a/packages/lib/schema.js +++ b/packages/lib/schema.js @@ -5,11 +5,9 @@ "use strict"; const Ajv = require("ajv"); const ajv = new Ajv({ + allowUnionTypes: true, + strict: "log", verbose: true, - format: "full", - extendRefs: "fail", - strictDefaults: true, - strictKeywords: true, }); module.exports = { diff --git a/packages/web_ui/package.json b/packages/web_ui/package.json index b8b815c9..59103248 100644 --- a/packages/web_ui/package.json +++ b/packages/web_ui/package.json @@ -39,8 +39,8 @@ "style-loader": "^2.0.0", "terser-webpack-plugin": "^5.3.9", "util": "^0.12.3", - "webpack": "^5.73.0", - "webpack-cli": "^4.9.2", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", "webpack-manifest-plugin": "^5.0.0" }, "publishConfig": { diff --git a/packages/web_ui/src/bootstrap.jsx b/packages/web_ui/src/bootstrap.jsx index d07d1ed3..40416a6e 100644 --- a/packages/web_ui/src/bootstrap.jsx +++ b/packages/web_ui/src/bootstrap.jsx @@ -108,8 +108,6 @@ export default async function bootstrap() { let plugins = await loadPlugins(pluginInfos); let wsUrl = new URL(window.webRoot, document.location); - wsUrl.protocol = wsUrl.protocol.replace("http", "ws"); - let controlConnector = new ControlConnector(wsUrl, 120); let control = new Control(controlConnector, plugins); diff --git a/plugins/global_chat/package.json b/plugins/global_chat/package.json index 5f558666..60ecce5f 100644 --- a/plugins/global_chat/package.json +++ b/plugins/global_chat/package.json @@ -22,9 +22,9 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", - "webpack": "^5.36.2", - "webpack-cli": "^4.7.0", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/plugins/inventory_sync/package.json b/plugins/inventory_sync/package.json index 9247f1e4..5c612ed2 100644 --- a/plugins/inventory_sync/package.json +++ b/plugins/inventory_sync/package.json @@ -28,9 +28,9 @@ "antd": "^4.16.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "webpack": "^5.36.2", - "webpack-cli": "^4.7.0", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/plugins/player_auth/package.json b/plugins/player_auth/package.json index 1973728d..f2324d22 100644 --- a/plugins/player_auth/package.json +++ b/plugins/player_auth/package.json @@ -27,12 +27,12 @@ "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", "antd": "^4.16.0", - "phin": "^3.5.1", + "phin": "^3.7.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "webpack": "^5.36.2", - "webpack-cli": "^4.7.0", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/plugins/research_sync/package.json b/plugins/research_sync/package.json index ff84249d..80f03da6 100644 --- a/plugins/research_sync/package.json +++ b/plugins/research_sync/package.json @@ -25,9 +25,9 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", - "webpack": "^5.36.2", - "webpack-cli": "^4.7.0", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/plugins/statistics_exporter/package.json b/plugins/statistics_exporter/package.json index d945a612..f3076d0a 100644 --- a/plugins/statistics_exporter/package.json +++ b/plugins/statistics_exporter/package.json @@ -22,9 +22,9 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", - "webpack": "^5.36.2", - "webpack-cli": "^4.7.0", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/plugins/subspace_storage/package.json b/plugins/subspace_storage/package.json index 079deb52..23664974 100644 --- a/plugins/subspace_storage/package.json +++ b/plugins/subspace_storage/package.json @@ -28,9 +28,9 @@ "antd": "^4.16.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "webpack": "^5.36.2", - "webpack-cli": "^4.7.0", - "webpack-merge": "^5.2.0" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" }, "publishConfig": { "access": "public" diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 48d62417..cb4f1744 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -246,7 +246,7 @@ describe("Integration of Clusterio", function() { let value = JSON.stringify({ lan: true, public: false }).replace( /"/g, process.platform === "win32" ? '""' : '\\"' ); - await execCtl(`instance config set-prop test factorio.settings visibility "'${value}'"`); + await execCtl(`instance config set-prop test factorio.settings visibility "${value}"`); await execCtl("instance config set-prop test factorio.settings require_user_verification false"); }); }); @@ -442,7 +442,7 @@ describe("Integration of Clusterio", function() { ]; for (let [prop, value] of testConfigs) { - value = `"'${JSON.stringify(value).replace(/"/g, process.platform === "win32" ? '""' : '\\"')}'"`; + value = `"${JSON.stringify(value).replace(/"/g, process.platform === "win32" ? '""' : '\\"')}"`; let args = `test factorio.settings ${prop} ${value}`; await execCtl(`instance config set-prop ${args}`); } From 2de029b96e6e07132e17aecdcbcc30c65d0a8f80 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 26 Jul 2023 23:53:08 +0200 Subject: [PATCH 3/9] Fix strict mode warnings from Ajv Add type object to schemas specifying properties and minItems to schemas specifying tuples. Disable strictTuples because it's desirable to be able to add additional items to the tuples in the future. --- packages/lib/data/messages_core.js | 9 +++++---- packages/lib/data/messages_host.js | 5 ++++- packages/lib/data/messages_instance.js | 10 +++++++++- packages/lib/data/messages_user.js | 2 ++ packages/lib/schema.js | 1 + plugins/inventory_sync/messages.js | 2 ++ plugins/research_sync/messages.js | 1 + plugins/subspace_storage/messages.js | 4 ++++ 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/lib/data/messages_core.js b/packages/lib/data/messages_core.js index 2b74c125..1bd925c6 100644 --- a/packages/lib/data/messages_core.js +++ b/packages/lib/data/messages_core.js @@ -67,7 +67,6 @@ class Address { static jsonSchema = { type: "array", minItems: 2, - maxItems: 3, items: [ { type: "integer", minimum: 0, maximum: 4 }, { type: "integer" }, @@ -175,6 +174,7 @@ class HelloData { } static jsonSchema = { + type: "object", required: ["version", "plugins"], properties: { "version": { type: "string" }, @@ -354,14 +354,13 @@ class AccountDetails { } static jsonSchema = { - additionalProperties: false, + type: "object", required: ["name", "roles"], properties: { "name": { type: "string" }, "roles": { type: "array", items: { - additionalProperties: false, type: "object", required: ["name", "id", "permissions"], properties: { @@ -404,7 +403,7 @@ class ReadyData { } static jsonSchema = { - additionalProperties: false, + type: "object", required: ["src", "sessionToken", "sessionTimeout", "heartbeatInterval"], properties: { "src": Address.jsonSchema, @@ -519,6 +518,7 @@ class ContinueData { } static jsonSchema = { + type: "object", required: ["sessionTimeout", "heartbeatInterval"], properties: { "sessionTimeout": { type: "number" }, @@ -701,6 +701,7 @@ class ResponseError { } static jsonSchema = { + type: "object", required: ["message"], properties: { "message": { type: "string" }, diff --git a/packages/lib/data/messages_host.js b/packages/lib/data/messages_host.js index 40b0b855..fe91ed68 100644 --- a/packages/lib/data/messages_host.js +++ b/packages/lib/data/messages_host.js @@ -27,6 +27,7 @@ class HostDetails { } static jsonSchema = { + type: "object", required: ["agent", "version", "name", "id", "connected"], properties: { "agent": { type: "string" }, @@ -156,6 +157,7 @@ class ControllerConnectionEvent { } static jsonSchema = { + type: "object", required: ["event"], properties: { "event": { type: "string" }, @@ -194,6 +196,7 @@ class SyncUserListsEvent { } static jsonSchema = { + type: "object", required: ["adminlist", "banlist", "whitelist"], properties: { "adminlist": { @@ -204,7 +207,7 @@ class SyncUserListsEvent { type: "array", items: { type: "array", - additionalItems: false, + minItems: 2, items: [{ type: "string" }, { type: "string" }], }, }, diff --git a/packages/lib/data/messages_instance.js b/packages/lib/data/messages_instance.js index 967f972a..d7bbaf0c 100644 --- a/packages/lib/data/messages_instance.js +++ b/packages/lib/data/messages_instance.js @@ -182,6 +182,7 @@ class InstanceConfigGetRequest { } static jsonSchema = { + type: "object", required: ["instanceId"], properties: { "instanceId": { type: "integer" }, @@ -235,6 +236,7 @@ class InstanceConfigSetFieldRequest { } static jsonSchema = { + type: "object", required: ["instanceId", "field", "value"], properties: { "instanceId": { type: "integer" }, @@ -274,6 +276,7 @@ class InstanceConfigSetPropRequest { } static jsonSchema = { + type: "object", required: ["instanceId", "field", "prop"], properties: { "instanceId": { type: "integer" }, @@ -306,6 +309,7 @@ class InstanceAssignRequest { } static jsonSchema = { + type: "object", required: ["instanceId", "hostId"], properties: { "instanceId": { type: "number" }, @@ -556,7 +560,7 @@ class InstanceRenameSaveRequest { properties: { "instanceId": { type: "integer" }, "oldName": { type: "string" }, - "newname": { type: "string" }, + "newName": { type: "string" }, }, }; @@ -1167,6 +1171,7 @@ class InstanceBanlistUpdateEvent { } static jsonSchema = { + type: "object", required: ["name", "banned", "reason"], properties: { "name": { type: "string" }, @@ -1198,6 +1203,7 @@ class InstanceAdminlistUpdateEvent { } static jsonSchema = { + type: "object", required: ["name", "admin"], properties: { "name": { type: "string" }, @@ -1228,6 +1234,7 @@ class InstanceWhitelistUpdateEvent { } static jsonSchema = { + type: "object", required: ["name", "whitelisted"], properties: { "name": { type: "string" }, @@ -1266,6 +1273,7 @@ class InstancePlayerUpdateEvent { } static jsonSchema = { + type: "object", required: ["type", "name"], properties: { "type": { type: "string", enum: ["join", "leave", "import"] }, diff --git a/packages/lib/data/messages_user.js b/packages/lib/data/messages_user.js index e45e1b4d..c651ed95 100644 --- a/packages/lib/data/messages_user.js +++ b/packages/lib/data/messages_user.js @@ -18,6 +18,7 @@ class RawPermission { // TODO refactor into lib/user.Permission } static jsonSchema = { + type: "object", required: ["name", "title", "description"], properties: { name: { type: "string" }, @@ -277,6 +278,7 @@ class RawUser { // TODO refactor into lib/user.User type: "array", items: { type: "array", + minItems: 2, items: [{ type: "integer" }, PlayerStats.jsonSchema], }, }, diff --git a/packages/lib/schema.js b/packages/lib/schema.js index 694bfceb..65f90937 100644 --- a/packages/lib/schema.js +++ b/packages/lib/schema.js @@ -7,6 +7,7 @@ const Ajv = require("ajv"); const ajv = new Ajv({ allowUnionTypes: true, strict: "log", + strictTuples: false, verbose: true, }); diff --git a/plugins/inventory_sync/messages.js b/plugins/inventory_sync/messages.js index a7f003c0..f1e58e98 100644 --- a/plugins/inventory_sync/messages.js +++ b/plugins/inventory_sync/messages.js @@ -47,6 +47,7 @@ AcquireRequest.Response = class Response { } static jsonSchema = { + type: "object", required: ["status"], properties: { "status": { @@ -165,6 +166,7 @@ DownloadRequest.Response = class Response { } static jsonSchema = { + type: "object", required: ["playerData"], properties: { "playerData": { type: ["object", "null"] }, diff --git a/plugins/research_sync/messages.js b/plugins/research_sync/messages.js index 33a77078..c131021e 100644 --- a/plugins/research_sync/messages.js +++ b/plugins/research_sync/messages.js @@ -55,6 +55,7 @@ class ProgressEvent { technologies: { type: "array", items: { + type: "object", required: ["name", "level", "progress"], properties: { name: { type: "string" }, diff --git a/plugins/subspace_storage/messages.js b/plugins/subspace_storage/messages.js index 643b8cf4..44a8ec8b 100644 --- a/plugins/subspace_storage/messages.js +++ b/plugins/subspace_storage/messages.js @@ -46,6 +46,7 @@ class PlaceEvent { } static jsonSchema = { + type: "object", required: ["items"], properties: { "items": { @@ -74,6 +75,7 @@ class RemoveRequest { } static jsonSchema = { + type: "object", required: ["items"], properties: { "items": { @@ -113,6 +115,7 @@ class UpdateStorageEvent { } static jsonSchema = { + type: "object", required: ["items"], properties: { "items": { @@ -142,6 +145,7 @@ class SetStorageSubscriptionRequest { } static jsonSchema = { + type: "object", required: ["storage"], properties: { "storage": { type: "boolean" }, From a1c9bc5f9bebbd69ce38e4f0096fdb9204c56d3d Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 27 Jul 2023 13:42:35 +0200 Subject: [PATCH 4/9] Migrate deprecated ant design APIs Rename visible to open and use menu instead of overlay in Dropdown as both of these have been removed in the next major version of Antd. --- .../src/components/AssignInstanceModal.jsx | 16 ++++------ .../web_ui/src/components/CreateSaveModal.jsx | 10 +++---- packages/web_ui/src/components/HostsPage.jsx | 12 ++++---- .../src/components/InstanceViewPage.jsx | 12 ++++---- .../web_ui/src/components/InstancesPage.jsx | 10 +++---- .../src/components/LoadScenarioModal.jsx | 10 +++---- .../web_ui/src/components/ModPackViewPage.jsx | 12 ++++---- packages/web_ui/src/components/ModsPage.jsx | 20 ++++++------- packages/web_ui/src/components/RolesPage.jsx | 10 +++---- packages/web_ui/src/components/SavesList.jsx | 30 +++++++++---------- packages/web_ui/src/components/SiteLayout.jsx | 14 ++++----- packages/web_ui/src/components/UsersPage.jsx | 10 +++---- 12 files changed, 81 insertions(+), 85 deletions(-) diff --git a/packages/web_ui/src/components/AssignInstanceModal.jsx b/packages/web_ui/src/components/AssignInstanceModal.jsx index f64edfbc..05049f41 100644 --- a/packages/web_ui/src/components/AssignInstanceModal.jsx +++ b/packages/web_ui/src/components/AssignInstanceModal.jsx @@ -11,20 +11,16 @@ const { Paragraph } = Typography; export default function AssignInstanceModal(props) { - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [hostList] = useHostList(); let [applying, setApplying] = useState(false); let [form] = Form.useForm(); let control = useContext(ControlContext); - function open() { - setVisible(true); - } - function handleAssign() { let hostId = form.getFieldValue("host"); if (hostId === undefined) { - setVisible(false); + setOpen(false); return; } @@ -36,7 +32,7 @@ export default function AssignInstanceModal(props) { control.send( new libData.InstanceAssignRequest(props.id, hostId) ).then(() => { - setVisible(false); + setOpen(false); if (props.onFinish) { props.onFinish(); } @@ -48,17 +44,17 @@ export default function AssignInstanceModal(props) { } function handleCancel() { - setVisible(false); + setOpen(false); } return <> - { form.resetFields(); - setVisible(false); + setOpen(false); }).catch( notifyErrorHandler("Error creating save") ).finally(() => { @@ -81,7 +81,7 @@ export default function CreateSaveModal(props) { } function handleCancel() { - setVisible(false); + setOpen(false); } return <> @@ -89,7 +89,7 @@ export default function CreateSaveModal(props) { loading={creatingSave} disabled={props.instance.status !== "stopped"} onClick={() => { - setVisible(true); + setOpen(true); }} > Create save @@ -98,7 +98,7 @@ export default function CreateSaveModal(props) { title="Create Save" width={560} okText="Create" - visible={visible} + open={open} confirmLoading={creatingSave} onOk={createSave} onCancel={handleCancel} diff --git a/packages/web_ui/src/components/HostsPage.jsx b/packages/web_ui/src/components/HostsPage.jsx index afb8c2dd..e58d582a 100644 --- a/packages/web_ui/src/components/HostsPage.jsx +++ b/packages/web_ui/src/components/HostsPage.jsx @@ -17,7 +17,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba function GenerateHostTokenButton(props) { let control = useContext(ControlContext); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [token, setToken] = useState(null); let [hostId, setHostId] = useState(null); let [form] = Form.useForm(); @@ -53,23 +53,23 @@ function GenerateHostTokenButton(props) { // Generate a new random useEffect(() => { - if (visible) { + if (open) { generateToken().catch(notifyErrorHandler("Error generating token")); } - }, [visible]); + }, [open]); // Only install plugins that aren't filesystem paths. Npm modules have max 1 forward slash in their name. const pluginString = pluginList.map(p => `"${p.requirePath}"`).filter(x => x.split("/") <= 1).join(" "); return <> { - setVisible(false); + setOpen(false); setToken(null); form.resetFields(); }} diff --git a/packages/web_ui/src/components/InstanceViewPage.jsx b/packages/web_ui/src/components/InstanceViewPage.jsx index 74a74335..1c5330df 100644 --- a/packages/web_ui/src/components/InstanceViewPage.jsx +++ b/packages/web_ui/src/components/InstanceViewPage.jsx @@ -126,9 +126,9 @@ export default function InstanceViewPage(props) { label: "Delete", }); } - let instanceButtonsMenu = { + let instanceButtonsMenuProps = { + items: instanceButtonMenuItems, + onClick: ({ key }) => { if (key === "export") { setExportingData(true); control.sendTo( @@ -167,8 +167,8 @@ export default function InstanceViewPage(props) { }, }); } - }} - />; + }, + }; let instanceButtons = { account.hasAnyPermission("core.instance.start", "core.instance.stop") @@ -180,7 +180,7 @@ export default function InstanceViewPage(props) { "core.instance.extract_players", "core.instance.kill", "core.instance.delete", - ) && + ) && } ; diff --git a/packages/web_ui/src/components/InstancesPage.jsx b/packages/web_ui/src/components/InstancesPage.jsx index b943c04e..1a33e9d7 100644 --- a/packages/web_ui/src/components/InstancesPage.jsx +++ b/packages/web_ui/src/components/InstancesPage.jsx @@ -15,7 +15,7 @@ import { notifyErrorHandler } from "../util/notify"; function CreateInstanceButton(props) { let control = useContext(ControlContext); let history = useHistory(); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); async function createInstance() { @@ -30,7 +30,7 @@ function CreateInstanceButton(props) { instanceConfig.set("instance.name", values.instanceName); let serializedConfig = instanceConfig.serialize("controller"); await control.send(new libData.InstanceCreateRequest(serializedConfig)); - setVisible(false); + setOpen(false); history.push(`/instances/${instanceConfig.get("instance.id")}/view`); } @@ -38,15 +38,15 @@ function CreateInstanceButton(props) { { createInstance().catch(notifyErrorHandler("Error creating instance")); }} - onCancel={() => { setVisible(false); }} + onCancel={() => { setOpen(false); }} destroyOnClose >
diff --git a/packages/web_ui/src/components/LoadScenarioModal.jsx b/packages/web_ui/src/components/LoadScenarioModal.jsx index cc290281..e4fba49d 100644 --- a/packages/web_ui/src/components/LoadScenarioModal.jsx +++ b/packages/web_ui/src/components/LoadScenarioModal.jsx @@ -8,7 +8,7 @@ import { notifyErrorHandler } from "../util/notify"; export default function LoadScenarioModal(props) { - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [loadingScenario, setLoadingScenario] = useState(false); let [form] = Form.useForm(); let control = useContext(ControlContext); @@ -55,7 +55,7 @@ export default function LoadScenarioModal(props) { { instanceId: props.instance.id }, new libData.InstanceLoadScenarioRequest(scenario, seed, mapGenSettings, mapSettings), ).then(() => { - setVisible(false); + setOpen(false); }).catch( notifyErrorHandler("Error loading scenario") ).finally(() => { @@ -80,7 +80,7 @@ export default function LoadScenarioModal(props) { } function handleCancel() { - setVisible(false); + setOpen(false); } return <> @@ -88,7 +88,7 @@ export default function LoadScenarioModal(props) { loading={loadingScenario} disabled={props.instance.status !== "stopped"} onClick={() => { - setVisible(true); + setOpen(true); }} > Load scenario @@ -97,7 +97,7 @@ export default function LoadScenarioModal(props) { title="Load Scenario" width={560} okText="Load" - visible={visible} + open={open} confirmLoading={loadingScenario} onOk={loadScenario} onCancel={handleCancel} diff --git a/packages/web_ui/src/components/ModPackViewPage.jsx b/packages/web_ui/src/components/ModPackViewPage.jsx index 3a8a4b26..29a56351 100644 --- a/packages/web_ui/src/components/ModPackViewPage.jsx +++ b/packages/web_ui/src/components/ModPackViewPage.jsx @@ -485,7 +485,7 @@ function CopyButton(props) { }, []); let denied = clipboard.readPermissionState === "denied"; - return + return + - + { setVisible(false); }} + onCancel={() => { setOpen(false); }} onOk={() => { (async () => { let values; @@ -74,19 +74,19 @@ function ImportModPackButton() { function CreateModPackButton() { let control = useContext(ControlContext); let history = useHistory(); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); function close() { - setVisible(false); + setOpen(false); } return <> - + { setVisible(false); }} + onCancel={() => { setOpen(false); }} onOk={() => { (async () => { let values; diff --git a/packages/web_ui/src/components/RolesPage.jsx b/packages/web_ui/src/components/RolesPage.jsx index 62eca08e..e78658bc 100644 --- a/packages/web_ui/src/components/RolesPage.jsx +++ b/packages/web_ui/src/components/RolesPage.jsx @@ -16,7 +16,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba function CreateRoleButton() { let control = useContext(ControlContext); let history = useHistory(); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); async function createRole() { @@ -29,21 +29,21 @@ function CreateRoleButton() { let result = await control.send( new libData.RoleCreateRequest(values.roleName, values.description || "", []) ); - setVisible(false); + setOpen(false); history.push(`/roles/${result.id}/view`); } return <> { createRole().catch(notifyErrorHandler("Error creating role")); }} - onCancel={() => { setVisible(false); }} + onCancel={() => { setOpen(false); }} destroyOnClose > diff --git a/packages/web_ui/src/components/SavesList.jsx b/packages/web_ui/src/components/SavesList.jsx index 04b908d1..50a72ed0 100644 --- a/packages/web_ui/src/components/SavesList.jsx +++ b/packages/web_ui/src/components/SavesList.jsx @@ -19,17 +19,17 @@ import { notifyErrorHandler } from "../util/notify"; function RenameModal(props) { let control = useContext(ControlContext); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); return <> - + form.submit()} - onCancel={() => setVisible(false)} + onCancel={() => setOpen(false)} > { - setVisible(false); + setOpen(false); form.resetFields(); }).catch(notifyErrorHandler("Error renaming save")); }} @@ -58,17 +58,17 @@ function RenameModal(props) { function CopyModal(props) { let control = useContext(ControlContext); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); return <> - + form.submit()} - onCancel={() => setVisible(false)} + onCancel={() => setOpen(false)} > { - setVisible(false); + setOpen(false); form.resetFields(); }).catch(notifyErrorHandler("Error copying save")); }} @@ -98,18 +98,18 @@ function CopyModal(props) { function TransferModal(props) { let account = useAccount(); let control = useContext(ControlContext); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); let [instanceList] = useInstanceList(); return <> - + form.submit()} - onCancel={() => setVisible(false)} + onCancel={() => setOpen(false)} destroyOnClose > { - setVisible(false); + setOpen(false); let hide = message.loading("Transferring save...", 0); control.send(new libData.InstanceTransferSaveRequest( props.instanceId, diff --git a/packages/web_ui/src/components/SiteLayout.jsx b/packages/web_ui/src/components/SiteLayout.jsx index 2d83b3d5..70cc2a35 100644 --- a/packages/web_ui/src/components/SiteLayout.jsx +++ b/packages/web_ui/src/components/SiteLayout.jsx @@ -26,20 +26,20 @@ export default function SiteLayout(props) { return null; } - let accountMenu = { + let accountMenuProps = { + onClick: ({ key }) => { if (key === "user") { history.push(`/users/${account.name}/view`); } else if (key === "logOut") { account.logOut(); } - }} - items={[ + }, + items: [ { label: account.name, key: "user" }, { type: "divider" }, { label: "Log out", danger: true, key: "logOut" }, - ]} - />; + ], + }; let combinedPages = [...pages]; for (let plugin of plugins.values()) { @@ -88,7 +88,7 @@ export default function SiteLayout(props) { className="account-dropdown-header" placement="bottomRight" trigger="click" - overlay={accountMenu} + menu={accountMenuProps} > diff --git a/packages/web_ui/src/components/UsersPage.jsx b/packages/web_ui/src/components/UsersPage.jsx index 1d0d82ad..6475e2c5 100644 --- a/packages/web_ui/src/components/UsersPage.jsx +++ b/packages/web_ui/src/components/UsersPage.jsx @@ -18,7 +18,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba function CreateUserButton() { let control = useContext(ControlContext); let history = useHistory(); - let [visible, setVisible] = useState(false); + let [open, setOpen] = useState(false); let [form] = Form.useForm(); async function createUser() { @@ -29,21 +29,21 @@ function CreateUserButton() { } await control.send(new libData.UserCreateRequest(values.userName)); - setVisible(false); + setOpen(false); history.push(`/users/${values.userName}/view`); } return <> { createUser().catch(notifyErrorHandler("Error creating user")); }} - onCancel={() => { setVisible(false); }} + onCancel={() => { setOpen(false); }} destroyOnClose > From 789c02b5c7820f4de1acc16001a74a7234f348e9 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 27 Jul 2023 13:43:32 +0200 Subject: [PATCH 5/9] Fix missing return in applyModPackChanges --- packages/web_ui/src/components/ModPackViewPage.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web_ui/src/components/ModPackViewPage.jsx b/packages/web_ui/src/components/ModPackViewPage.jsx index 29a56351..a9947885 100644 --- a/packages/web_ui/src/components/ModPackViewPage.jsx +++ b/packages/web_ui/src/components/ModPackViewPage.jsx @@ -593,6 +593,7 @@ function applyModPackChanges(modPack, changes) { throw new Error(`Unknown change type ${change.type}`); } } + return modifiedModPack; } export default function ModPackViewPage() { From 8d98eaf3a59548d25ad40d931b14abde72fe1a0d Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 27 Jul 2023 17:37:27 +0200 Subject: [PATCH 6/9] Reimplement PageHeader Implement our own version ofwhe PageHeader component, due to it being removed from ant design v5. --- .../web_ui/src/components/HostViewPage.jsx | 13 +++------ packages/web_ui/src/components/HostsPage.jsx | 4 +-- .../src/components/InstanceViewPage.jsx | 4 +-- .../web_ui/src/components/InstancesPage.jsx | 4 +-- .../web_ui/src/components/ModPackViewPage.jsx | 14 +++------- packages/web_ui/src/components/ModsPage.jsx | 8 ++---- packages/web_ui/src/components/PageHeader.jsx | 8 ++++++ .../web_ui/src/components/RoleViewPage.jsx | 14 +++------- packages/web_ui/src/components/RolesPage.jsx | 4 +-- .../web_ui/src/components/UserViewPage.jsx | 14 +++------- packages/web_ui/src/components/UsersPage.jsx | 4 +-- packages/web_ui/src/index.css | 28 ++++++++++++++----- packages/web_ui/src/index.jsx | 1 + 13 files changed, 59 insertions(+), 61 deletions(-) create mode 100644 packages/web_ui/src/components/PageHeader.jsx diff --git a/packages/web_ui/src/components/HostViewPage.jsx b/packages/web_ui/src/components/HostViewPage.jsx index 51f1db69..7548be88 100644 --- a/packages/web_ui/src/components/HostViewPage.jsx +++ b/packages/web_ui/src/components/HostViewPage.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { Descriptions, PageHeader, Spin, Tag, Typography } from "antd"; +import { Descriptions, Spin, Tag, Typography } from "antd"; import { useParams } from "react-router-dom"; import InstanceList from "./InstanceList"; @@ -7,6 +7,7 @@ import LogConsole from "./LogConsole"; import { useAccount } from "../model/account"; import { useInstanceList } from "../model/instance"; import { useHost } from "../model/host"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; @@ -29,19 +30,13 @@ export default function HostViewPage(props) { if (host.missing) { return - +

Host with id {hostId} was not found on the controller.

; } return - + {host["name"]} diff --git a/packages/web_ui/src/components/HostsPage.jsx b/packages/web_ui/src/components/HostsPage.jsx index e58d582a..4163ce54 100644 --- a/packages/web_ui/src/components/HostsPage.jsx +++ b/packages/web_ui/src/components/HostsPage.jsx @@ -1,12 +1,13 @@ import React, { useContext, useRef, useState, useEffect } from "react"; import { useHistory } from "react-router-dom"; -import { Button, Form, InputNumber, Modal, PageHeader, Table, Tag, Typography } from "antd"; +import { Button, Form, InputNumber, Modal, Table, Tag, Typography } from "antd"; import CopyOutlined from "@ant-design/icons/lib/icons/CopyOutlined"; import { libData } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import { useHostList } from "../model/host"; @@ -182,7 +183,6 @@ export default function HostsPage() { return } /> diff --git a/packages/web_ui/src/components/InstanceViewPage.jsx b/packages/web_ui/src/components/InstanceViewPage.jsx index 1c5330df..8ff8ccf0 100644 --- a/packages/web_ui/src/components/InstanceViewPage.jsx +++ b/packages/web_ui/src/components/InstanceViewPage.jsx @@ -1,6 +1,6 @@ import React, { useContext, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; -import { Alert, Button, Descriptions, Dropdown, Menu, Modal, PageHeader, Space, Spin, Typography } from "antd"; +import { Alert, Button, Descriptions, Dropdown, Menu, Modal, Space, Spin, Typography } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import DownOutlined from "@ant-design/icons/DownOutlined"; @@ -8,6 +8,7 @@ import { libData } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import InstanceConfigTree from "./InstanceConfigTree"; @@ -187,7 +188,6 @@ export default function InstanceViewPage(props) { return diff --git a/packages/web_ui/src/components/InstancesPage.jsx b/packages/web_ui/src/components/InstancesPage.jsx index 1a33e9d7..72670aca 100644 --- a/packages/web_ui/src/components/InstancesPage.jsx +++ b/packages/web_ui/src/components/InstancesPage.jsx @@ -1,11 +1,12 @@ import React, { useContext, useState } from "react"; import { useHistory } from "react-router-dom"; -import { Button, Form, Input, Modal, PageHeader } from "antd"; +import { Button, Form, Input, Modal } from "antd"; import { libConfig, libData } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import { useInstanceList } from "../model/instance"; @@ -65,7 +66,6 @@ export default function InstancesPage() { return {account.hasPermission("core.instance.create") && } diff --git a/packages/web_ui/src/components/ModPackViewPage.jsx b/packages/web_ui/src/components/ModPackViewPage.jsx index a9947885..ed2e117c 100644 --- a/packages/web_ui/src/components/ModPackViewPage.jsx +++ b/packages/web_ui/src/components/ModPackViewPage.jsx @@ -1,7 +1,7 @@ import React, { Fragment, memo, useCallback, useEffect, useContext, useRef, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; import { - Button, Card, Checkbox, Col, ConfigProvider, Descriptions, Form, Input, PageHeader, Pagination, + Button, Card, Checkbox, Col, ConfigProvider, Descriptions, Form, Input, Pagination, Popconfirm, Row, Table, Tag, Typography, Select, Skeleton, Space, Spin, Switch, Modal, Tooltip, } from "antd"; import InfoCircleOutlined from "@ant-design/icons/InfoCircleOutlined"; @@ -20,6 +20,7 @@ import { useModPack } from "../model/mod_pack"; import { useModList } from "../model/mods"; import { useClipboard } from "../util/clipboard"; import notify, { notifyErrorHandler } from "../util/notify"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import SectionHeader from "./SectionHeader"; import ModDetails from "./ModDetails"; @@ -682,27 +683,20 @@ export default function ModPackViewPage() { let nav = [{ name: "Mods", path: "/mods" }, { name: "Mod Packs" }, { name: modPack.name || modPackId }]; if (modifiedModPack.loading) { return - + ; } if (modifiedModPack.missing) { return - +

Mod pack with id {modPackId} was not found on the controller.

; } return diff --git a/packages/web_ui/src/components/ModsPage.jsx b/packages/web_ui/src/components/ModsPage.jsx index 041168f5..15dd9a49 100644 --- a/packages/web_ui/src/components/ModsPage.jsx +++ b/packages/web_ui/src/components/ModsPage.jsx @@ -1,6 +1,6 @@ import React, { Fragment, useState, useContext } from "react"; import { useHistory } from "react-router-dom"; -import { Button, Form, Input, Modal, PageHeader, Popconfirm, Space, Table, Typography, Upload } from "antd"; +import { Button, Form, Input, Modal, Popconfirm, Space, Table, Typography, Upload } from "antd"; import ImportOutlined from "@ant-design/icons/ImportOutlined"; import PlusOutlined from "@ant-design/icons/PlusOutlined"; import UploadOutlined from "@ant-design/icons/UploadOutlined"; @@ -12,6 +12,7 @@ import { useModList } from "../model/mods"; import { useModPackList } from "../model/mod_pack"; import { notifyErrorHandler } from "../util/notify"; import ControlContext from "./ControlContext"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import SectionHeader from "./SectionHeader"; @@ -184,10 +185,7 @@ export default function ModsPage() { } return - + diff --git a/packages/web_ui/src/components/PageHeader.jsx b/packages/web_ui/src/components/PageHeader.jsx new file mode 100644 index 00000000..14c42271 --- /dev/null +++ b/packages/web_ui/src/components/PageHeader.jsx @@ -0,0 +1,8 @@ +import React from "react"; + +export default function PageHeader(props) { + return
+

{props.title}

+ {props.extra &&
{props.extra}
} +
; +} diff --git a/packages/web_ui/src/components/RoleViewPage.jsx b/packages/web_ui/src/components/RoleViewPage.jsx index a8f8248c..275fed31 100644 --- a/packages/web_ui/src/components/RoleViewPage.jsx +++ b/packages/web_ui/src/components/RoleViewPage.jsx @@ -1,12 +1,13 @@ import React, { useEffect, useContext, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; -import { Button, Checkbox, Form, Input, PageHeader, Popconfirm, Spin } from "antd"; +import { Button, Checkbox, Form, Input, Popconfirm, Spin } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import { libData, libUsers } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import { notifyErrorHandler } from "../util/notify"; @@ -53,20 +54,14 @@ export default function RoleViewPage() { let nav = [{ name: "Roles", path: "/roles" }, { name: role["name"] || roleId }]; if (role.loading) { return - + ; } if (role.missing) { return - +

Role with id {roleId} was not found on the controller.

; } @@ -101,7 +96,6 @@ export default function RoleViewPage() { }} > {canUpdate && } diff --git a/packages/web_ui/src/components/RolesPage.jsx b/packages/web_ui/src/components/RolesPage.jsx index e78658bc..0cd509f1 100644 --- a/packages/web_ui/src/components/RolesPage.jsx +++ b/packages/web_ui/src/components/RolesPage.jsx @@ -1,12 +1,13 @@ import React, { useContext, useEffect, useState } from "react"; import { useHistory } from "react-router-dom"; -import { Button, Form, Input, Modal, PageHeader, Table } from "antd"; +import { Button, Form, Input, Modal, Table } from "antd"; import { libData } from "@clusterio/lib"; import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import { notifyErrorHandler } from "../util/notify"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; @@ -73,7 +74,6 @@ export default function RolesPage() { return } /> diff --git a/packages/web_ui/src/components/UserViewPage.jsx b/packages/web_ui/src/components/UserViewPage.jsx index 76b203f6..e8740b61 100644 --- a/packages/web_ui/src/components/UserViewPage.jsx +++ b/packages/web_ui/src/components/UserViewPage.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useContext, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; import { - Button, Col, Descriptions, Form, Input, PageHeader, Popconfirm, + Button, Col, Descriptions, Form, Input, Popconfirm, Row, Table, Tag, Select, Space, Spin, Switch, } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; @@ -11,6 +11,7 @@ import { libErrors, libData } from "@clusterio/lib"; import { useAccount } from "../model/account"; import { useInstanceList } from "../model/instance"; import ControlContext from "./ControlContext"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import SectionHeader from "./SectionHeader"; @@ -62,20 +63,14 @@ export default function UserViewPage() { let nav = [{ name: "Users", path: "/users" }, { name: userName }]; if (user.loading) { return - + ; } if (user.missing) { return - +

User with name {userName} was not found on the controller.

; } @@ -112,7 +107,6 @@ export default function UserViewPage() { return {userName} diff --git a/packages/web_ui/src/components/UsersPage.jsx b/packages/web_ui/src/components/UsersPage.jsx index 6475e2c5..cfc72df3 100644 --- a/packages/web_ui/src/components/UsersPage.jsx +++ b/packages/web_ui/src/components/UsersPage.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useContext, useState } from "react"; import { useHistory } from "react-router-dom"; -import { Button, Form, Input, Modal, PageHeader, Space, Table, Tag } from "antd"; +import { Button, Form, Input, Modal, Space, Table, Tag } from "antd"; import { libData } from "@clusterio/lib"; @@ -8,6 +8,7 @@ import { useAccount } from "../model/account"; import ControlContext from "./ControlContext"; import { notifyErrorHandler } from "../util/notify"; import { formatDuration } from "../util/time_format"; +import PageHeader from "./PageHeader"; import PageLayout from "./PageLayout"; import PluginExtra from "./PluginExtra"; import { formatLastSeen, sortLastSeen, useUserList } from "../model/user"; @@ -73,7 +74,6 @@ export default function UsersPage() { return } /> diff --git a/packages/web_ui/src/index.css b/packages/web_ui/src/index.css index 267f11bf..12c56d59 100644 --- a/packages/web_ui/src/index.css +++ b/packages/web_ui/src/index.css @@ -67,29 +67,43 @@ body { padding-right: 64px; } -.site-page-header { +.page-header { padding: 0 0 16px; } -.section-header { +.site-layout-content > h2:first-child { + margin-top: 0; +} + +.section-header, .page-header { display: flex; flex-wrap: wrap; align-items: center; +} + +.section-header { margin-top: 16px; margin-bottom: 8px; } -.section-header-title { +.section-header-title, .page-header-title { flex: auto; overflow: hidden; - font-weight: bold; - font-size: 16px; - line-height: 1.5715; white-space: nowrap; text-overflow: ellipsis; } -.section-header-extra { +.section-header-title { + font-weight: bold; + font-size: 16px; + line-height: 1.5715; +} + +.page-header-title { + margin: 4px 0; +} + +.section-header-extra, .page-header-extra { margin-left: auto; } diff --git a/packages/web_ui/src/index.jsx b/packages/web_ui/src/index.jsx index 70bdc2e0..868f907f 100644 --- a/packages/web_ui/src/index.jsx +++ b/packages/web_ui/src/index.jsx @@ -23,6 +23,7 @@ export { default as InstanceStatusTag } from "./components/InstanceStatusTag"; export { default as StartStopInstanceButton } from "./components/StartStopInstanceButton"; export { statusColors } from "./components/InstanceStatusTag"; +export { default as PageHeader } from "./components/PageHeader"; export { default as PageLayout } from "./components/PageLayout"; export { default as SavesList } from "./components/SavesList"; export { default as SectionHeader } from "./components/SectionHeader"; From fd709fd98cf3571d8698bcf67dd67dafecd4a347 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 27 Jul 2023 17:44:10 +0200 Subject: [PATCH 7/9] Update web dependencies - react: Update to new root rendering method - react-router: Replace useHistory with useNavigate - react-router: Replace with - reimplement sticky notice from scratch in ModPackViewPage - remove isomorphic-ws as it's latest version doesn't work and only saves one line of code. - remove import of antd.dark.css - fix misc css breakages. --- packages/controller/package.json | 6 +- packages/lib/link/connectors.js | 3 +- packages/lib/package.json | 1 - packages/web_ui/package.json | 28 ++++----- packages/web_ui/src/bootstrap.jsx | 5 +- packages/web_ui/src/components/App.jsx | 16 ++++-- packages/web_ui/src/components/HostsPage.jsx | 6 +- .../web_ui/src/components/InstanceList.jsx | 6 +- .../src/components/InstanceViewPage.jsx | 8 +-- .../web_ui/src/components/InstancesPage.jsx | 6 +- .../web_ui/src/components/ModPackViewPage.jsx | 57 +++++++++---------- packages/web_ui/src/components/ModsPage.jsx | 14 ++--- .../web_ui/src/components/PluginsPage.jsx | 6 +- .../web_ui/src/components/RoleViewPage.jsx | 6 +- packages/web_ui/src/components/RolesPage.jsx | 10 ++-- packages/web_ui/src/components/SiteLayout.jsx | 32 ++++++----- .../web_ui/src/components/UserViewPage.jsx | 8 +-- packages/web_ui/src/components/UsersPage.jsx | 10 ++-- packages/web_ui/src/index.css | 24 ++++++-- plugins/inventory_sync/package.json | 6 +- plugins/player_auth/package.json | 6 +- plugins/subspace_storage/package.json | 6 +- 22 files changed, 143 insertions(+), 127 deletions(-) diff --git a/packages/controller/package.json b/packages/controller/package.json index e78fe6b3..c2ebd05c 100644 --- a/packages/controller/package.json +++ b/packages/controller/package.json @@ -35,9 +35,9 @@ }, "devDependencies": { "@clusterio/web_ui": "^2.0.0-alpha.13", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-router-dom": "^5.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.4.14", "webpack": "^5.88.2", "webpack-dev-middleware": "^6.1.1", "webpack-merge": "^5.9.0" diff --git a/packages/lib/link/connectors.js b/packages/lib/link/connectors.js index 1cb8d2ee..7f4ed270 100644 --- a/packages/lib/link/connectors.js +++ b/packages/lib/link/connectors.js @@ -2,7 +2,8 @@ "use strict"; const assert = require("assert").strict; const events = require("events"); -const WebSocket = require("isomorphic-ws"); +// eslint-disable-next-line node/no-process-env +const WebSocket = process.env.APP_ENV === "browser" ? window.WebSocket : require("ws"); const libData = require("../data"); const libErrors = require("../errors"); diff --git a/packages/lib/package.json b/packages/lib/package.json index ec67bd67..222124ba 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -16,7 +16,6 @@ "chalk": "^4.1.2", "fast-deep-equal": "^3.1.3", "fs-extra": "^11.1.1", - "isomorphic-ws": "^5.0.0", "jimp": "^0.22.8", "jsonwebtoken": "^9.0.1", "jszip": "^3.10.1", diff --git a/packages/web_ui/package.json b/packages/web_ui/package.json index 59103248..133370b1 100644 --- a/packages/web_ui/package.json +++ b/packages/web_ui/package.json @@ -15,30 +15,30 @@ "node": ">=12" }, "dependencies": { - "@ant-design/icons": "^4.2.2", - "@babel/core": "^7.13.8", - "@babel/preset-react": "^7.12.13", + "@ant-design/icons": "^5.1.4", + "@babel/core": "^7.22.9", + "@babel/preset-react": "^7.22.5", "@clusterio/lib": "^2.0.0-alpha.13", - "antd": "^4.16.0", + "antd": "^5.7.3", "assert": "^2.0.0", - "babel-loader": "^8.2.2", + "babel-loader": "^9.1.3", "browserify-zlib": "^0.2.0", - "buffer": "^6.0.2", - "clean-webpack-plugin": "^3.0.0", - "css-loader": "^5.1.0", - "events": "^3.2.0", + "buffer": "^6.0.3", + "clean-webpack-plugin": "^4.0.0", + "css-loader": "^6.8.1", + "events": "^3.3.0", "file-loader": "^6.2.0", "null-loader": "^4.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "process": "^0.11.10", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-router-dom": "^5.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.14.2", "stream-browserify": "^3.0.0", - "style-loader": "^2.0.0", + "style-loader": "^3.3.3", "terser-webpack-plugin": "^5.3.9", - "util": "^0.12.3", + "util": "^0.12.5", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "webpack-manifest-plugin": "^5.0.0" diff --git a/packages/web_ui/src/bootstrap.jsx b/packages/web_ui/src/bootstrap.jsx index 40416a6e..c0861fda 100644 --- a/packages/web_ui/src/bootstrap.jsx +++ b/packages/web_ui/src/bootstrap.jsx @@ -1,7 +1,7 @@ import "./index.css"; import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import { libConfig, libLink, libLogging, libPlugin } from "@clusterio/lib"; const { ConsoleTransport, WebConsoleFormat, logger } = libLogging; @@ -111,5 +111,6 @@ export default async function bootstrap() { let controlConnector = new ControlConnector(wsUrl, 120); let control = new Control(controlConnector, plugins); - ReactDOM.render(, document.getElementById("root")); + const root = createRoot(document.getElementById("root")); + root.render(); } diff --git a/packages/web_ui/src/components/App.jsx b/packages/web_ui/src/components/App.jsx index 20981c70..5422c9f4 100644 --- a/packages/web_ui/src/components/App.jsx +++ b/packages/web_ui/src/components/App.jsx @@ -8,7 +8,7 @@ import SiteLayout from "./SiteLayout"; import ControlContext from "./ControlContext"; import LoginForm from "./LoginForm"; -import { Card, Spin, Typography } from "antd"; +import { Card, ConfigProvider, Spin, Typography, theme } from "antd"; const { Paragraph } = Typography; const { logger } = libLogging; @@ -93,11 +93,15 @@ export default function App(props) { return ( - - - {page} - - + + + + {page} + + + ); } diff --git a/packages/web_ui/src/components/HostsPage.jsx b/packages/web_ui/src/components/HostsPage.jsx index 4163ce54..e1df0228 100644 --- a/packages/web_ui/src/components/HostsPage.jsx +++ b/packages/web_ui/src/components/HostsPage.jsx @@ -1,5 +1,5 @@ import React, { useContext, useRef, useState, useEffect } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Button, Form, InputNumber, Modal, Table, Tag, Typography } from "antd"; import CopyOutlined from "@ant-design/icons/lib/icons/CopyOutlined"; @@ -178,7 +178,7 @@ function CopyButton({ text, message }) { export default function HostsPage() { let account = useAccount(); - let history = useHistory(); + let navigate = useNavigate(); let [hostList] = useHostList(); return @@ -226,7 +226,7 @@ export default function HostsPage() { pagination={false} onRow={(record, rowIndex) => ({ onClick: event => { - history.push(`/hosts/${record.id}/view`); + navigate(`/hosts/${record.id}/view`); }, })} /> diff --git a/packages/web_ui/src/components/InstanceList.jsx b/packages/web_ui/src/components/InstanceList.jsx index 89db9a6f..e9503903 100644 --- a/packages/web_ui/src/components/InstanceList.jsx +++ b/packages/web_ui/src/components/InstanceList.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { message, Button, Space, Table } from "antd"; import CopyOutlined from "@ant-design/icons/CopyOutlined"; @@ -12,7 +12,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba export default function InstanceList(props) { let account = useAccount(); - let history = useHistory(); + let navigate = useNavigate(); let [hostList] = useHostList(); function hostName(hostId) { @@ -102,7 +102,7 @@ export default function InstanceList(props) { pagination={false} onRow={(record, rowIndex) => ({ onClick: event => { - history.push(`/instances/${record.id}/view`); + navigate(`/instances/${record.id}/view`); }, })} />; diff --git a/packages/web_ui/src/components/InstanceViewPage.jsx b/packages/web_ui/src/components/InstanceViewPage.jsx index 8ff8ccf0..c9b59679 100644 --- a/packages/web_ui/src/components/InstanceViewPage.jsx +++ b/packages/web_ui/src/components/InstanceViewPage.jsx @@ -1,5 +1,5 @@ import React, { useContext, useState } from "react"; -import { useHistory, useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { Alert, Button, Descriptions, Dropdown, Menu, Modal, Space, Spin, Typography } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; import DownOutlined from "@ant-design/icons/DownOutlined"; @@ -60,7 +60,7 @@ export default function InstanceViewPage(props) { let params = useParams(); let instanceId = Number(params.id); - let history = useHistory(); + let navigate = useNavigate(); let account = useAccount(); let control = useContext(ControlContext); @@ -84,7 +84,7 @@ export default function InstanceViewPage(props) { action={ @@ -163,7 +163,7 @@ export default function InstanceViewPage(props) { control.send( new libData.InstanceDeleteRequest(instanceId) ).then(() => { - history.push("/instances"); + navigate("/instances"); }).catch(notifyErrorHandler("Error deleting instance")); }, }); diff --git a/packages/web_ui/src/components/InstancesPage.jsx b/packages/web_ui/src/components/InstancesPage.jsx index 72670aca..36b764cf 100644 --- a/packages/web_ui/src/components/InstancesPage.jsx +++ b/packages/web_ui/src/components/InstancesPage.jsx @@ -1,5 +1,5 @@ import React, { useContext, useState } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Button, Form, Input, Modal } from "antd"; import { libConfig, libData } from "@clusterio/lib"; @@ -15,7 +15,7 @@ import { notifyErrorHandler } from "../util/notify"; function CreateInstanceButton(props) { let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [open, setOpen] = useState(false); let [form] = Form.useForm(); @@ -32,7 +32,7 @@ function CreateInstanceButton(props) { let serializedConfig = instanceConfig.serialize("controller"); await control.send(new libData.InstanceCreateRequest(serializedConfig)); setOpen(false); - history.push(`/instances/${instanceConfig.get("instance.id")}/view`); + navigate(`/instances/${instanceConfig.get("instance.id")}/view`); } return <> diff --git a/packages/web_ui/src/components/ModPackViewPage.jsx b/packages/web_ui/src/components/ModPackViewPage.jsx index ed2e117c..f74ed148 100644 --- a/packages/web_ui/src/components/ModPackViewPage.jsx +++ b/packages/web_ui/src/components/ModPackViewPage.jsx @@ -1,5 +1,5 @@ import React, { Fragment, memo, useCallback, useEffect, useContext, useRef, useState } from "react"; -import { useHistory, useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { Button, Card, Checkbox, Col, ConfigProvider, Descriptions, Form, Input, Pagination, Popconfirm, Row, Table, Tag, Typography, Select, Skeleton, Space, Spin, Switch, Modal, Tooltip, @@ -599,7 +599,7 @@ function applyModPackChanges(modPack, changes) { export default function ModPackViewPage() { let account = useAccount(); - let history = useHistory(); + let navigate = useNavigate(); let control = useContext(ControlContext); let params = useParams(); let modPackId = Number(params.id); @@ -709,7 +709,7 @@ export default function ModPackViewPage() { control.send( new libData.ModPackDeleteRequest(modPack.id) ).then(() => { - history.push("/mods"); + navigate("/mods"); }).catch(notifyErrorHandler("Error deleting mod pack")); }} > @@ -783,40 +783,35 @@ export default function ModPackViewPage() {
-
- - You have unsaved changes - - - + - - - - -
+ }).catch(notifyErrorHandler("Error deleting mod pack")); + }} + >Apply + + +
; diff --git a/packages/web_ui/src/components/ModsPage.jsx b/packages/web_ui/src/components/ModsPage.jsx index 15dd9a49..e158dbdb 100644 --- a/packages/web_ui/src/components/ModsPage.jsx +++ b/packages/web_ui/src/components/ModsPage.jsx @@ -1,5 +1,5 @@ import React, { Fragment, useState, useContext } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Button, Form, Input, Modal, Popconfirm, Space, Table, Typography, Upload } from "antd"; import ImportOutlined from "@ant-design/icons/ImportOutlined"; import PlusOutlined from "@ant-design/icons/PlusOutlined"; @@ -25,7 +25,7 @@ const { logger } = libLogging; function ImportModPackButton() { let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [open, setOpen] = useState(false); let [form] = Form.useForm(); function close() { @@ -49,7 +49,7 @@ function ImportModPackButton() { } const modPack = libData.ModPack.fromModPackString(values.string); await control.send(new libData.ModPackCreateRequest(modPack)); - history.push(`/mods/mod-packs/${modPack.id}/view`); + navigate(`/mods/mod-packs/${modPack.id}/view`); })().catch(notifyErrorHandler("Error creating mod pack")); }} > @@ -74,7 +74,7 @@ function ImportModPackButton() { function CreateModPackButton() { let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [open, setOpen] = useState(false); let [form] = Form.useForm(); function close() { @@ -101,7 +101,7 @@ function CreateModPackButton() { modPack.factorioVersion = values.factorioVersion; if (values.description) { modPack.description = values.description; } await control.send(new libData.ModPackCreateRequest(modPack.toJSON())); - history.push(`/mods/mod-packs/${modPack.id}/view`); + navigate(`/mods/mod-packs/${modPack.id}/view`); })().catch(notifyErrorHandler("Error creating mod pack")); }} > @@ -131,7 +131,7 @@ function CreateModPackButton() { export default function ModsPage() { let account = useAccount(); let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [modList] = useModList(); let [modPackList] = useModPackList(); @@ -217,7 +217,7 @@ export default function ModsPage() { rowKey={modPack => modPack.id} onRow={(modPack, rowIndex) => ({ onClick: event => { - history.push(`/mods/mod-packs/${modPack.id}/view`); + navigate(`/mods/mod-packs/${modPack.id}/view`); }, })} /> diff --git a/packages/web_ui/src/components/PluginsPage.jsx b/packages/web_ui/src/components/PluginsPage.jsx index 3c3c5a46..d914ffec 100644 --- a/packages/web_ui/src/components/PluginsPage.jsx +++ b/packages/web_ui/src/components/PluginsPage.jsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Table } from "antd"; import notify from "../util/notify"; @@ -11,7 +11,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba export default function PluginsPage() { let plugins = useContext(ControlContext).plugins; - let history = useHistory(); + let navigate = useNavigate(); let [pluginList, setPluginList] = useState([]); useEffect(() => { @@ -79,7 +79,7 @@ export default function PluginsPage() { pagination={false} onRow={plugin => ({ onClick: event => { - history.push(`/plugins/${plugin.meta.name}/view`); + navigate(`/plugins/${plugin.meta.name}/view`); }, })} /> diff --git a/packages/web_ui/src/components/RoleViewPage.jsx b/packages/web_ui/src/components/RoleViewPage.jsx index 275fed31..60266fe3 100644 --- a/packages/web_ui/src/components/RoleViewPage.jsx +++ b/packages/web_ui/src/components/RoleViewPage.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useContext, useState } from "react"; -import { useHistory, useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { Button, Checkbox, Form, Input, Popconfirm, Spin } from "antd"; import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; @@ -43,7 +43,7 @@ export default function RoleViewPage() { let params = useParams(); let roleId = Number(params.id); - let history = useHistory(); + let navigate = useNavigate(); let account = useAccount(); let control = useContext(ControlContext); @@ -108,7 +108,7 @@ export default function RoleViewPage() { control.send( new libData.RoleDeleteRequest(roleId) ).then(() => { - history.push("/roles"); + navigate("/roles"); }).catch(notifyErrorHandler("Error deleting role")); }} > diff --git a/packages/web_ui/src/components/RolesPage.jsx b/packages/web_ui/src/components/RolesPage.jsx index 0cd509f1..b550edcd 100644 --- a/packages/web_ui/src/components/RolesPage.jsx +++ b/packages/web_ui/src/components/RolesPage.jsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Button, Form, Input, Modal, Table } from "antd"; import { libData } from "@clusterio/lib"; @@ -16,7 +16,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba function CreateRoleButton() { let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [open, setOpen] = useState(false); let [form] = Form.useForm(); @@ -31,7 +31,7 @@ function CreateRoleButton() { new libData.RoleCreateRequest(values.roleName, values.description || "", []) ); setOpen(false); - history.push(`/roles/${result.id}/view`); + navigate(`/roles/${result.id}/view`); } return <> @@ -62,7 +62,7 @@ function CreateRoleButton() { export default function RolesPage() { let account = useAccount(); let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [roles, setRoles] = useState([]); @@ -94,7 +94,7 @@ export default function RolesPage() { rowKey={role => role.id} onRow={(role, rowIndex) => ({ onClick: event => { - history.push(`/roles/${role.id}/view`); + navigate(`/roles/${role.id}/view`); }, })} /> diff --git a/packages/web_ui/src/components/SiteLayout.jsx b/packages/web_ui/src/components/SiteLayout.jsx index 70cc2a35..86bcfcef 100644 --- a/packages/web_ui/src/components/SiteLayout.jsx +++ b/packages/web_ui/src/components/SiteLayout.jsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from "react"; -import { Switch, Route, useHistory } from "react-router-dom"; +import { Route, Routes, useNavigate } from "react-router-dom"; import { Dropdown, Layout, Menu } from "antd"; import UserOutlined from "@ant-design/icons/UserOutlined"; import webUiPackage from "../../package.json"; @@ -14,7 +14,7 @@ const { Header, Sider } = Layout; export default function SiteLayout(props) { - let history = useHistory(); + let navigate = useNavigate(); let [currentSidebarPath, setCurrentSidebarPath] = useState(null); let account = useAccount(); let plugins = useContext(ControlContext).plugins; @@ -29,7 +29,7 @@ export default function SiteLayout(props) { let accountMenuProps = { onClick: ({ key }) => { if (key === "user") { - history.push(`/users/${account.name}/view`); + navigate(`/users/${account.name}/view`); } else if (key === "logOut") { account.logOut(); } @@ -107,22 +107,24 @@ export default function SiteLayout(props) { defaultOpenKeys={[...menuGroups.keys()]} selectedKeys={[currentSidebarPath]} style={{ height: "100%", borderRight: 0 }} - onClick={({ key }) => history.push(key)} + onClick={({ key }) => navigate(key)} items={menuItems} /> - - {combinedPages.map(({path, sidebarPath, content}) => - - - {content} - - )} - - - - + + {combinedPages.map(({path, sidebarPath, content}) => + + + {content} + + } + />)} + } /> + ; diff --git a/packages/web_ui/src/components/UserViewPage.jsx b/packages/web_ui/src/components/UserViewPage.jsx index e8740b61..9bc03dc7 100644 --- a/packages/web_ui/src/components/UserViewPage.jsx +++ b/packages/web_ui/src/components/UserViewPage.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useContext, useState } from "react"; -import { useHistory, useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { Button, Col, Descriptions, Form, Input, Popconfirm, Row, Table, Tag, Select, Space, Spin, Switch, @@ -29,7 +29,7 @@ export default function UserViewPage() { let params = useParams(); let userName = params.name; - let history = useHistory(); + let navigate = useNavigate(); let account = useAccount(); let control = useContext(ControlContext); @@ -147,7 +147,7 @@ export default function UserViewPage() { control.send( new libData.UserDeleteRequest(userName) ).then(() => { - history.push("/users"); + navigate("/users"); }).catch(notifyErrorHandler("Error deleting user")); }} > @@ -333,7 +333,7 @@ export default function UserViewPage() { rowKey={([id]) => id} onRow={([id], rowIndex) => ({ onClick: event => { - history.push(`/instances/${id}/view`); + navigate(`/instances/${id}/view`); }, })} /> diff --git a/packages/web_ui/src/components/UsersPage.jsx b/packages/web_ui/src/components/UsersPage.jsx index cfc72df3..457b0ef2 100644 --- a/packages/web_ui/src/components/UsersPage.jsx +++ b/packages/web_ui/src/components/UsersPage.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useContext, useState } from "react"; -import { useHistory } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Button, Form, Input, Modal, Space, Table, Tag } from "antd"; import { libData } from "@clusterio/lib"; @@ -18,7 +18,7 @@ const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "ba function CreateUserButton() { let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [open, setOpen] = useState(false); let [form] = Form.useForm(); @@ -31,7 +31,7 @@ function CreateUserButton() { await control.send(new libData.UserCreateRequest(values.userName)); setOpen(false); - history.push(`/users/${values.userName}/view`); + navigate(`/users/${values.userName}/view`); } return <> @@ -59,7 +59,7 @@ function CreateUserButton() { export default function UsersPage() { let account = useAccount(); let control = useContext(ControlContext); - let history = useHistory(); + let navigate = useNavigate(); let [userList] = useUserList(); let [roles, setRoles] = useState(new Map()); @@ -120,7 +120,7 @@ export default function UsersPage() { rowKey={user => user.name} onRow={(user, rowIndex) => ({ onClick: event => { - history.push(`/users/${user.name}/view`); + navigate(`/users/${user.name}/view`); }, })} /> diff --git a/packages/web_ui/src/index.css b/packages/web_ui/src/index.css index 12c56d59..276865a8 100644 --- a/packages/web_ui/src/index.css +++ b/packages/web_ui/src/index.css @@ -1,5 +1,3 @@ -@import '~antd/dist/antd.dark.css'; - body { margin: 0; } @@ -157,7 +155,8 @@ body { height: 64px; font-size: 18px; line-height: 72px; - text-align: center; + display: flex; + justify-content: center; } .account-dropdown-header::after { @@ -339,8 +338,8 @@ body { border: none; } -tr.no-expanded-padding > td, -.ant-table.ant-table-small .ant-table-tbody > tr.no-expanded-padding > td { +div.ant-table tr.no-expanded-padding > td, +div.ant-table.ant-table-small .ant-table-tbody > tr.no-expanded-padding > td { padding: 0; } @@ -368,3 +367,18 @@ tr.ant-table-row.deleted { tr.ant-table-row.deleted { background: #441c18; } + +.sticky-notice { + position: sticky; + inset: auto auto 24px auto; + background: #1f1f1f; + padding: 24px; + width: auto; + margin: 16px auto 0; + box-shadow: + 0 6px 16px 0 rgba(0, 0, 0, 0.08), + 0 3px 6px -4px rgba(0, 0, 0, 0.12), + 0 9px 28px 8px rgba(0, 0, 0, 0.05); + border-radius: 8px; + max-width: 384px; +} diff --git a/plugins/inventory_sync/package.json b/plugins/inventory_sync/package.json index 5c612ed2..ce1a56ea 100644 --- a/plugins/inventory_sync/package.json +++ b/plugins/inventory_sync/package.json @@ -25,9 +25,9 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", - "antd": "^4.16.0", - "react": "^16.13.1", - "react-dom": "^16.13.1", + "antd": "^5.7.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "webpack-merge": "^5.9.0" diff --git a/plugins/player_auth/package.json b/plugins/player_auth/package.json index f2324d22..b5a886e8 100644 --- a/plugins/player_auth/package.json +++ b/plugins/player_auth/package.json @@ -26,10 +26,10 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", - "antd": "^4.16.0", + "antd": "^5.7.3", "phin": "^3.7.0", - "react": "^16.13.1", - "react-dom": "^16.13.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "webpack-merge": "^5.9.0" diff --git a/plugins/subspace_storage/package.json b/plugins/subspace_storage/package.json index 23664974..40748d6a 100644 --- a/plugins/subspace_storage/package.json +++ b/plugins/subspace_storage/package.json @@ -25,9 +25,9 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.13", "@clusterio/web_ui": "^2.0.0-alpha.13", - "antd": "^4.16.0", - "react": "^16.13.1", - "react-dom": "^16.13.1", + "antd": "^5.7.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "webpack-merge": "^5.9.0" From d9e94648d320845d95e36822d01d2ddf9bc12c49 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 27 Jul 2023 17:55:34 +0200 Subject: [PATCH 8/9] Fix wrong Node.js version in contributing docs. --- docs/contributing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index 22c92a2b..c007ee77 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -181,8 +181,8 @@ The format of this log should be self explanatory, please follow it carefully. ## Supported Node.js and Factorio Versions -Clusterio run on Node.js v16 and up, make sure to check that the Node builtin API's and npm packages you use are supported on these versions. -The GitHub Actions tests are run on whatever version of Node.js v16 and v18 is the default. +Clusterio run on Node.js v18 and up, make sure to check that the Node builtin API's and npm packages you use are supported on these versions. +The GitHub Actions tests are run on whatever version of Node.js v18 and v20 is the default. For Factorio Clusterio 2.0 aims to support version 0.17.69 and up, including the latest experimental release. It's recommended that you use the lua API reference for 0.17.69, as there's no information on what version Factorio API's were introduced in. From c2fa873d85cd69d3a2bdf82f86acc3f824b1ce0f Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 27 Jul 2023 21:04:27 +0200 Subject: [PATCH 9/9] Update version of pnpm used on CI --- .github/workflows/node.js.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 5cab94b7..5808be74 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -17,9 +17,9 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2.2.2 + - uses: pnpm/action-setup@v2.4.0 with: - version: 6 + version: 8 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: @@ -44,11 +44,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2.2.2 + - uses: pnpm/action-setup@v2.4.0 with: - version: 6 + version: 8 - uses: actions/setup-node@v2 with: - node-version: 16.x + node-version: 18.x - run: pnpm i --no-optional --no-frozen-lockfile - run: pnpm run lint