diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a0c6669..7acd543b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,18 +45,22 @@ Many thanks to the following for contributing to this release: - Fixed spaces passed in arguments to the installer causing it to break [#620](https://github.com/clusterio/clusterio/issues/620) - Fixed numeric admin name breaking login [#536](https://github.com/clusterio/clusterio/issues/536) - Added factorio.executable_path option which allows overriding the default path to the Factorio executable run. +- Added instance version display [#573](https://github.com/clusterio/clusterio/pull/573) ### Breaking Changes - Previously silent errors for controller.sendEvent now throw exceptions [#625](https://github.com/clusterio/clusterio/pull/625). +- @clusterio/controller export InstanceInfo has added factorioVersion parameter. Many thanks to the following for contributing to this release: +[@CCpersonguy](https://github.com/CCpersonguy) [@Cooldude2606](https://github.com/Cooldude2606) [@Danielv123](https://github.com/Danielv123) [@Hornwitser](https://github.com/Hornwitser) [@Laar](https://github.com/Laar) [@psihius](https://github.com/psihius) + ## Version 2.0.0-alpha.18 ### Features diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index d7f9f7b1..530ad456 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -896,7 +896,7 @@ export default class Controller { }; instanceConfig.set("factorio.settings", settings); - let instance = new InstanceInfo(instanceConfig, "unassigned", undefined, Date.now()); + let instance = new InstanceInfo(instanceConfig, "unassigned", undefined, undefined, Date.now()); this.instances.set(instanceId, instance); this.instancesDirty = true; await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance); diff --git a/packages/controller/src/HostConnection.ts b/packages/controller/src/HostConnection.ts index ab566a40..7540452c 100644 --- a/packages/controller/src/HostConnection.ts +++ b/packages/controller/src/HostConnection.ts @@ -188,6 +188,7 @@ export default class HostConnection extends BaseConnection { let prev = instance.status; instance.status = request.status; instance.gamePort = request.gamePort; + instance.factorioVersion = request.factorioVersion; instance.updatedAtMs = Date.now(); logger.verbose(`Instance ${instance.config.get("instance.name")} State: ${instance.status}`); this._controller.instanceDetailsUpdated([instance]); @@ -225,6 +226,7 @@ export default class HostConnection extends BaseConnection { let prev = controllerInstance.status; controllerInstance.status = instanceData.status; controllerInstance.gamePort = instanceData.gamePort; + controllerInstance.factorioVersion = instanceData.factorioVersion; controllerInstance.updatedAtMs = Date.now(); logger.verbose(`Instance ${instanceConfig.get("instance.name")} State: ${instanceData.status}`); instanceUpdates.push(controllerInstance); @@ -240,6 +242,7 @@ export default class HostConnection extends BaseConnection { instanceConfig, instanceData.status, instanceData.gamePort, + instanceData.factorioVersion, Date.now(), ); this._controller.instances.set(instanceConfig.get("instance.id"), newInstance); diff --git a/packages/controller/src/InstanceInfo.ts b/packages/controller/src/InstanceInfo.ts index 0534c20d..ab664511 100644 --- a/packages/controller/src/InstanceInfo.ts +++ b/packages/controller/src/InstanceInfo.ts @@ -11,6 +11,7 @@ export default class InstanceInfo { public config: lib.InstanceConfig, public status: lib.InstanceStatus, public gamePort?: number, + public factorioVersion?: string, public updatedAtMs = 0, ) { this.config = config; @@ -22,6 +23,7 @@ export default class InstanceInfo { "status": lib.InstanceStatus, "gamePort": Type.Optional(Type.Number()), "updatedAtMs": Type.Optional(Type.Number()), + "factorioVersion": Type.Optional(Type.String()), }); static fromJSON(json: Static, location: lib.ConfigLocation) { @@ -29,6 +31,7 @@ export default class InstanceInfo { lib.InstanceConfig.fromJSON(json.config, location), json.status, json.gamePort, + json.factorioVersion, json.updatedAtMs, ); } @@ -40,6 +43,7 @@ export default class InstanceInfo { this.config.get("instance.assigned_host") ?? undefined, this.gamePort, this.status, + this.factorioVersion, this.updatedAtMs, ); } diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index 2c806c42..5fd19fb7 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -667,6 +667,9 @@ export default class Host extends lib.Link { instanceId, instanceConnection ? instanceConnection.instance.status : "stopped", instanceConnection ? instanceConnection.instance.server.gamePort : this.gamePort(instanceId), + instanceConnection + ? instanceConnection.instance.server.version + : instanceInfo.config.get("factorio.version"), ) ); @@ -736,7 +739,7 @@ export default class Host extends lib.Link { } assignGamePort(instanceId: number) { - const availablePorts = lib.parseRanges(this.config.get("host.factorio_port_range"), 1, 2**16 - 1); + const availablePorts = lib.parseRanges(this.config.get("host.factorio_port_range"), 1, 2 ** 16 - 1); for (const [id, instance] of this.instanceInfos) { if (id === instanceId) { continue; @@ -1060,6 +1063,7 @@ export default class Host extends lib.Link { instanceInfo.config.toRemote("controller"), instanceConnection ? instanceConnection.instance.status : "stopped", instanceConnection ? instanceConnection.instance.server.gamePort : this.gamePort(instanceId), + instanceConnection ? instanceConnection.instance.server.version : undefined, )); } await this.send(new lib.InstancesUpdateRequest(list)); diff --git a/packages/host/src/Instance.ts b/packages/host/src/Instance.ts index 330e73d4..f7e8ae7b 100644 --- a/packages/host/src/Instance.ts +++ b/packages/host/src/Instance.ts @@ -454,6 +454,7 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " "); this.id, status, this.server.gamePort, + status === "running"? this.server.version : this.config.get("factorio.version"), ), ); } diff --git a/packages/host/src/export.ts b/packages/host/src/export.ts index 530f8fc9..f5f83528 100644 --- a/packages/host/src/export.ts +++ b/packages/host/src/export.ts @@ -64,7 +64,7 @@ async function generateExportMod(server: FactorioServer) { sourceDir: path.join(__dirname, "..", "..", "..", "lua", "export"), outputDir: server.writePath("mods"), bumpPatch: false, - factorioVersion: server.version.replace(/\.\d+$/, ""), + factorioVersion: server.version?.replace(/\.\d+$/, ""), dependencies, }); } diff --git a/packages/host/src/server.ts b/packages/host/src/server.ts index aa438c8f..c486e4d4 100644 --- a/packages/host/src/server.ts +++ b/packages/host/src/server.ts @@ -730,12 +730,12 @@ export class FactorioServer extends events.EventEmitter { } /** - * The version of Factorio in use. This will be the actual version if - * "latest" (the default) was specified as `factorioVersion` to the - * constructor. + * The version of Factorio in use. This will be the actual version if + * "latest" (the default) was specified as `factorioVersion` to the constructor. + * This will be undefined before the server is initialized, or if init failed. */ - get version() { - return this._version!; + get version(): string | undefined { + return this._version ?? undefined; } /** diff --git a/packages/lib/src/data/messages_instance.ts b/packages/lib/src/data/messages_instance.ts index d14894d4..13d80966 100644 --- a/packages/lib/src/data/messages_instance.ts +++ b/packages/lib/src/data/messages_instance.ts @@ -42,6 +42,7 @@ export class InstanceDetails { public assignedHost: number | undefined, public gamePort: number | undefined, public status: InstanceStatus, + public factorioVersion: string | undefined, /** Millisecond Unix timestamp this entry was last updated at */ public updatedAtMs = 0, ) { } @@ -55,6 +56,7 @@ export class InstanceDetails { "unknown", "unassigned", "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", "deleted", ]), + "factorioVersion": Type.Optional(Type.String()), "updatedAtMs": Type.Optional(Type.Number()), }); @@ -65,6 +67,7 @@ export class InstanceDetails { json.assignedHost, json.gamePort, json.status, + json.factorioVersion, json.updatedAtMs, ); } @@ -717,6 +720,7 @@ export class HostInstanceUpdate { public config: Static, public status: InstanceStatus, public gamePort: undefined | number, + public factorioVersion: undefined | string, ) { } static jsonSchema = Type.Object({ @@ -725,10 +729,11 @@ export class HostInstanceUpdate { "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", ]), "gamePort": Type.Optional(Type.Number()), + "factorioVersion": Type.Optional(Type.String()), }); static fromJSON(json: Static) { - return new this(json.config, json.status, json.gamePort); + return new this(json.config, json.status, json.gamePort, json.factorioVersion); } } @@ -822,6 +827,7 @@ export class InstanceStatusChangedEvent { public instanceId: number, public status: InstanceStatus, public gamePort?: number, + public factorioVersion?: string, ) { } static jsonSchema = Type.Object({ @@ -830,10 +836,11 @@ export class InstanceStatusChangedEvent { "stopped", "starting", "running", "stopping", "creating_save", "exporting_data", ]), "gamePort": Type.Optional(Type.Integer()), + "factorioVersion": Type.Optional(Type.String()), }); static fromJSON(json: Static) { - return new this(json.instanceId, json.status, json.gamePort); + return new this(json.instanceId, json.status, json.gamePort, json.factorioVersion); } } diff --git a/packages/web_ui/src/components/InstanceList.tsx b/packages/web_ui/src/components/InstanceList.tsx index c1aa76d0..7c05fd41 100644 --- a/packages/web_ui/src/components/InstanceList.tsx +++ b/packages/web_ui/src/components/InstanceList.tsx @@ -9,7 +9,7 @@ import { useAccount } from "../model/account"; import { useHosts } from "../model/host"; import InstanceStatusTag from "./InstanceStatusTag"; import StartStopInstanceButton from "./StartStopInstanceButton"; -import { InstanceDetails } from "@clusterio/lib"; +import { InstanceDetails, integerFactorioVersion } from "@clusterio/lib"; import Link from "./Link"; const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare; @@ -46,6 +46,13 @@ export default function InstanceList(props: InstanceListProps) { return `${host.publicAddress}:${instance.gamePort}`; } + function integerFactorioVersionOrDefault(instance: InstanceDetails) { + if (instance.factorioVersion === undefined) { + return -1; + } + return integerFactorioVersion(instance.factorioVersion); + } + let columns: ColumnsType = [ { title: "Name", @@ -86,6 +93,13 @@ export default function InstanceList(props: InstanceListProps) { sorter: (a, b) => strcmp(instancePublicAddress(a), instancePublicAddress(b)), responsive: ["lg"], }, + { + title: "Factorio Version", + key: "version", + render: (_, instance) => instance.factorioVersion ?? "unknown", + sorter: (a, b) => integerFactorioVersionOrDefault(a) - integerFactorioVersionOrDefault(b), + responsive: ["xl"], + }, { title: "Status", key: "status", diff --git a/packages/web_ui/src/components/InstanceViewPage.tsx b/packages/web_ui/src/components/InstanceViewPage.tsx index 0b773d85..90c08037 100644 --- a/packages/web_ui/src/components/InstanceViewPage.tsx +++ b/packages/web_ui/src/components/InstanceViewPage.tsx @@ -59,6 +59,9 @@ function InstanceDescription(props: InstanceDescriptionProps) { buttonContent={assigned ? "Reassign" : "Assign"} />} + + {instance.factorioVersion ?? "unknown"} + ; }