mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 15:04:52 +09:00
Refactor config to JSON data class pattern
Refactor the Config and ConfigGroup classes to match the JSON data class pattern with toJSON, toRemote and fromJSON methods replacing the load, init, and serialize methods. Since fromJSON requires extra information the messages structures passes around the plain JSON representation of the configs instead of serialising instances of the config classes automatically.
This commit is contained in:
1 parent
27af142a87
commit
001db4befe
24 files changed
+269
-334
No files matched your search
@@ -156,7 +156,6 @@ async function handleBootstrapCommand(
|
||||
return;
|
||||
}
|
||||
let controlConfig = new lib.ControlConfig("control");
|
||||
controlConfig.init();
|
||||
|
||||
controlConfig.set("control.controller_url", Controller.calculateControllerUrl(controllerConfig));
|
||||
controlConfig.set(
|
||||
@@ -164,7 +163,7 @@ async function handleBootstrapCommand(
|
||||
userManager.signUserToken(admin.name),
|
||||
);
|
||||
|
||||
let content = JSON.stringify(controlConfig.serialize(), null, "\t");
|
||||
let content = JSON.stringify(controlConfig, null, "\t");
|
||||
if (args.output === "-") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(content);
|
||||
@@ -322,15 +321,15 @@ async function initialize(): Promise<InitializeParameters> {
|
||||
|
||||
const controllerConfigPath = args.config;
|
||||
logger.info(`Loading config from ${controllerConfigPath}`);
|
||||
const controllerConfig = new lib.ControllerConfig("controller");
|
||||
let controllerConfig = new lib.ControllerConfig("controller");
|
||||
try {
|
||||
let fileData = await fs.readFile(controllerConfigPath, { encoding: "utf8" });
|
||||
controllerConfig.load(JSON.parse(fileData));
|
||||
let jsonConfig = JSON.parse(await fs.readFile(controllerConfigPath, { encoding: "utf8" }));
|
||||
controllerConfig = lib.ControllerConfig.fromJSON(jsonConfig, "controller");
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.info("Config not found, initializing new config");
|
||||
controllerConfig.init();
|
||||
controllerConfig = new lib.ControllerConfig("controller");
|
||||
|
||||
} else {
|
||||
throw new lib.StartupError(`Failed to load ${controllerConfigPath}: ${err.message}`);
|
||||
@@ -343,7 +342,7 @@ async function initialize(): Promise<InitializeParameters> {
|
||||
let bytes = await asyncRandomBytes(256);
|
||||
controllerConfig.set("controller.auth_secret", bytes.toString("base64"));
|
||||
await lib.safeOutputFile(
|
||||
controllerConfigPath, JSON.stringify(controllerConfig.serialize(), null, "\t")
|
||||
controllerConfigPath, JSON.stringify(controllerConfig, null, "\t")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,8 +169,8 @@ export default class ControlConnection extends BaseConnection {
|
||||
throw new Error("Should be unreachable");
|
||||
}
|
||||
|
||||
async handleControllerConfigGetRequest(): Promise<lib.RawConfig> {
|
||||
return new lib.RawConfig(this._controller.config.serialize("control"));
|
||||
async handleControllerConfigGetRequest() {
|
||||
return this._controller.config.toRemote("control");
|
||||
}
|
||||
|
||||
async handleControllerConfigSetFieldRequest(request: lib.ControllerConfigSetFieldRequest) {
|
||||
@@ -211,8 +211,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
}
|
||||
|
||||
async handleHostConfigCreateRequest(request: lib.HostConfigCreateRequest) {
|
||||
let hostConfig = new lib.HostConfig("control");
|
||||
hostConfig.init();
|
||||
const hostConfig = new lib.HostConfig("host");
|
||||
|
||||
hostConfig.set("host.controller_url", this._controller.getControllerUrl());
|
||||
if (request.id !== undefined) {
|
||||
@@ -225,7 +224,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
this.user.checkPermission("core.host.generate_token");
|
||||
hostConfig.set("host.controller_token", this._controller.generateHostToken(hostConfig.get("host.id")));
|
||||
}
|
||||
return new lib.RawConfig(hostConfig.serialize());
|
||||
return hostConfig.toJSON();
|
||||
}
|
||||
|
||||
async handleInstanceDetailsGetRequest(request: lib.InstanceDetailsGetRequest) {
|
||||
@@ -236,10 +235,9 @@ export default class ControlConnection extends BaseConnection {
|
||||
return [...this._controller.instances.values()].map(instance => instance.toInstanceDetails());
|
||||
}
|
||||
|
||||
// XXX should probably add a hook for host reuqests?
|
||||
async handleInstanceCreateRequest(request: lib.InstanceCreateRequest) {
|
||||
let instanceConfig = new lib.InstanceConfig("controller");
|
||||
instanceConfig.load(request.config as lib.SerializedConfig);
|
||||
const instanceConfig = new lib.InstanceConfig("controller");
|
||||
instanceConfig.update(request.config, false, "control");
|
||||
await this._controller.instanceCreate(instanceConfig);
|
||||
}
|
||||
|
||||
@@ -249,7 +247,7 @@ export default class ControlConnection extends BaseConnection {
|
||||
|
||||
async handleInstanceConfigGetRequest(request: lib.InstanceConfigGetRequest) {
|
||||
let instance = this._controller.getRequestInstance(request.instanceId);
|
||||
return new lib.InstanceConfigGetRequest.Response(instance.config.serialize("control"));
|
||||
return instance.config.toRemote("control");
|
||||
}
|
||||
|
||||
async handleInstanceConfigSetFieldRequest(request: lib.InstanceConfigSetFieldRequest) {
|
||||
|
||||
@@ -421,7 +421,7 @@ export default class Controller {
|
||||
private async _saveDataInternal() {
|
||||
if (this.config.dirty) {
|
||||
this.config.dirty = false;
|
||||
await lib.safeOutputFile(this.configPath, JSON.stringify(this.config.serialize(), null, "\t"));
|
||||
await lib.safeOutputFile(this.configPath, JSON.stringify(this.config, null, "\t"));
|
||||
}
|
||||
|
||||
let databaseDirectory = this.config.get("controller.database_directory");
|
||||
@@ -495,11 +495,9 @@ export default class Controller {
|
||||
) as Static<typeof InstanceInfo.jsonSchema>[];
|
||||
for (let json of serialized) {
|
||||
if (!json.config) { // migrate: from pre Alpha 14 format.
|
||||
json = { config: json, status: "running" }; // Use running to force updatedAt
|
||||
json = { config: json as any, status: "running" }; // Use running to force updatedAt
|
||||
}
|
||||
let instanceConfig = new lib.InstanceConfig("controller");
|
||||
instanceConfig.load(json.config as lib.SerializedConfig);
|
||||
let instance = InstanceInfo.fromJSON(json, instanceConfig);
|
||||
const instance = InstanceInfo.fromJSON(json, "controller");
|
||||
const status = instance.config.get("instance.assigned_host") === null ? "unassigned" : "unknown";
|
||||
if (instance.status !== status) {
|
||||
instance.status = status;
|
||||
@@ -519,7 +517,7 @@ export default class Controller {
|
||||
}
|
||||
|
||||
static async saveInstances(filePath: string, instances: Map<number, InstanceInfo>) {
|
||||
let serialized = [...instances.values()].map(instance => instance.toJSON());
|
||||
let serialized = [...instances.values()];
|
||||
await lib.safeOutputFile(filePath, JSON.stringify(serialized, null, "\t"));
|
||||
}
|
||||
|
||||
@@ -690,7 +688,6 @@ export default class Controller {
|
||||
*
|
||||
* @example
|
||||
* let instanceConfig = new lib.InstanceConfig("controller");
|
||||
* instanceConfig.init();
|
||||
* instanceConfig.set("instance.name", "My instance");
|
||||
* let instance = await controller.instanceAssign(instanceConfig);
|
||||
* await controller.instanceAssign(instance.id, hostId);
|
||||
@@ -806,7 +803,7 @@ export default class Controller {
|
||||
// "fieldChanged" event handler will set this.instancesDirty
|
||||
if (hostId !== undefined && newHostConnection) {
|
||||
await newHostConnection.send(
|
||||
new lib.InstanceAssignInternalRequest(instanceId, instance.config.serialize("host"))
|
||||
new lib.InstanceAssignInternalRequest(instanceId, instance.config.toRemote("host"))
|
||||
);
|
||||
} else {
|
||||
instance.status = "unassigned";
|
||||
@@ -855,7 +852,7 @@ export default class Controller {
|
||||
let connection = this.wsServer.hostConnections.get(hostId);
|
||||
if (connection) {
|
||||
await connection.send(
|
||||
new lib.InstanceAssignInternalRequest(instance.id, instance.config.serialize("host"))
|
||||
new lib.InstanceAssignInternalRequest(instance.id, instance.config.toRemote("host"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ export default class HostConnection extends BaseConnection {
|
||||
for (let instance of this._controller.instances.values()) {
|
||||
if (instance.config.get("instance.assigned_host") === this._id) {
|
||||
await this.send(
|
||||
new lib.InstanceAssignInternalRequest(instance.id, instance.config.serialize("host"))
|
||||
new lib.InstanceAssignInternalRequest(instance.id, instance.config.toRemote("host"))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -197,8 +197,8 @@ export default class HostConnection extends BaseConnection {
|
||||
// Assign instances the host has but controller does not
|
||||
const instanceUpdates: InstanceInfo[] = [];
|
||||
for (let instanceData of request.instances) {
|
||||
let instanceConfig = new lib.InstanceConfig("controller");
|
||||
instanceConfig.load(instanceData.config as lib.SerializedConfig, "host");
|
||||
const instanceConfig = new lib.InstanceConfig("controller");
|
||||
instanceConfig.update(instanceData.config, false, "host");
|
||||
|
||||
let controllerInstance = this._controller.instances.get(instanceConfig.get("instance.id"));
|
||||
if (controllerInstance) {
|
||||
@@ -237,7 +237,7 @@ export default class HostConnection extends BaseConnection {
|
||||
this._controller.addInstanceHooks(newInstance);
|
||||
await this.send(
|
||||
new lib.InstanceAssignInternalRequest(
|
||||
instanceConfig.get("instance.id"), instanceConfig.serialize("host")
|
||||
instanceConfig.get("instance.id"), instanceConfig.toRemote("host")
|
||||
)
|
||||
);
|
||||
instanceUpdates.push(newInstance);
|
||||
|
||||
@@ -18,30 +18,21 @@ export default class InstanceInfo {
|
||||
}
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"config": Type.Object({}),
|
||||
"config": lib.InstanceConfig.jsonSchema,
|
||||
"status": lib.InstanceStatus,
|
||||
"gamePort": Type.Optional(Type.Number()),
|
||||
"updatedAt": Type.Optional(Type.Number()),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>, config: lib.InstanceConfig) {
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>, location: lib.ConfigLocation) {
|
||||
return new this(
|
||||
config,
|
||||
lib.InstanceConfig.fromJSON(json.config, location),
|
||||
json.status,
|
||||
json.gamePort,
|
||||
json.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
toJSON(): Static<typeof InstanceInfo.jsonSchema> {
|
||||
return {
|
||||
config: this.config.serialize(),
|
||||
status: this.status,
|
||||
gamePort: this.gamePort,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
toInstanceDetails() {
|
||||
return new lib.InstanceDetails(
|
||||
this.config.get("instance.name"),
|
||||
|
||||
+4
-3
@@ -219,14 +219,15 @@ async function startControl() {
|
||||
;
|
||||
|
||||
logger.verbose(`Loading config from ${args.config}`);
|
||||
let controlConfig = new lib.ControlConfig("control");
|
||||
let controlConfig;
|
||||
try {
|
||||
controlConfig.load(JSON.parse(await fs.readFile(args.config, "utf8")));
|
||||
const jsonConfig = JSON.parse(await fs.readFile(args.config, "utf8"));
|
||||
controlConfig = lib.ControlConfig.fromJSON(jsonConfig, "control");
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.verbose("Config not found, initializing new config");
|
||||
controlConfig.init();
|
||||
controlConfig = new lib.ControlConfig("control");
|
||||
|
||||
} else {
|
||||
throw new lib.StartupError(`Failed to load ${args.config}: ${err.message}`);
|
||||
|
||||
@@ -56,8 +56,8 @@ async function configToKeyVal(data: string) {
|
||||
return final;
|
||||
}
|
||||
|
||||
async function serializedConfigToString(
|
||||
serializedConfig: any,
|
||||
function serializedConfigToString(
|
||||
serializedConfig: lib.SerializedConfig,
|
||||
configGroup: typeof lib.Config,
|
||||
disallowedList: Record<string, unknown>,
|
||||
) {
|
||||
@@ -91,9 +91,9 @@ const controllerConfigCommands = new lib.CommandTree({
|
||||
controllerConfigCommands.add(new lib.Command({
|
||||
definition: ["list", "List controller configuration"],
|
||||
handler: async function(args: object, control: Control) {
|
||||
let response = await control.send(new lib.ControllerConfigGetRequest());
|
||||
let config = await control.send(new lib.ControllerConfigGetRequest());
|
||||
|
||||
for (let group of (response.serializedConfig as any).groups) {
|
||||
for (let group of config.groups) {
|
||||
for (let [name, value] of Object.entries(group.fields)) {
|
||||
print(`${group.name}.${name} ${JSON.stringify(value)}`);
|
||||
}
|
||||
@@ -156,15 +156,15 @@ controllerConfigCommands.add(new lib.Command({
|
||||
});
|
||||
}],
|
||||
handler: async function(args: { editor: string }, control: Control) {
|
||||
let response = await control.send(new lib.ControllerConfigGetRequest());
|
||||
let config = await control.send(new lib.ControllerConfigGetRequest());
|
||||
let tmpFile = await lib.getTempFile("ctl-", "-tmp", os.tmpdir());
|
||||
let editor = await getEditor(args.editor);
|
||||
if (editor === undefined) {
|
||||
throw new lib.CommandError(`No editor avalible. Checked CLI input, EDITOR and VISUAL env vars
|
||||
Try "ctl controller config edit <editor of choice>"`);
|
||||
}
|
||||
let allConfigElements = await serializedConfigToString(
|
||||
response.serializedConfig, lib.ControllerConfig, {}
|
||||
let allConfigElements = serializedConfigToString(
|
||||
config, lib.ControllerConfig, {}
|
||||
);
|
||||
await fs.writeFile(tmpFile, allConfigElements);
|
||||
let editorSpawn = child_process.spawn(editor, [tmpFile], {
|
||||
@@ -272,11 +272,11 @@ hostCommands.add(new lib.Command({
|
||||
args: { id?: number, name?: string, generateToken: boolean, output: string },
|
||||
control: Control
|
||||
) {
|
||||
let rawConfig = await control.send(
|
||||
let config = await control.send(
|
||||
new lib.HostConfigCreateRequest(args.id, args.name, args.generateToken)
|
||||
);
|
||||
|
||||
let content = JSON.stringify(rawConfig.serializedConfig, null, "\t");
|
||||
let content = JSON.stringify(config, null, "\t");
|
||||
if (args.output === "-") {
|
||||
print(content);
|
||||
} else {
|
||||
@@ -315,12 +315,11 @@ instanceCommands.add(new lib.Command({
|
||||
}],
|
||||
handler: async function(args: { name: string, id?: number }, control: Control) {
|
||||
let instanceConfig = new lib.InstanceConfig("control");
|
||||
instanceConfig.init();
|
||||
if (args.id !== undefined) {
|
||||
instanceConfig.set("instance.id", args.id);
|
||||
}
|
||||
instanceConfig.set("instance.name", args.name);
|
||||
let serializedConfig = instanceConfig.serialize("controller");
|
||||
const serializedConfig = instanceConfig.toRemote("controller");
|
||||
await control.send(new lib.InstanceCreateRequest(serializedConfig));
|
||||
},
|
||||
}));
|
||||
@@ -334,9 +333,9 @@ instanceConfigCommands.add(new lib.Command({
|
||||
}],
|
||||
handler: async function(args: { instance: string }, control: Control) {
|
||||
let instanceId = await lib.resolveInstance(control, args.instance);
|
||||
let response = await control.send(new lib.InstanceConfigGetRequest(instanceId));
|
||||
let config = await control.send(new lib.InstanceConfigGetRequest(instanceId));
|
||||
|
||||
for (let group of response.serializedConfig.groups) {
|
||||
for (let group of config.groups) {
|
||||
for (let [name, value] of Object.entries(group.fields)) {
|
||||
print(`${group.name}.${name} ${JSON.stringify(value)}`);
|
||||
}
|
||||
@@ -427,7 +426,7 @@ instanceConfigCommands.add(new lib.Command({
|
||||
}],
|
||||
handler: async function(args: { instance: string, editor: string }, control: Control) {
|
||||
let instanceId = await lib.resolveInstance(control, args.instance);
|
||||
let response = await control.send(new lib.InstanceConfigGetRequest(instanceId));
|
||||
let config = await control.send(new lib.InstanceConfigGetRequest(instanceId));
|
||||
let tmpFile = await lib.getTempFile("ctl-", "-tmp", os.tmpdir());
|
||||
let editor = await getEditor(args.editor);
|
||||
if (editor === undefined) {
|
||||
@@ -435,8 +434,8 @@ instanceConfigCommands.add(new lib.Command({
|
||||
Try "ctl controller config edit <editor of choice>"`);
|
||||
}
|
||||
let disallowedList = {"instance.id": 0, "instance.assigned_host": 0, "factorio.settings": 0};
|
||||
let allConfigElements = await serializedConfigToString(
|
||||
response.serializedConfig,
|
||||
let allConfigElements = serializedConfigToString(
|
||||
config,
|
||||
lib.InstanceConfig,
|
||||
disallowedList
|
||||
);
|
||||
|
||||
@@ -146,14 +146,15 @@ async function startHost() {
|
||||
lib.finalizeConfigs();
|
||||
|
||||
logger.info(`Loading config from ${args.config}`);
|
||||
let hostConfig = new lib.HostConfig("host");
|
||||
let hostConfig;
|
||||
try {
|
||||
hostConfig.load(JSON.parse(await fs.readFile(args.config, "utf8")));
|
||||
const jsonConfig = JSON.parse(await fs.readFile(args.config, "utf8"));
|
||||
hostConfig = lib.HostConfig.fromJSON(jsonConfig, "host");
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") {
|
||||
logger.info("Config not found, initializing new config");
|
||||
hostConfig.init();
|
||||
hostConfig = new lib.HostConfig("host");
|
||||
|
||||
} else {
|
||||
throw new lib.StartupError(`Failed to load ${args.config}: ${err.message}`);
|
||||
|
||||
@@ -47,7 +47,8 @@ async function discoverInstances(instancesDir: string) {
|
||||
let configPath = path.join(instancesDir, entry.name, "instance.json");
|
||||
|
||||
try {
|
||||
instanceConfig.load(JSON.parse(await fs.readFile(configPath, "utf8")));
|
||||
const jsonConfig = JSON.parse(await fs.readFile(configPath, "utf8"));
|
||||
instanceConfig = lib.InstanceConfig.fromJSON(jsonConfig, "host");
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") {
|
||||
@@ -564,17 +565,17 @@ export default class Host extends lib.Link {
|
||||
let { instanceId, config } = request;
|
||||
let instanceInfo = this.instanceInfos.get(instanceId);
|
||||
if (instanceInfo) {
|
||||
instanceInfo.config.update(config as any, true, "controller");
|
||||
instanceInfo.config.update(config, true, "controller");
|
||||
logger.verbose(`Updated config for ${instanceInfo.path}`, this.instanceLogMeta(instanceId, instanceInfo));
|
||||
|
||||
} else {
|
||||
instanceInfo = this.discoveredInstanceInfos.get(instanceId);
|
||||
if (instanceInfo) {
|
||||
instanceInfo.config.update(config as any, true, "controller");
|
||||
instanceInfo.config.update(config, true, "controller");
|
||||
|
||||
} else {
|
||||
let instanceConfig = new lib.InstanceConfig("host");
|
||||
instanceConfig.load(config as any, "controller");
|
||||
instanceConfig.update(config, false, "controller");
|
||||
|
||||
let instanceDir = await this._createNewInstanceDir(instanceConfig.get("instance.name"));
|
||||
|
||||
@@ -608,7 +609,7 @@ export default class Host extends lib.Link {
|
||||
// save a copy of the instance config
|
||||
let warnedOutput = {
|
||||
_warning: "Changes to this file will be overwritten by the controller's copy.",
|
||||
...instanceInfo.config.serialize(),
|
||||
...instanceInfo.config.toJSON(),
|
||||
};
|
||||
await lib.safeOutputFile(
|
||||
path.join(instanceInfo.path, "instance.json"),
|
||||
@@ -920,7 +921,7 @@ export default class Host extends lib.Link {
|
||||
for (let [instanceId, instanceInfo] of this.discoveredInstanceInfos) {
|
||||
let instanceConnection = this.instanceConnections.get(instanceId);
|
||||
list.push(new lib.RawInstanceInfo(
|
||||
instanceInfo.config.serialize("controller"),
|
||||
instanceInfo.config.toRemote("controller"),
|
||||
instanceConnection ? instanceConnection.instance.status : "stopped",
|
||||
instanceConnection ? instanceConnection.instance.server.gamePort : undefined,
|
||||
));
|
||||
@@ -984,7 +985,7 @@ export default class Host extends lib.Link {
|
||||
}
|
||||
|
||||
logger.info("Saving config");
|
||||
await lib.safeOutputFile(this.configPath, JSON.stringify(this.config.serialize(), null, "\t"));
|
||||
await lib.safeOutputFile(this.configPath, JSON.stringify(this.config, null, "\t"));
|
||||
|
||||
try {
|
||||
// Clear silly interval in pidfile library.
|
||||
|
||||
+117
-129
@@ -4,9 +4,12 @@ import events from "events";
|
||||
|
||||
import isDeepStrictEqual from "../is_deep_strict_equal";
|
||||
import { basicType } from "../helpers";
|
||||
import * as libSchema from "../schema";
|
||||
import { StringEnum } from "../data";
|
||||
|
||||
|
||||
export type ConfigLocation = "controller" | "host" | "instance" | "control";
|
||||
const ConfigLocation = StringEnum(["controller", "host", "instance", "control"]);
|
||||
export type ConfigLocation = Static<typeof ConfigLocation>;
|
||||
|
||||
/**
|
||||
* Invalid Access exception
|
||||
@@ -59,8 +62,18 @@ function splitOn(separator: string, string: string) {
|
||||
return [string.slice(0, index), string.slice(index + separator.length)];
|
||||
}
|
||||
|
||||
export type SerializedConfig = ReturnType<Config["serialize"]>;
|
||||
type SerializedGroup = ReturnType<ConfigGroup["serialize"]>;
|
||||
|
||||
const ConfigGroupSchema = Type.Object({
|
||||
"name": Type.String(),
|
||||
"fields": Type.Record(Type.String(), Type.Unknown()),
|
||||
});
|
||||
type SerializedGroup = Static<typeof ConfigGroupSchema>;
|
||||
|
||||
const ConfigSchema = Type.Object({
|
||||
"groups": Type.Array(ConfigGroupSchema),
|
||||
});
|
||||
export type SerializedConfig = Static<typeof ConfigSchema>
|
||||
|
||||
|
||||
/**
|
||||
* Collection of config groups
|
||||
@@ -80,10 +93,10 @@ export class Config extends events.EventEmitter {
|
||||
declare static _finalized: boolean;
|
||||
declare static _initialized: boolean;
|
||||
declare static _groups: Map<string, typeof ConfigGroup>;
|
||||
declare ["constructor"]: typeof Config;
|
||||
|
||||
_initialized = false;
|
||||
_groups = new Map<string, ConfigGroup>();
|
||||
_unknownGroups = new Map<string, SerializedGroup>();
|
||||
_unknownGroups = new Map<string, Static<typeof ConfigGroup.jsonSchema>>();
|
||||
|
||||
/** Set to true when a field in the config is changed. */
|
||||
dirty = false;
|
||||
@@ -93,66 +106,64 @@ export class Config extends events.EventEmitter {
|
||||
*
|
||||
* @param location -
|
||||
* Location to evaluate access for this instance from
|
||||
* @param groups -
|
||||
* Serialized representation of groups to load.
|
||||
*/
|
||||
constructor(
|
||||
public location: ConfigLocation,
|
||||
groups: Static<typeof Config.jsonSchema>["groups"] = [],
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!(this.constructor as typeof Config)._finalized) {
|
||||
throw new Error(`Cannot instantiate incomplete Config class ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
if (typeof location !== "string") {
|
||||
throw new Error("location must be a string");
|
||||
}
|
||||
}
|
||||
super();
|
||||
|
||||
init() {
|
||||
for (let [name, GroupClass] of (this.constructor as typeof Config)._groups) {
|
||||
if (!this._groups.has(name)) {
|
||||
let group = new GroupClass(this);
|
||||
group.init();
|
||||
this._groups.set(name, group);
|
||||
}
|
||||
}
|
||||
this._initialized = true;
|
||||
}
|
||||
|
||||
_validate(serializedConfig: SerializedConfig) {
|
||||
if (basicType(serializedConfig) !== "object") {
|
||||
throw new Error(`Expected object, not ${basicType(serializedConfig)} for config`);
|
||||
if (!this.constructor._finalized) {
|
||||
throw new Error(`Cannot instantiate incomplete Config class ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
if (basicType(serializedConfig.groups) !== "array") {
|
||||
throw new Error(`Expected groups to be an array, not ${basicType(serializedConfig.groups)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load config from a serialized object
|
||||
*
|
||||
* @param serializedConfig - Serialized config to load.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
load(
|
||||
serializedConfig: SerializedConfig,
|
||||
location = this.location
|
||||
) {
|
||||
this._validate(serializedConfig);
|
||||
for (let serializedGroup of serializedConfig.groups) {
|
||||
let GroupClass = (this.constructor as typeof Config)._groups.get(serializedGroup.name);
|
||||
for (let serializedGroup of groups) {
|
||||
let GroupClass = this.constructor._groups.get(serializedGroup.name);
|
||||
if (!GroupClass) {
|
||||
this._unknownGroups.set(serializedGroup.name, serializedGroup);
|
||||
continue;
|
||||
}
|
||||
|
||||
let group = new GroupClass(this);
|
||||
group.load(serializedGroup, location);
|
||||
let group = GroupClass.fromJSON(serializedGroup, this);
|
||||
this._groups.set(group.name, group);
|
||||
}
|
||||
|
||||
this.init();
|
||||
for (let [name, GroupClass] of this.constructor._groups) {
|
||||
if (!this._groups.has(name)) {
|
||||
this._groups.set(name, new GroupClass(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static jsonSchema = ConfigSchema;
|
||||
|
||||
static validate = libSchema.compile(this.jsonSchema as any);
|
||||
|
||||
/**
|
||||
* Create config from a serialized object
|
||||
*
|
||||
* @param json - Serialized config to load.
|
||||
* @param location - Location used for access control.
|
||||
* @returns Instance of this config
|
||||
*/
|
||||
static fromJSON(
|
||||
json: Static<typeof this.jsonSchema>,
|
||||
location: ConfigLocation,
|
||||
) {
|
||||
const valid = Config.validate(json);
|
||||
if (!valid) {
|
||||
throw new Error("Invalid config");
|
||||
}
|
||||
return new this(location, json.groups);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.toRemote(this.location);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,10 +172,10 @@ export class Config extends events.EventEmitter {
|
||||
* @param location - Location used for access control.
|
||||
* @returns JSON serializable representation of the config.
|
||||
*/
|
||||
serialize(location = this.location) {
|
||||
let groups: SerializedGroup[] = [...this._unknownGroups.values()];
|
||||
toRemote(location: ConfigLocation): Static<typeof Config.jsonSchema> {
|
||||
let groups: Static<typeof ConfigGroup.jsonSchema>[] = [...this._unknownGroups.values()];
|
||||
for (let group of this._groups.values()) {
|
||||
groups.push(group.serialize(location));
|
||||
groups.push(group.toRemote(location));
|
||||
}
|
||||
|
||||
return { groups };
|
||||
@@ -176,16 +187,19 @@ export class Config extends events.EventEmitter {
|
||||
* Updates all groups in the config with groups in the serialized
|
||||
* config passed.
|
||||
*
|
||||
* @param serializedConfig - Output from .serialize().
|
||||
* @param serializedConfig - Output from .toRemote().
|
||||
* @param notify - Invoke fieldChanged events if true.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
update(
|
||||
serializedConfig: SerializedConfig,
|
||||
serializedConfig: Static<typeof Config.jsonSchema>,
|
||||
notify: boolean,
|
||||
location = this.location
|
||||
) {
|
||||
this._validate(serializedConfig);
|
||||
const valid = Config.validate(serializedConfig);
|
||||
if (!valid) {
|
||||
throw new Error("Invalid config");
|
||||
}
|
||||
for (let serializedGroup of serializedConfig.groups) {
|
||||
let group = this._groups.get(serializedGroup.name);
|
||||
if (group) {
|
||||
@@ -252,9 +266,6 @@ export class Config extends events.EventEmitter {
|
||||
* @returns config group.
|
||||
*/
|
||||
group(name: string) {
|
||||
if (!this._initialized) {
|
||||
throw new Error(`${this.constructor.name} instance is uninitialized`);
|
||||
}
|
||||
let group = this._groups.get(name);
|
||||
if (!group) {
|
||||
throw new InvalidField(`No config group named '${name}'`);
|
||||
@@ -321,32 +332,6 @@ export class Config extends events.EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove after config refactor
|
||||
export class RawConfig {
|
||||
constructor(
|
||||
public serializedConfig: SerializedConfig
|
||||
) { }
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"serializedConfig": Type.Object({
|
||||
"groups": Type.Array(
|
||||
Type.Object({
|
||||
"name": Type.String(),
|
||||
"fields": Type.Record(
|
||||
Type.String(),
|
||||
Type.Unknown(),
|
||||
),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||
return new this(json.serializedConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type FieldType = "boolean" | "string" | "number" | "object";
|
||||
export interface FieldDefinition {
|
||||
type: FieldType;
|
||||
@@ -376,6 +361,7 @@ export class ConfigGroup {
|
||||
declare static _definitions: Map<string, FieldDefinition>;
|
||||
declare static groupName: string;
|
||||
declare static defaultAccess: ConfigLocation[];
|
||||
declare ["constructor"]: typeof ConfigGroup;
|
||||
|
||||
_fields = new Map<string, unknown>();
|
||||
_unknownFields = new Map<string, unknown>();
|
||||
@@ -384,18 +370,41 @@ export class ConfigGroup {
|
||||
/**
|
||||
* Creates a new config group.
|
||||
*
|
||||
* After the creation of the new config group you have to call
|
||||
* either .init() or .load() on it in order to fully initialize it.
|
||||
*
|
||||
* @param config -
|
||||
* Parent config for this group instance.
|
||||
* @param serializedGroup -
|
||||
* Serialized representation of group to load.
|
||||
*/
|
||||
constructor(
|
||||
public config: Config
|
||||
public config: Config,
|
||||
serializedGroup?: Static<typeof ConfigGroup.jsonSchema>,
|
||||
) {
|
||||
if (!(this.constructor as typeof ConfigGroup)._finalized) {
|
||||
if (!this.constructor._finalized) {
|
||||
throw new Error(`Cannot instantiate incomplete ConfigGroup class ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
if (serializedGroup) {
|
||||
this.update(serializedGroup, false, config.location);
|
||||
}
|
||||
|
||||
for (let [name, def] of this.constructor._definitions) {
|
||||
if (this._fields.has(name)) {
|
||||
continue;
|
||||
}
|
||||
if (!def.access.includes(this.config.location)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = null;
|
||||
if (typeof def.initial_value === "function") {
|
||||
value = def.initial_value();
|
||||
|
||||
} else if (def.initial_value !== undefined) {
|
||||
value = def.initial_value;
|
||||
}
|
||||
|
||||
this._fields.set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,32 +431,7 @@ export class ConfigGroup {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize new config group
|
||||
*
|
||||
* Computes and assigns the initial values for all fields of the
|
||||
* config group.
|
||||
*/
|
||||
init() {
|
||||
for (let [name, def] of (this.constructor as typeof ConfigGroup)._definitions) {
|
||||
if (this._fields.has(name)) {
|
||||
continue;
|
||||
}
|
||||
if (!def.access.includes(this.config.location)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = null;
|
||||
if (typeof def.initial_value === "function") {
|
||||
value = def.initial_value();
|
||||
|
||||
} else if (def.initial_value !== undefined) {
|
||||
value = def.initial_value;
|
||||
}
|
||||
|
||||
this._fields.set(name, value);
|
||||
}
|
||||
}
|
||||
static jsonSchema = ConfigGroupSchema;
|
||||
|
||||
/**
|
||||
* Load from serialized group
|
||||
@@ -456,13 +440,13 @@ export class ConfigGroup {
|
||||
* group and then initializes any possible missing fields from it to
|
||||
* their default values.
|
||||
*
|
||||
* @param serializedGroup -
|
||||
* Result from a previous call to .serialize().
|
||||
* @param location - Location used for access control.
|
||||
* @param json -
|
||||
* Result from a previous call to .toJSON().
|
||||
* @param config - Parent config for this group instance.
|
||||
* @returns Instance of this group.
|
||||
*/
|
||||
load(serializedGroup: SerializedGroup, location = this.config.location) {
|
||||
this.update(serializedGroup, false, location);
|
||||
this.init();
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>, config: Config) {
|
||||
return new this(config, json);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,7 +455,7 @@ export class ConfigGroup {
|
||||
* Shortcut for `group.constructor.groupName`
|
||||
*/
|
||||
get name() {
|
||||
return (this.constructor as typeof ConfigGroup).groupName;
|
||||
return this.constructor.groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,7 +466,7 @@ export class ConfigGroup {
|
||||
* @returns true if field is accessible
|
||||
*/
|
||||
canAccess(name: string, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
let def = this.constructor._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -497,7 +481,7 @@ export class ConfigGroup {
|
||||
* @returns Value stored for the field.
|
||||
*/
|
||||
get(name: string, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
let def = this.constructor._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -519,7 +503,7 @@ export class ConfigGroup {
|
||||
* @throws {InvalidValue} if value is not allowed for the field.
|
||||
*/
|
||||
set(name: string, value: unknown, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
let def = this.constructor._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -588,7 +572,7 @@ export class ConfigGroup {
|
||||
* @throws {InvalidValue} if field is not an object.
|
||||
*/
|
||||
setProp(name: string, prop: string, value: unknown, location = this.config.location) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
let def = this.constructor._definitions.get(name);
|
||||
if (!def) {
|
||||
throw new InvalidField(`No field named '${name}'`);
|
||||
}
|
||||
@@ -620,12 +604,12 @@ export class ConfigGroup {
|
||||
* has a valid value in the passed serialized group with that value.
|
||||
*
|
||||
* @param serializedGroup -
|
||||
* Result from a previous call to .serialize().
|
||||
* Result from a previous call to .toRemote().
|
||||
* @param notify - Invoke fieldChanged events if true.
|
||||
* @param location - Location used for access control.
|
||||
*/
|
||||
update(
|
||||
serializedGroup: SerializedGroup,
|
||||
serializedGroup: Static<typeof ConfigGroup.jsonSchema>,
|
||||
notify: boolean,
|
||||
location = this.config.location
|
||||
) {
|
||||
@@ -642,7 +626,7 @@ export class ConfigGroup {
|
||||
}
|
||||
|
||||
for (let [name, value] of Object.entries(serializedGroup.fields)) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name);
|
||||
let def = this.constructor._definitions.get(name);
|
||||
if (!def) {
|
||||
this._unknownFields.set(name, value);
|
||||
continue;
|
||||
@@ -674,16 +658,20 @@ export class ConfigGroup {
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.toRemote(this.config.location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the config to a plain JavaScript object
|
||||
*
|
||||
* @param location - Location used for access control.
|
||||
* @returns JSON serializable representation of the group.
|
||||
*/
|
||||
serialize(location: ConfigLocation = this.config.location) {
|
||||
toRemote(location: ConfigLocation): Static<typeof ConfigGroup.jsonSchema> {
|
||||
let fields: Record<string, unknown> = {};
|
||||
for (let [name, value] of this._fields) {
|
||||
let def = (this.constructor as typeof ConfigGroup)._definitions.get(name)!;
|
||||
let def = this.constructor._definitions.get(name)!;
|
||||
if (!this._checkAccess(def, location, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ ControllerGroup.finalize();
|
||||
* @extends classes.Config
|
||||
*/
|
||||
export class ControllerConfig extends classes.Config {
|
||||
declare static fromJSON: (json: classes.SerializedConfig, location: classes.ConfigLocation) => ControllerConfig;
|
||||
get(name: "controller.name"): string;
|
||||
get(name: "controller.mods_directory"): string;
|
||||
get(name: "controller.database_directory"): string;
|
||||
@@ -255,6 +256,7 @@ HostGroup.finalize();
|
||||
* @extends classes.Config
|
||||
*/
|
||||
export class HostConfig extends classes.Config {
|
||||
declare static fromJSON: (json: classes.SerializedConfig, location: classes.ConfigLocation) => HostConfig;
|
||||
get(name: "host.name"): string;
|
||||
get(name: "host.id"): number;
|
||||
get(name: "host.factorio_directory"): string;
|
||||
@@ -433,6 +435,8 @@ FactorioGroup.finalize();
|
||||
* @extends classes.Config
|
||||
*/
|
||||
export class InstanceConfig extends classes.Config {
|
||||
declare static fromJSON: (json: classes.SerializedConfig, location: classes.ConfigLocation) => InstanceConfig;
|
||||
|
||||
get(name: "instance.name"): string;
|
||||
get(name: "instance.id"): number;
|
||||
get(name: "instance.assigned_host"): number | null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { Static, TSchema, Type } from "@sinclair/typebox";
|
||||
|
||||
export function StringEnum<T extends string[]>(values: [...T]) {
|
||||
return Type.Unsafe<T[number]>({ type: "string", enum: values });
|
||||
@@ -32,3 +32,12 @@ export function jsonPrimitive<T, U>(type: U) {
|
||||
export const JsonBoolean = jsonPrimitive<boolean, "boolean">("boolean");
|
||||
export const JsonNumber = jsonPrimitive<number, "number">("number");
|
||||
export const JsonString = jsonPrimitive<string, "string">("string");
|
||||
|
||||
export function plainJson<T extends TSchema>(schema: T) {
|
||||
return {
|
||||
jsonSchema: schema,
|
||||
fromJSON(json: Static<T>) {
|
||||
return json;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type, Static } from "@sinclair/typebox";
|
||||
import { JsonString, StringEnum } from "./composites";
|
||||
import { JsonString, StringEnum, plainJson } from "./composites";
|
||||
import { levels } from "../logging";
|
||||
import { RawConfig } from "../config";
|
||||
import { ControllerConfig, HostConfig } from "../config";
|
||||
|
||||
export class ControllerConfigGetRequest {
|
||||
declare ["constructor"]: typeof ControllerConfigGetRequest;
|
||||
@@ -9,7 +9,7 @@ export class ControllerConfigGetRequest {
|
||||
static src = "control" as const;
|
||||
static dst = "controller" as const;
|
||||
static permission = "core.controller.get_config" as const;
|
||||
static Response = RawConfig;
|
||||
static Response = plainJson(ControllerConfig.jsonSchema);
|
||||
}
|
||||
|
||||
export class ControllerConfigSetFieldRequest {
|
||||
@@ -103,7 +103,7 @@ export class HostConfigCreateRequest {
|
||||
return new this(json.id, json.name, json.generateToken);
|
||||
}
|
||||
|
||||
static Response = RawConfig;
|
||||
static Response = plainJson(HostConfig.jsonSchema);
|
||||
}
|
||||
|
||||
export class LogSetSubscriptionsRequest {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type, Static } from "@sinclair/typebox";
|
||||
import PlayerStats from "./PlayerStats";
|
||||
import { JsonString, StringEnum, jsonArray } from "./composites";
|
||||
import { RawConfig } from "../config";
|
||||
import { JsonString, StringEnum, jsonArray, plainJson } from "./composites";
|
||||
import { InstanceConfig } from "../config";
|
||||
import type { IControllerUser } from "./User";
|
||||
import type { MessageRequest } from "./messages_core";
|
||||
import { CollectorResultSerialized } from "../prometheus";
|
||||
@@ -133,15 +133,14 @@ export class InstanceCreateRequest {
|
||||
static permission = "core.instance.create" as const;
|
||||
|
||||
constructor(
|
||||
public config: object,
|
||||
public config: Static<typeof InstanceConfig.jsonSchema>,
|
||||
) { }
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"config": Type.Object({}),
|
||||
"config": InstanceConfig.jsonSchema,
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||
// TODO deserialise config here after config refactor
|
||||
return new this(json.config);
|
||||
}
|
||||
}
|
||||
@@ -152,8 +151,7 @@ export class InstanceConfigGetRequest {
|
||||
static src = "control" as const;
|
||||
static dst = "controller" as const;
|
||||
static permission = "core.instance.get_config" as const;
|
||||
// TODO replace with InstanceConfig after config refactor
|
||||
static Response = RawConfig;
|
||||
static Response = plainJson(InstanceConfig.jsonSchema);
|
||||
|
||||
constructor(
|
||||
public instanceId: number,
|
||||
@@ -716,15 +714,14 @@ export class InstanceSendRconRequest {
|
||||
|
||||
// TODO remove this after config refactor
|
||||
export class RawInstanceInfo {
|
||||
|
||||
constructor(
|
||||
public config: object,
|
||||
public config: Static<typeof InstanceConfig.jsonSchema>,
|
||||
public status: InstanceStatus,
|
||||
public gamePort: undefined | number,
|
||||
) { }
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"config": Type.Object({}),
|
||||
"config": InstanceConfig.jsonSchema,
|
||||
"status": StringEnum([
|
||||
"stopped", "starting", "running", "stopping", "creating_save", "exporting_data",
|
||||
]),
|
||||
@@ -765,16 +762,15 @@ export class InstanceAssignInternalRequest {
|
||||
|
||||
constructor(
|
||||
public instanceId: number,
|
||||
public config: object,
|
||||
public config: Static<typeof InstanceConfig.jsonSchema>,
|
||||
) { }
|
||||
|
||||
static jsonSchema = Type.Object({
|
||||
"instanceId": Type.Integer(),
|
||||
"config": Type.Object({}),
|
||||
"config": InstanceConfig.jsonSchema,
|
||||
});
|
||||
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||
// TODO deserialise config here after config refactor
|
||||
return new this(json.instanceId, json.config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ export async function handleConfigCommand(
|
||||
|
||||
try {
|
||||
instance.set(args.field as string, args.value);
|
||||
await libFileOps.safeOutputFile(configPath, JSON.stringify(instance.serialize(), null, "\t"));
|
||||
await libFileOps.safeOutputFile(configPath, JSON.stringify(instance, null, "\t"));
|
||||
} catch (err) {
|
||||
if (err instanceof libConfig.InvalidField || err instanceof libConfig.InvalidValue) {
|
||||
logger.error(err.message);
|
||||
|
||||
@@ -48,8 +48,7 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) {
|
||||
|
||||
async function updateConfig() {
|
||||
let serializedConfig = await props.retrieveConfig();
|
||||
let newConfig = new props.ConfigClass("control");
|
||||
newConfig.load(serializedConfig);
|
||||
const newConfig = props.ConfigClass.fromJSON(serializedConfig, "control");
|
||||
setConfig(newConfig);
|
||||
return newConfig;
|
||||
}
|
||||
@@ -197,12 +196,11 @@ export default function BaseConfigTree(props: BaseConfigTreeProps) {
|
||||
</Form.Item>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
onClick={() => {
|
||||
let propName = form.getFieldValue(`${newPropPath}.name`);
|
||||
let propValue = form.getFieldValue(`${newPropPath}.value`);
|
||||
if (!Object.prototype.hasOwnProperty.call(value, propName)) {
|
||||
let newConfig = new props.ConfigClass("control");
|
||||
await newConfig.load(config!.serialize());
|
||||
let newConfig = props.ConfigClass.fromJSON(config!.toJSON(), "control");
|
||||
newConfig.setProp(fieldName, propName, null);
|
||||
setConfig(newConfig);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ export default function ControllerConfigTree() {
|
||||
let control = useContext(ControlContext);
|
||||
|
||||
async function retrieveConfig() {
|
||||
let result = await control.send(new lib.ControllerConfigGetRequest());
|
||||
return result.serializedConfig;
|
||||
return await control.send(new lib.ControllerConfigGetRequest());
|
||||
}
|
||||
|
||||
async function setField(field: string, value: any) {
|
||||
|
||||
@@ -14,8 +14,7 @@ export default function InstanceConfigTree(props: InstanceConfigTreeProps) {
|
||||
let control = useContext(ControlContext);
|
||||
|
||||
async function retrieveConfig() {
|
||||
let result = await control.send(new lib.InstanceConfigGetRequest(props.id));
|
||||
return result.serializedConfig;
|
||||
return await control.send(new lib.InstanceConfigGetRequest(props.id));
|
||||
}
|
||||
|
||||
async function setField(field: string, value: any) {
|
||||
|
||||
@@ -27,9 +27,8 @@ function CreateInstanceButton() {
|
||||
}
|
||||
|
||||
let instanceConfig = new lib.InstanceConfig("control");
|
||||
instanceConfig.init();
|
||||
instanceConfig.set("instance.name", values.instanceName);
|
||||
let serializedConfig = instanceConfig.serialize("controller");
|
||||
const serializedConfig = instanceConfig.toRemote("controller");
|
||||
await control.send(new lib.InstanceCreateRequest(serializedConfig));
|
||||
setOpen(false);
|
||||
navigate(`/instances/${instanceConfig.get("instance.id")}/view`);
|
||||
|
||||
@@ -14,19 +14,18 @@ describe("controller/InstanceInfo", function() {
|
||||
if (!validate(json)) {
|
||||
throw validate.errors;
|
||||
}
|
||||
assert.deepEqual(InstanceInfo.fromJSON(json, info.config), info);
|
||||
assert.deepEqual(InstanceInfo.fromJSON(json, "controller"), info);
|
||||
}
|
||||
|
||||
if (!lib.InstanceConfig._finalized) {
|
||||
lib.InstanceConfig.finalize();
|
||||
}
|
||||
const config = new lib.InstanceConfig("controller");
|
||||
config.init();
|
||||
const config = new lib.InstanceConfig("controller").toJSON();
|
||||
|
||||
check(InstanceInfo.fromJSON({ config, status: "stopped" }, config));
|
||||
check(InstanceInfo.fromJSON({ config, status: "running" }, config));
|
||||
check(InstanceInfo.fromJSON({ config, status: "running", gamePort: 34197 }, config));
|
||||
check(InstanceInfo.fromJSON({ config, status: "running", updatedAt: Date.now() }, config));
|
||||
check(InstanceInfo.fromJSON({ config, status: "stopped" }, "controller"));
|
||||
check(InstanceInfo.fromJSON({ config, status: "running" }, "controller"));
|
||||
check(InstanceInfo.fromJSON({ config, status: "running", gamePort: 34197 }, "controller"));
|
||||
check(InstanceInfo.fromJSON({ config, status: "running", updatedAt: Date.now() }, "controller"));
|
||||
|
||||
// All at once
|
||||
check(InstanceInfo.fromJSON({
|
||||
@@ -34,7 +33,7 @@ describe("controller/InstanceInfo", function() {
|
||||
status: "unknown",
|
||||
gamePort: 10000,
|
||||
updateAt: 20000,
|
||||
}, config));
|
||||
}, "controller"));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,7 +17,6 @@ describe("Host testing", function() {
|
||||
let instanceInfos = await _discoverInstances(instancePath);
|
||||
|
||||
let referenceConfig = new lib.InstanceConfig("host");
|
||||
referenceConfig.init();
|
||||
referenceConfig.set("instance.id", 1);
|
||||
referenceConfig.set("instance.name", "test");
|
||||
referenceConfig.dirty = false;
|
||||
|
||||
@@ -21,7 +21,6 @@ describe("class Instance", function() {
|
||||
let connector;
|
||||
beforeEach(function() {
|
||||
let instanceConfig = new lib.InstanceConfig("host");
|
||||
instanceConfig.init();
|
||||
instanceConfig.set("instance.name", "foo");
|
||||
src = addr({ instanceId: instanceConfig.get("instance.id") });
|
||||
connector = new MockConnector(src, dst);
|
||||
|
||||
@@ -206,7 +206,7 @@ describe("Integration of Clusterio", function() {
|
||||
it("should not leak auth_secret", async function() {
|
||||
let result = await getControl().send(new lib.ControllerConfigGetRequest());
|
||||
let done = false;
|
||||
for (let group of result.serializedConfig.groups) {
|
||||
for (let group of result.groups) {
|
||||
if (group.name === "controller") {
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(group.fields, "auth_secret"), false);
|
||||
done = true;
|
||||
@@ -221,7 +221,7 @@ describe("Integration of Clusterio", function() {
|
||||
await execCtl('controller config set controller.name "Test Cluster"');
|
||||
let result = await getControl().send(new lib.ControllerConfigGetRequest());
|
||||
let done = false;
|
||||
for (let group of result.serializedConfig.groups) {
|
||||
for (let group of result.groups) {
|
||||
if (group.name === "controller") {
|
||||
assert.equal(group.fields.name, "Test Cluster");
|
||||
done = true;
|
||||
|
||||
+52
-93
@@ -163,13 +163,8 @@ describe("lib/config/classes", function() {
|
||||
it("should create an instance", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".init()", function() {
|
||||
it("should initialize all fields with defaults", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.init();
|
||||
|
||||
assert.equal(testInstance.get("enum"), "b");
|
||||
assert.equal(testInstance.get("test"), null);
|
||||
assert.equal(testInstance.get("func"), 42);
|
||||
@@ -177,7 +172,6 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
it("should not initalize inaccessible fields", function() {
|
||||
let testInstance = new TestGroup({ location: "remote" });
|
||||
testInstance.init();
|
||||
assert.equal(testInstance._fields.get("enum"), "b");
|
||||
assert.equal(testInstance._fields.get("priv"), undefined);
|
||||
});
|
||||
@@ -186,17 +180,14 @@ describe("lib/config/classes", function() {
|
||||
describe("get name", function() {
|
||||
it("should give the name of the group", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.init();
|
||||
assert.equal(testInstance.name, "test_group");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".serialize()", function() {
|
||||
describe(".toJSON()", function() {
|
||||
it("should serialize a basic group", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.init();
|
||||
|
||||
assert.deepEqual(testInstance.serialize(), {
|
||||
assert.deepEqual(testInstance.toJSON(), {
|
||||
name: "test_group",
|
||||
fields: {
|
||||
enum: "b",
|
||||
@@ -208,11 +199,12 @@ describe("lib/config/classes", function() {
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(".toRemote()", function() {
|
||||
it("should leave out inaccessible fields", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.init();
|
||||
|
||||
assert.deepEqual(testInstance.serialize("remote"), {
|
||||
assert.deepEqual(testInstance.toRemote("remote"), {
|
||||
name: "test_group",
|
||||
fields: {
|
||||
enum: "b",
|
||||
@@ -225,18 +217,16 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe(".load()", function() {
|
||||
describe(".fromJSON()", function() {
|
||||
it("should load a basic group", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: {
|
||||
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: {
|
||||
enum: "c",
|
||||
test: "blah",
|
||||
func: 22,
|
||||
bool: null,
|
||||
json: { valid: true },
|
||||
priv: "bar",
|
||||
}});
|
||||
|
||||
}}, mockConfig);
|
||||
assert.equal(testInstance.get("enum"), "c");
|
||||
assert.equal(testInstance.get("test"), "blah");
|
||||
assert.equal(testInstance.get("func"), 22);
|
||||
@@ -245,17 +235,16 @@ describe("lib/config/classes", function() {
|
||||
assert.deepEqual(testInstance.get("json"), { valid: true });
|
||||
});
|
||||
it("should load defaults for missing serialized fields", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: { enum: "a" } });
|
||||
|
||||
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: { enum: "a" } }, mockConfig);
|
||||
assert.equal(testInstance.get("enum"), "a");
|
||||
assert.equal(testInstance.get("test"), null);
|
||||
assert.equal(testInstance.get("func"), 42);
|
||||
});
|
||||
it("should ignore inaccessible fields", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: { priv: "bad" } }, "remote");
|
||||
assert.equal(testInstance.get("priv"), null);
|
||||
let remoteConfig = new events.EventEmitter();
|
||||
remoteConfig.location = "remote";
|
||||
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: { priv: "bad" } }, remoteConfig);
|
||||
assert.equal(testInstance._fields.get("priv"), undefined);
|
||||
});
|
||||
|
||||
it("should preserve unknown fields when serialized back", function() {
|
||||
@@ -271,30 +260,24 @@ describe("lib/config/classes", function() {
|
||||
gamma: 99,
|
||||
};
|
||||
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: testFields });
|
||||
|
||||
assert.deepEqual(testInstance.serialize().fields, testFields);
|
||||
let testInstance = TestGroup.fromJSON({ name: "test_group", fields: testFields }, mockConfig);
|
||||
assert.deepEqual(testInstance.toJSON().fields, testFields);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".update()", function() {
|
||||
it("should skip updating invalid values", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.init();
|
||||
testInstance.update({ name: "test_group", fields: { enum: "wrong", test: 3, func: null }}, false);
|
||||
|
||||
assert.equal(testInstance.get("enum"), "b");
|
||||
assert.equal(testInstance.get("test"), null);
|
||||
assert.equal(testInstance.get("func"), 42);
|
||||
});
|
||||
it("should skip updating inaccessible fields", function() {
|
||||
let testInstance = new TestGroup(mockConfig);
|
||||
testInstance.init();
|
||||
testInstance.update(
|
||||
{ name: "test_group", fields: { test: "a", priv: "bad" }}, false, "remote"
|
||||
);
|
||||
|
||||
assert.equal(testInstance.get("test"), "a");
|
||||
assert.equal(testInstance.get("priv"), null);
|
||||
});
|
||||
@@ -327,10 +310,10 @@ describe("lib/config/classes", function() {
|
||||
describe(".canAccess()", function() {
|
||||
let testInstance;
|
||||
before(function() {
|
||||
testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: {
|
||||
enum: "a", priv: "blah", func: 27,
|
||||
}});
|
||||
testInstance = new TestGroup(mockConfig, {
|
||||
name: "test_group",
|
||||
fields: { enum: "a", priv: "blah", func: 27 },
|
||||
});
|
||||
});
|
||||
it("should return true for fields that are accessible", function() {
|
||||
for (let field of ["enum", "test", "func", "bool", "json"]) {
|
||||
@@ -347,10 +330,10 @@ describe("lib/config/classes", function() {
|
||||
describe(".get()", function() {
|
||||
let testInstance;
|
||||
before(function() {
|
||||
testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: {
|
||||
enum: "a", priv: "blah", func: 27,
|
||||
}});
|
||||
testInstance = new TestGroup(mockConfig, {
|
||||
name: "test_group",
|
||||
fields: { enum: "a", priv: "blah", func: 27 },
|
||||
});
|
||||
});
|
||||
it("should retreive value of field", function() {
|
||||
assert.equal(testInstance.get("enum"), "a");
|
||||
@@ -368,8 +351,10 @@ describe("lib/config/classes", function() {
|
||||
describe(".set()", function() {
|
||||
let testInstance;
|
||||
before(function() {
|
||||
testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: { enum: "a", test: "blah", func: 27 }});
|
||||
testInstance = TestGroup.fromJSON(
|
||||
{ name: "test_group", fields: { enum: "a", test: "blah", func: 27 }},
|
||||
mockConfig,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if field does not exist", function() {
|
||||
@@ -473,10 +458,10 @@ describe("lib/config/classes", function() {
|
||||
describe(".setProp()", function() {
|
||||
let testInstance;
|
||||
before(function() {
|
||||
testInstance = new TestGroup(mockConfig);
|
||||
testInstance.load({ name: "test_group", fields: {
|
||||
enum: "a", test: "blah", func: 27,
|
||||
}});
|
||||
testInstance = TestGroup.fromJSON(
|
||||
{ name: "test_group", fields: { enum: "a", test: "blah", func: 27 }},
|
||||
mockConfig,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if field does not exist", function() {
|
||||
@@ -585,22 +570,17 @@ describe("lib/config/classes", function() {
|
||||
new Error("location must be a string")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".init()", function() {
|
||||
it("should initialize all fields", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
assert.equal(testInstance.get("alpha.foo"), null);
|
||||
assert.deepEqual(testInstance.get("beta.bar"), {});
|
||||
});
|
||||
});
|
||||
|
||||
describe(".serialize()", function() {
|
||||
describe(".toJSON()", function() {
|
||||
it("should serialize a basic config", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
assert.deepEqual(testInstance.serialize(), {
|
||||
assert.deepEqual(testInstance.toJSON(), {
|
||||
groups: [
|
||||
{
|
||||
name: "alpha",
|
||||
@@ -615,27 +595,24 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe(".load()", function() {
|
||||
it("should reject on incorrect input passed", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
describe("static .fromJSON()", function() {
|
||||
it("should throw on incorrect input passed", function() {
|
||||
assert.throws(
|
||||
testInstance.load(null),
|
||||
new Error("Expected object, not null for config")
|
||||
() => TestConfig.fromJSON(null, "local"),
|
||||
new Error("Invalid config")
|
||||
);
|
||||
assert.throws(
|
||||
testInstance.load(undefined),
|
||||
new Error("Expected object, not undefined for config")
|
||||
() => TestConfig.fromJSON(undefined, "local"),
|
||||
new Error("Invalid config")
|
||||
);
|
||||
assert.throws(
|
||||
testInstance.load({ groups: null }),
|
||||
new Error("Expected groups to be an array, not null")
|
||||
() => TestConfig.fromJSON({ groups: null }, "local"),
|
||||
new Error("Invalid config")
|
||||
);
|
||||
});
|
||||
|
||||
it("should load defaults for missing serialized groups", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.load({ groups: [{ name: "alpha", fields: { foo: "a" }}] });
|
||||
|
||||
let testInstance = TestConfig.fromJSON({ groups: [{ name: "alpha", fields: { foo: "a" }}] }, "local");
|
||||
assert.equal(testInstance.get("alpha.foo"), "a");
|
||||
assert.deepEqual(testInstance.get("beta.bar"), {});
|
||||
});
|
||||
@@ -655,18 +632,14 @@ describe("lib/config/classes", function() {
|
||||
fields: { bar: { value: 20 }},
|
||||
},
|
||||
];
|
||||
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.load({ groups: testGroups });
|
||||
|
||||
assert.deepEqual(testInstance.serialize().groups, testGroups);
|
||||
let testInstance = TestConfig.fromJSON({ groups: testGroups }, "local");
|
||||
assert.deepEqual(testInstance.toJSON().groups, testGroups);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".update()", function() {
|
||||
it("should update a basic config", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
testInstance.update({ groups: [
|
||||
{
|
||||
name: "extra",
|
||||
@@ -678,7 +651,7 @@ describe("lib/config/classes", function() {
|
||||
},
|
||||
]}, false);
|
||||
|
||||
assert.deepEqual(testInstance.serialize(), {
|
||||
assert.deepEqual(testInstance.toJSON(), {
|
||||
groups: [
|
||||
{
|
||||
name: "extra",
|
||||
@@ -698,34 +671,25 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
|
||||
describe(".group()", function() {
|
||||
it("should throw if instance is not initialized", function() {
|
||||
it("should retrieve a given group", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
assert.throws(
|
||||
() => testInstance.group("test"),
|
||||
new Error("TestConfig instance is uninitialized")
|
||||
);
|
||||
assert(testInstance.group("alpha") instanceof AlphaGroup);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".canAccess()", function() {
|
||||
it("should throw if group does not exist", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
assert.throws(() => testInstance.canAccess("invalid"), new Error("No config group named 'invalid'"));
|
||||
});
|
||||
|
||||
it("should throw if no field is specified", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
assert.throws(() => testInstance.canAccess("alpha"), new Error("No field named ''"));
|
||||
});
|
||||
|
||||
it("should return true for an accessible field", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
assert.equal(testInstance.canAccess("alpha.foo"), true);
|
||||
});
|
||||
});
|
||||
@@ -733,15 +697,11 @@ describe("lib/config/classes", function() {
|
||||
describe(".get()", function() {
|
||||
it("should throw if group does not exist", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
assert.throws(() => testInstance.get("invalid"), new Error("No config group named 'invalid'"));
|
||||
});
|
||||
|
||||
it("should throw if no field is specified", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
assert.throws(() => testInstance.get("alpha"), new Error("No field named ''"));
|
||||
});
|
||||
});
|
||||
@@ -749,8 +709,6 @@ describe("lib/config/classes", function() {
|
||||
describe(".set()", function() {
|
||||
it("should set the value", function() {
|
||||
let testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
testInstance.set("alpha.foo", "new value");
|
||||
assert.equal(testInstance.get("alpha.foo"), "new value");
|
||||
});
|
||||
@@ -767,8 +725,6 @@ describe("lib/config/classes", function() {
|
||||
let called;
|
||||
beforeEach(function() {
|
||||
testInstance = new TestConfig("local");
|
||||
testInstance.init();
|
||||
|
||||
called = false;
|
||||
testInstance.once("fieldChanged", (group, field, prev) => {
|
||||
if (group.name === "beta" && field === "bar") {
|
||||
@@ -783,7 +739,10 @@ describe("lib/config/classes", function() {
|
||||
});
|
||||
|
||||
it("should be called when updating a config", function() {
|
||||
testInstance.update({ groups: [{ name: "beta", fields: { bar: { value: 1 }}}]}, true);
|
||||
testInstance.update({
|
||||
location: "local",
|
||||
groups: [{ name: "beta", fields: { bar: { value: 1 }}}],
|
||||
}, true);
|
||||
assert(called, "fieldChanged was not called");
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user