mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 07:24:52 +09:00
Merge pull request #487 from Hornwitser/node-js-16
Bump supported Node.js to v18 and update dependencies
This commit is contained in:
51 files changed
+445
-412
No files matched your search
@@ -12,14 +12,14 @@ 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:
|
||||
- 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:
|
||||
@@ -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
|
||||
@@ -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
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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 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.
|
||||
|
||||
+8
-8
@@ -19,23 +19,23 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@clusterio/controller": "workspace:*",
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
@@ -16,31 +16,31 @@
|
||||
"factorio"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"clusterio"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"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"
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
"factorio"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"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"
|
||||
|
||||
@@ -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"""""'
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
"factorio"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"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"
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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" }],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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"] },
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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");
|
||||
@@ -432,6 +433,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}`);
|
||||
|
||||
+14
-15
@@ -12,23 +12,22 @@
|
||||
"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",
|
||||
"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"
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
"use strict";
|
||||
const Ajv = require("ajv");
|
||||
const ajv = new Ajv({
|
||||
allowUnionTypes: true,
|
||||
strict: "log",
|
||||
strictTuples: false,
|
||||
verbose: true,
|
||||
format: "full",
|
||||
extendRefs: "fail",
|
||||
strictDefaults: true,
|
||||
strictKeywords: true,
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -15,32 +15,32 @@
|
||||
"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",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"util": "^0.12.5",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-manifest-plugin": "^5.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -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;
|
||||
@@ -108,10 +108,9 @@ 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);
|
||||
|
||||
ReactDOM.render(<App control={control}/>, document.getElementById("root"));
|
||||
const root = createRoot(document.getElementById("root"));
|
||||
root.render(<App control={control}/>);
|
||||
}
|
||||
@@ -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 (
|
||||
<ErrorBoundary Component={ErrorCard}>
|
||||
<ControlContext.Provider value={props.control}>
|
||||
<BrowserRouter basename={webRoot}>
|
||||
{page}
|
||||
</BrowserRouter>
|
||||
</ControlContext.Provider>
|
||||
<ConfigProvider
|
||||
theme={{ algorithm: theme.darkAlgorithm }}
|
||||
>
|
||||
<ControlContext.Provider value={props.control}>
|
||||
<BrowserRouter basename={webRoot}>
|
||||
{page}
|
||||
</BrowserRouter>
|
||||
</ControlContext.Provider>
|
||||
</ConfigProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -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 <>
|
||||
<Button {...props.buttonProps} onClick={open}>
|
||||
<Button {...props.buttonProps} onClick={() => setOpen(true)}>
|
||||
{props.buttonContent || "Assign"}
|
||||
</Button>
|
||||
<Modal
|
||||
title="Assign Instance"
|
||||
okText="Assign"
|
||||
visible={visible}
|
||||
open={open}
|
||||
confirmLoading={applying}
|
||||
onOk={handleAssign}
|
||||
onCancel={handleCancel}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
|
||||
export default function CreateSaveModal(props) {
|
||||
let [visible, setVisible] = useState(false);
|
||||
let [open, setOpen] = useState(false);
|
||||
let [creatingSave, setCreatingSave] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
let control = useContext(ControlContext);
|
||||
@@ -56,7 +56,7 @@ export default function CreateSaveModal(props) {
|
||||
new libData.InstanceCreateSaveRequest(name, seed, mapGenSettings, mapSettings),
|
||||
).then(() => {
|
||||
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}
|
||||
|
||||
@@ -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 <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={hostId}
|
||||
/>
|
||||
<PageHeader title={hostId} />
|
||||
<p>Host with id {hostId} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={host.name || hostId}
|
||||
/>
|
||||
<PageHeader title={host.name || hostId} />
|
||||
|
||||
<Descriptions bordered size="small" column={{ xs: 1, sm: 2, xl: 4 }}>
|
||||
<Descriptions.Item label="Name">{host["name"]}</Descriptions.Item>
|
||||
|
||||
@@ -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 { useNavigate } from "react-router-dom";
|
||||
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";
|
||||
@@ -17,7 +18,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 +54,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 <>
|
||||
<Button
|
||||
onClick={() => { setVisible(true); }}
|
||||
onClick={() => { setOpen(true); }}
|
||||
>Generate Token</Button>
|
||||
<Modal
|
||||
title="Generate Host Token"
|
||||
visible={visible}
|
||||
open={open}
|
||||
footer={null}
|
||||
onCancel={() => {
|
||||
setVisible(false);
|
||||
setOpen(false);
|
||||
setToken(null);
|
||||
form.resetFields();
|
||||
}}
|
||||
@@ -177,12 +178,11 @@ function CopyButton({ text, message }) {
|
||||
|
||||
export default function HostsPage() {
|
||||
let account = useAccount();
|
||||
let history = useHistory();
|
||||
let navigate = useNavigate();
|
||||
let [hostList] = useHostList();
|
||||
|
||||
return <PageLayout nav={[{ name: "Hosts" }]}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Hosts"
|
||||
extra={account.hasPermission("core.host.generate_token") && <GenerateHostTokenButton />}
|
||||
/>
|
||||
@@ -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`);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -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`);
|
||||
},
|
||||
})}
|
||||
/>;
|
||||
|
||||
@@ -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 { 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";
|
||||
|
||||
@@ -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";
|
||||
@@ -59,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);
|
||||
@@ -83,7 +84,7 @@ export default function InstanceViewPage(props) {
|
||||
action={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => { history.push("/instances"); }}
|
||||
onClick={() => { navigate("/instances"); }}
|
||||
>
|
||||
Go back to instances list
|
||||
</Button>
|
||||
@@ -126,9 +127,9 @@ export default function InstanceViewPage(props) {
|
||||
label: "Delete",
|
||||
});
|
||||
}
|
||||
let instanceButtonsMenu = <Menu
|
||||
items={instanceButtonMenuItems}
|
||||
onClick={({ key }) => {
|
||||
let instanceButtonsMenuProps = {
|
||||
items: instanceButtonMenuItems,
|
||||
onClick: ({ key }) => {
|
||||
if (key === "export") {
|
||||
setExportingData(true);
|
||||
control.sendTo(
|
||||
@@ -162,13 +163,13 @@ export default function InstanceViewPage(props) {
|
||||
control.send(
|
||||
new libData.InstanceDeleteRequest(instanceId)
|
||||
).then(() => {
|
||||
history.push("/instances");
|
||||
navigate("/instances");
|
||||
}).catch(notifyErrorHandler("Error deleting instance"));
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>;
|
||||
},
|
||||
};
|
||||
let instanceButtons = <Space>
|
||||
{
|
||||
account.hasAnyPermission("core.instance.start", "core.instance.stop")
|
||||
@@ -180,14 +181,13 @@ export default function InstanceViewPage(props) {
|
||||
"core.instance.extract_players",
|
||||
"core.instance.kill",
|
||||
"core.instance.delete",
|
||||
) && <Dropdown placement="bottomRight" trigger={["click"]} overlay={instanceButtonsMenu}>
|
||||
) && <Dropdown placement="bottomRight" trigger={["click"]} menu={instanceButtonsMenuProps}>
|
||||
<Button>More <DownOutlined /></Button>
|
||||
</Dropdown>}
|
||||
</Space>;
|
||||
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={instance.name}
|
||||
extra={instanceButtons}
|
||||
/>
|
||||
|
||||
@@ -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 { useNavigate } from "react-router-dom";
|
||||
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";
|
||||
@@ -14,8 +15,8 @@ import { notifyErrorHandler } from "../util/notify";
|
||||
|
||||
function CreateInstanceButton(props) {
|
||||
let control = useContext(ControlContext);
|
||||
let history = useHistory();
|
||||
let [visible, setVisible] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
let [open, setOpen] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
|
||||
async function createInstance() {
|
||||
@@ -30,23 +31,23 @@ function CreateInstanceButton(props) {
|
||||
instanceConfig.set("instance.name", values.instanceName);
|
||||
let serializedConfig = instanceConfig.serialize("controller");
|
||||
await control.send(new libData.InstanceCreateRequest(serializedConfig));
|
||||
setVisible(false);
|
||||
history.push(`/instances/${instanceConfig.get("instance.id")}/view`);
|
||||
setOpen(false);
|
||||
navigate(`/instances/${instanceConfig.get("instance.id")}/view`);
|
||||
}
|
||||
|
||||
return <>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
setOpen(true);
|
||||
}}
|
||||
>Create</Button>
|
||||
<Modal
|
||||
title="Create Instance"
|
||||
okText="Create"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={() => { createInstance().catch(notifyErrorHandler("Error creating instance")); }}
|
||||
onCancel={() => { setVisible(false); }}
|
||||
onCancel={() => { setOpen(false); }}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form}>
|
||||
@@ -65,7 +66,6 @@ export default function InstancesPage() {
|
||||
|
||||
return <PageLayout nav={[{ name: "Instances" }]}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Instances"
|
||||
extra={<>
|
||||
{account.hasPermission("core.instance.create") && <CreateInstanceButton />}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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, 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";
|
||||
@@ -485,7 +486,7 @@ function CopyButton(props) {
|
||||
}, []);
|
||||
|
||||
let denied = clipboard.readPermissionState === "denied";
|
||||
return <Tooltip title={denied ? clipboard.deniedReason : "Copied!"} visible={denied ? undefined : copiedVisible}>
|
||||
return <Tooltip title={denied ? clipboard.deniedReason : "Copied!"} open={denied ? undefined : copiedVisible}>
|
||||
<Button disabled={denied} onClick={() => {
|
||||
try {
|
||||
clipboard.writeText(props.content);
|
||||
@@ -503,19 +504,19 @@ function CopyButton(props) {
|
||||
}
|
||||
|
||||
function ExportButton(props) {
|
||||
let [visible, setVisible] = useState(false);
|
||||
let [open, setOpen] = useState(false);
|
||||
function close() {
|
||||
setVisible(false);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
let exportString;
|
||||
if (visible) { exportString = props.modPack.toModPackString(); }
|
||||
if (open) { exportString = props.modPack.toModPackString(); }
|
||||
|
||||
return <>
|
||||
<Button icon={<ExportOutlined />} onClick={() => { setVisible(true); }}>Export to string</Button>
|
||||
<Button icon={<ExportOutlined />} onClick={() => { setOpen(true); }}>Export to string</Button>
|
||||
<Modal
|
||||
title="Mod Pack String"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={close}
|
||||
onCancel={close}
|
||||
destroyOnClose
|
||||
@@ -593,11 +594,12 @@ function applyModPackChanges(modPack, changes) {
|
||||
throw new Error(`Unknown change type ${change.type}`);
|
||||
}
|
||||
}
|
||||
return modifiedModPack;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -681,27 +683,20 @@ export default function ModPackViewPage() {
|
||||
let nav = [{ name: "Mods", path: "/mods" }, { name: "Mod Packs" }, { name: modPack.name || modPackId }];
|
||||
if (modifiedModPack.loading) {
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={modPackId}
|
||||
/>
|
||||
<PageHeader title={modPackId} />
|
||||
<Spin size="large" />
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
if (modifiedModPack.missing) {
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Mod Pack Not Found"
|
||||
/>
|
||||
<PageHeader title="Mod Pack Not Found" />
|
||||
<p>Mod pack with id {modPackId} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={modPack.name}
|
||||
extra={<Space>
|
||||
<ExportButton modPack={modifiedModPack}/>
|
||||
@@ -714,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"));
|
||||
}}
|
||||
>
|
||||
@@ -788,40 +783,35 @@ export default function ModPackViewPage() {
|
||||
</Form>
|
||||
|
||||
<div
|
||||
className="ant-notification ant-notification-bottom"
|
||||
className="sticky-notice"
|
||||
style={{
|
||||
visibility: changes.length ? "visible" : "hidden",
|
||||
inset: "auto auto 24px auto",
|
||||
position: "sticky",
|
||||
marginTop: 16,
|
||||
}}
|
||||
>
|
||||
<div className="ant-notification-notice" style={{ width: "auto", maxWidth: 384, marginBottom: 0 }}>
|
||||
<Row style={{ alignItems: "center", rowGap: 12 }}>
|
||||
<Col flex="auto">You have unsaved changes</Col>
|
||||
<Col flex="0 0 auto" style={{ marginLeft: "auto" }}>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
<Row style={{ alignItems: "center", rowGap: 12 }}>
|
||||
<Col flex="auto">You have unsaved changes</Col>
|
||||
<Col flex="0 0 auto" style={{ marginLeft: "auto" }}>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.resetFields();
|
||||
setChanges([]);
|
||||
}}
|
||||
>Revert</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
control.send(
|
||||
new libData.ModPackUpdateRequest(modifiedModPack)
|
||||
).then(() => {
|
||||
form.resetFields();
|
||||
setChanges([]);
|
||||
}}
|
||||
>Revert</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
control.send(
|
||||
new libData.ModPackUpdateRequest(modifiedModPack)
|
||||
).then(() => {
|
||||
form.resetFields();
|
||||
setChanges([]);
|
||||
}).catch(notifyErrorHandler("Error deleting mod pack"));
|
||||
}}
|
||||
>Apply</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
}).catch(notifyErrorHandler("Error deleting mod pack"));
|
||||
}}
|
||||
>Apply</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</PageLayout>;
|
||||
|
||||
|
||||
@@ -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 { 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";
|
||||
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";
|
||||
@@ -24,20 +25,20 @@ const { logger } = libLogging;
|
||||
|
||||
function ImportModPackButton() {
|
||||
let control = useContext(ControlContext);
|
||||
let history = useHistory();
|
||||
let [visible, setVisible] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
let [open, setOpen] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
function close() {
|
||||
setVisible(false);
|
||||
setOpen(false);
|
||||
}
|
||||
return <>
|
||||
<Button icon={<ImportOutlined />} onClick={() => { setVisible(true); }}>Import string</Button>
|
||||
<Button icon={<ImportOutlined />} onClick={() => { setOpen(true); }}>Import string</Button>
|
||||
<Modal
|
||||
title="Import Mod Pack String"
|
||||
visible={visible}
|
||||
open={open}
|
||||
okText="Import"
|
||||
cancelText="Cancel"
|
||||
onCancel={() => { setVisible(false); }}
|
||||
onCancel={() => { setOpen(false); }}
|
||||
onOk={() => {
|
||||
(async () => {
|
||||
let values;
|
||||
@@ -48,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"));
|
||||
}}
|
||||
>
|
||||
@@ -73,20 +74,20 @@ function ImportModPackButton() {
|
||||
|
||||
function CreateModPackButton() {
|
||||
let control = useContext(ControlContext);
|
||||
let history = useHistory();
|
||||
let [visible, setVisible] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
let [open, setOpen] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
function close() {
|
||||
setVisible(false);
|
||||
setOpen(false);
|
||||
}
|
||||
return <>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => { setVisible(true); }}>Create</Button>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => { setOpen(true); }}>Create</Button>
|
||||
<Modal
|
||||
title="Create Mod Pack"
|
||||
visible={visible}
|
||||
open={open}
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={() => { setVisible(false); }}
|
||||
onCancel={() => { setOpen(false); }}
|
||||
onOk={() => {
|
||||
(async () => {
|
||||
let values;
|
||||
@@ -100,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"));
|
||||
}}
|
||||
>
|
||||
@@ -130,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();
|
||||
|
||||
@@ -184,10 +185,7 @@ export default function ModsPage() {
|
||||
}
|
||||
|
||||
return <PageLayout nav={[{ name: "Mods" }]}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Mods"
|
||||
/>
|
||||
<PageHeader title="Mods" />
|
||||
<SectionHeader
|
||||
title="Mod Packs"
|
||||
extra={<Space>
|
||||
@@ -219,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`);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
export default function PageHeader(props) {
|
||||
return <div className="page-header">
|
||||
<h2 className="page-header-title">{props.title}</h2>
|
||||
{props.extra && <div className="page-header-extra">{props.extra}</div>}
|
||||
</div>;
|
||||
}
|
||||
@@ -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`);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -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 { useNavigate, useParams } from "react-router-dom";
|
||||
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";
|
||||
@@ -42,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);
|
||||
@@ -53,20 +54,14 @@ export default function RoleViewPage() {
|
||||
let nav = [{ name: "Roles", path: "/roles" }, { name: role["name"] || roleId }];
|
||||
if (role.loading) {
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={roleId}
|
||||
/>
|
||||
<PageHeader title={roleId} />
|
||||
<Spin size="large" />
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
if (role.missing) {
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Role not found"
|
||||
/>
|
||||
<PageHeader title="Role not found" />
|
||||
<p>Role with id {roleId} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
@@ -101,7 +96,6 @@ export default function RoleViewPage() {
|
||||
}}
|
||||
>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={role["name"]}
|
||||
extra={<>
|
||||
{canUpdate && <Button type={edited ? "primary" : "default"} htmlType="submit">Apply</Button>}
|
||||
@@ -114,7 +108,7 @@ export default function RoleViewPage() {
|
||||
control.send(
|
||||
new libData.RoleDeleteRequest(roleId)
|
||||
).then(() => {
|
||||
history.push("/roles");
|
||||
navigate("/roles");
|
||||
}).catch(notifyErrorHandler("Error deleting role"));
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 { useNavigate } from "react-router-dom";
|
||||
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";
|
||||
|
||||
@@ -15,8 +16,8 @@ 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 navigate = useNavigate();
|
||||
let [open, setOpen] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
|
||||
async function createRole() {
|
||||
@@ -29,21 +30,21 @@ function CreateRoleButton() {
|
||||
let result = await control.send(
|
||||
new libData.RoleCreateRequest(values.roleName, values.description || "", [])
|
||||
);
|
||||
setVisible(false);
|
||||
history.push(`/roles/${result.id}/view`);
|
||||
setOpen(false);
|
||||
navigate(`/roles/${result.id}/view`);
|
||||
}
|
||||
|
||||
return <>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => { setVisible(true); }}
|
||||
onClick={() => { setOpen(true); }}
|
||||
>Create</Button>
|
||||
<Modal
|
||||
title="Create Role"
|
||||
okText="Create"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={() => { createRole().catch(notifyErrorHandler("Error creating role")); }}
|
||||
onCancel={() => { setVisible(false); }}
|
||||
onCancel={() => { setOpen(false); }}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form}>
|
||||
@@ -61,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([]);
|
||||
|
||||
@@ -73,7 +74,6 @@ export default function RolesPage() {
|
||||
|
||||
return <PageLayout nav={[{ name: "Roles" }]}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Roles"
|
||||
extra={account.hasPermission("core.role.create") && <CreateRoleButton />}
|
||||
/>
|
||||
@@ -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`);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -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 <>
|
||||
<Button disabled={props.disabled} onClick={() => setVisible(true)}>Rename</Button>
|
||||
<Button disabled={props.disabled} onClick={() => setOpen(true)}>Rename</Button>
|
||||
<Modal
|
||||
title="Rename save"
|
||||
okText="Rename"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => setVisible(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
@@ -39,7 +39,7 @@ function RenameModal(props) {
|
||||
control.send(
|
||||
new libData.InstanceRenameSaveRequest(props.instanceId, props.save.name, values.newName)
|
||||
).then(() => {
|
||||
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 <>
|
||||
<Button disabled={props.disabled} onClick={() => setVisible(true)}>Copy</Button>
|
||||
<Button disabled={props.disabled} onClick={() => setOpen(true)}>Copy</Button>
|
||||
<Modal
|
||||
title="Copy save"
|
||||
okText="Copy"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => setVisible(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
@@ -78,7 +78,7 @@ function CopyModal(props) {
|
||||
control.send(
|
||||
new libData.InstanceCopySaveRequest(props.instanceId, props.save.name, values.newName)
|
||||
).then(() => {
|
||||
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 <>
|
||||
<Button disabled={props.disabled} onClick={() => setVisible(true)}>Transfer</Button>
|
||||
<Button disabled={props.disabled} onClick={() => setOpen(true)}>Transfer</Button>
|
||||
<Modal
|
||||
title="Transfer save"
|
||||
okText="Transfer"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => setVisible(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form
|
||||
@@ -118,7 +118,7 @@ function TransferModal(props) {
|
||||
wrapperCol={{ span: 18 }}
|
||||
initialValues={{ transferredName: props.save.name, copy: false }}
|
||||
onFinish={values => {
|
||||
setVisible(false);
|
||||
setOpen(false);
|
||||
let hide = message.loading("Transferring save...", 0);
|
||||
control.send(new libData.InstanceTransferSaveRequest(
|
||||
props.instanceId,
|
||||
|
||||
@@ -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;
|
||||
@@ -26,20 +26,20 @@ export default function SiteLayout(props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let accountMenu = <Menu
|
||||
onClick={({ key }) => {
|
||||
let accountMenuProps = {
|
||||
onClick: ({ key }) => {
|
||||
if (key === "user") {
|
||||
history.push(`/users/${account.name}/view`);
|
||||
navigate(`/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}
|
||||
>
|
||||
<UserOutlined/>
|
||||
</Dropdown>
|
||||
@@ -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}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout className="site-layout-content-container">
|
||||
<Switch>
|
||||
{combinedPages.map(({path, sidebarPath, content}) => <Route exact path={path} key={path}>
|
||||
<SetSidebar path={sidebarPath ? sidebarPath : path} />
|
||||
<ErrorBoundary Component={ErrorPage}>
|
||||
{content}
|
||||
</ErrorBoundary>
|
||||
</Route>)}
|
||||
<Route>
|
||||
<SetSidebar path={null} />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Routes>
|
||||
{combinedPages.map(({path, sidebarPath, content}) => <Route
|
||||
path={path}
|
||||
key={path}
|
||||
element={<>
|
||||
<SetSidebar path={sidebarPath ? sidebarPath : path} />
|
||||
<ErrorBoundary Component={ErrorPage}>
|
||||
{content}
|
||||
</ErrorBoundary>
|
||||
</>}
|
||||
/>)}
|
||||
<Route element={<SetSidebar path={null} />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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, 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";
|
||||
@@ -28,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);
|
||||
@@ -62,20 +63,14 @@ export default function UserViewPage() {
|
||||
let nav = [{ name: "Users", path: "/users" }, { name: userName }];
|
||||
if (user.loading) {
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={userName}
|
||||
/>
|
||||
<PageHeader title={userName} />
|
||||
<Spin size="large" />
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
if (user.missing) {
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="User not found"
|
||||
/>
|
||||
<PageHeader title="User not found" />
|
||||
<p>User with name {userName} was not found on the controller.</p>
|
||||
</PageLayout>;
|
||||
}
|
||||
@@ -112,7 +107,6 @@ export default function UserViewPage() {
|
||||
|
||||
return <PageLayout nav={nav}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={<Space>
|
||||
{userName}
|
||||
<span>
|
||||
@@ -153,7 +147,7 @@ export default function UserViewPage() {
|
||||
control.send(
|
||||
new libData.UserDeleteRequest(userName)
|
||||
).then(() => {
|
||||
history.push("/users");
|
||||
navigate("/users");
|
||||
}).catch(notifyErrorHandler("Error deleting user"));
|
||||
}}
|
||||
>
|
||||
@@ -339,7 +333,7 @@ export default function UserViewPage() {
|
||||
rowKey={([id]) => id}
|
||||
onRow={([id], rowIndex) => ({
|
||||
onClick: event => {
|
||||
history.push(`/instances/${id}/view`);
|
||||
navigate(`/instances/${id}/view`);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -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 { useNavigate } from "react-router-dom";
|
||||
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";
|
||||
@@ -17,8 +18,8 @@ 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 navigate = useNavigate();
|
||||
let [open, setOpen] = useState(false);
|
||||
let [form] = Form.useForm();
|
||||
|
||||
async function createUser() {
|
||||
@@ -29,21 +30,21 @@ function CreateUserButton() {
|
||||
}
|
||||
|
||||
await control.send(new libData.UserCreateRequest(values.userName));
|
||||
setVisible(false);
|
||||
history.push(`/users/${values.userName}/view`);
|
||||
setOpen(false);
|
||||
navigate(`/users/${values.userName}/view`);
|
||||
}
|
||||
|
||||
return <>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => { setVisible(true); }}
|
||||
onClick={() => { setOpen(true); }}
|
||||
>Create</Button>
|
||||
<Modal
|
||||
title="Create User"
|
||||
okText="Create"
|
||||
visible={visible}
|
||||
open={open}
|
||||
onOk={() => { createUser().catch(notifyErrorHandler("Error creating user")); }}
|
||||
onCancel={() => { setVisible(false); }}
|
||||
onCancel={() => { setOpen(false); }}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form}>
|
||||
@@ -58,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());
|
||||
@@ -73,7 +74,6 @@ export default function UsersPage() {
|
||||
|
||||
return <PageLayout nav={[{ name: "Users" }]}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title="Users"
|
||||
extra={account.hasPermission("core.user.create") && <CreateUserButton />}
|
||||
/>
|
||||
@@ -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`);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import '~antd/dist/antd.dark.css';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -67,29 +65,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;
|
||||
}
|
||||
|
||||
@@ -143,7 +155,8 @@ body {
|
||||
height: 64px;
|
||||
font-size: 18px;
|
||||
line-height: 72px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.account-dropdown-header::after {
|
||||
@@ -325,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;
|
||||
}
|
||||
|
||||
@@ -354,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;
|
||||
}
|
||||
@@ -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";
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://github.com/clusterio/clusterio/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
@@ -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"
|
||||
|
||||
@@ -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"] },
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://github.com/clusterio/factorioClusterio/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
@@ -25,12 +25,12 @@
|
||||
"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",
|
||||
"webpack": "^5.36.2",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-merge": "^5.2.0"
|
||||
"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"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://github.com/clusterio/clusterio/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
@@ -26,13 +26,13 @@
|
||||
"devDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.13",
|
||||
"@clusterio/web_ui": "^2.0.0-alpha.13",
|
||||
"antd": "^4.16.0",
|
||||
"phin": "^3.5.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"webpack": "^5.36.2",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-merge": "^5.2.0"
|
||||
"antd": "^5.7.3",
|
||||
"phin": "^3.7.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-merge": "^5.9.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -55,6 +55,7 @@ class ProgressEvent {
|
||||
technologies: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
required: ["name", "level", "progress"],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://github.com/clusterio/clusterio/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
@@ -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"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://github.com/clusterio/clusterio/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
@@ -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"
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "https://github.com/clusterio/clusterio/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.0"
|
||||
@@ -25,12 +25,12 @@
|
||||
"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",
|
||||
"webpack": "^5.36.2",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-merge": "^5.2.0"
|
||||
"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"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user