diff --git a/packages/web_ui/src/components/BaseConfigTree.jsx b/packages/web_ui/src/components/BaseConfigTree.jsx
new file mode 100644
index 00000000..7fcc1548
--- /dev/null
+++ b/packages/web_ui/src/components/BaseConfigTree.jsx
@@ -0,0 +1,249 @@
+import React, { useEffect, useContext, useState } from "react";
+import { Button, Card, Checkbox, Form, Input, InputNumber, Space, Spin, Tree, Typography } from "antd";
+
+const { Title } = Typography;
+
+
+export default function BaseConfigTree(props) {
+ let [config, setConfig] = useState(null);
+ let [form] = Form.useForm();
+ let [changedFields, setChangedFields] = useState(new Set());
+ let [errorFields, setErrorFields] = useState(new Map());
+ let [applying, setApplying] = useState(false);
+
+ async function updateConfig() {
+ let serializedConfig = await props.retrieveConfig();
+ let newConfig = new props.ConfigClass("control");
+ await newConfig.load(serializedConfig);
+ setConfig(newConfig);
+ }
+ useEffect(() => { updateConfig(); }, [props.id]);
+
+ function renderInput(def) {
+ if (def.type === "boolean") {
+ return ;
+ }
+ if (def.type === "string") {
+ return ;
+ }
+ if (def.type === "number") {
+ return ;
+ }
+
+ return `Unknown type ${def.type}`;
+ }
+
+ if (config === null) {
+ return ;
+ }
+
+ let initialValues = {};
+ function onValuesChange(changedValues) {
+ let newChangedFields = new Set(changedFields);
+ let changed = false;
+ for (let [key, value] of Object.entries(changedValues)) {
+ if (initialValues[key] === value) {
+ changed = newChangedFields.delete(key) || changed;
+ } else {
+ changed = !newChangedFields.has(key) || changed;
+ newChangedFields.add(key);
+ }
+ }
+
+ if (changed) {
+ setChangedFields(newChangedFields);
+ }
+ }
+
+ let treeData = [];
+ let propsMap = new Map();
+ for (let [name, GroupClass] of props.ConfigClass.groups) {
+ let group = config.group(name);
+ let treeNode = {
+ key: group.name,
+ title: group.name,
+ children: [],
+ };
+
+ for (let def of GroupClass.definitions.values()) {
+ if (!group.canAccess(def.name)) {
+ continue;
+ }
+
+ let value = group.get(def.name);
+ let fieldName = `${group.name}.${def.name}`;
+ let childNode = {
+ key: fieldName,
+ children: [],
+ };
+
+ if (def.type === "object") {
+ childNode.title =
;
+ for (let prop of Object.keys(value)) {
+ let propPath = `${group.name}.${def.name}.${prop}`;
+ childNode.children.push({
+ key: propPath,
+ title: {
+ if (!fieldValue.length) {
+ return Promise.reject(new Error("Will be removed"));
+ }
+ try {
+ JSON.parse(fieldValue);
+ } catch (err) {
+ return Promise.reject(new Error("Must be valid json"));
+ }
+ return Promise.resolve();
+ }}]}
+ >
+
+ ,
+ });
+
+ initialValues[propPath] = JSON.stringify(value[prop]);
+ propsMap.set(propPath, [fieldName, prop]);
+ }
+
+ let newPropPath = `${group.name}.${def.name}:add`;
+ childNode.children.push({
+ key: newPropPath,
+ title:
+
+
+ :
+
+
+
+
+ ,
+ });
+
+ } else {
+ childNode.title =
+ {renderInput(def)}
+ ;
+ initialValues[fieldName] = value;
+ }
+
+ treeNode.children.push(childNode);
+ }
+
+ treeData.push(treeNode);
+ }
+
+ return <>
+
+ Config
+
+
+
+
+
+
+
+
+ >;
+}
diff --git a/packages/web_ui/src/components/InstanceConfigTree.jsx b/packages/web_ui/src/components/InstanceConfigTree.jsx
index e5a29865..eaefa3c2 100644
--- a/packages/web_ui/src/components/InstanceConfigTree.jsx
+++ b/packages/web_ui/src/components/InstanceConfigTree.jsx
@@ -1,277 +1,52 @@
-import React, { useEffect, useContext, useState } from "react";
-import { Button, Card, Checkbox, Form, Input, InputNumber, Space, Spin, Tree, Typography } from "antd";
+import React, { useContext } from "react";
+import { Typography } from "antd";
import libLink from "@clusterio/lib/link";
import libConfig from "@clusterio/lib/config";
+import BaseConfigTree from "./BaseConfigTree";
import ControlContext from "./ControlContext";
-const { Title } = Typography;
-
export default function InstanceConfigTree(props) {
let control = useContext(ControlContext);
- let [config, setConfig] = useState(null);
- let [form] = Form.useForm();
- let [changedFields, setChangedFields] = useState(new Set());
- let [errorFields, setErrorFields] = useState(new Map());
- let [applying, setApplying] = useState(false);
- async function updateConfig() {
+ async function retrieveConfig() {
let result = await libLink.messages.getInstanceConfig.send(control, {
instance_id: props.id,
});
-
- let newConfig = new libConfig.InstanceConfig("control");
- await newConfig.load(result.serialized_config);
- setConfig(newConfig);
+ return result.serialized_config;
}
- useEffect(() => { updateConfig(); }, [props.id]);
-
- function renderInput(def) {
- if (def.type === "boolean") {
- return ;
- }
- if (def.type === "string") {
- return ;
- }
- if (def.type === "number") {
- return ;
- }
-
- return `Unknown type ${def.type}`;
+ async function setField(field, value) {
+ await libLink.messages.setInstanceConfigField.send(control, {
+ instance_id: props.id,
+ field,
+ value: String(value),
+ });
}
- if (config === null) {
- return ;
- }
-
- let initialValues = {};
- function onValuesChange(changedValues) {
- let newChangedFields = new Set(changedFields);
- let changed = false;
- for (let [key, value] of Object.entries(changedValues)) {
- if (initialValues[key] === value) {
- changed = newChangedFields.delete(key) || changed;
- } else {
- changed = !newChangedFields.has(key) || changed;
- newChangedFields.add(key);
- }
- }
-
- if (changed) {
- setChangedFields(newChangedFields);
- }
- }
-
- let treeData = [];
- let propsMap = new Map();
- for (let [name, GroupClass] of libConfig.InstanceConfig.groups) {
- let group = config.group(name);
- let treeNode = {
- key: group.name,
- title: group.name,
- children: [],
+ async function setProp(field, prop, value) {
+ let data = {
+ instance_id: props.id,
+ field,
+ prop,
};
-
- for (let def of GroupClass.definitions.values()) {
- if (!group.canAccess(def.name)) {
- continue;
+ if (value) {
+ try {
+ data.value = JSON.parse(value);
+ } catch (err) {
+ return;
}
-
- let value = group.get(def.name);
- let fieldName = `${group.name}.${def.name}`;
- let childNode = {
- key: fieldName,
- children: [],
- };
-
- if (def.type === "object") {
- childNode.title = ;
- for (let prop of Object.keys(value)) {
- let propPath = `${group.name}.${def.name}.${prop}`;
- childNode.children.push({
- key: propPath,
- title: {
- if (!fieldValue.length) {
- return Promise.reject(new Error("Will be removed"));
- }
- try {
- JSON.parse(fieldValue);
- } catch (err) {
- return Promise.reject(new Error("Must be valid json"));
- }
- return Promise.resolve();
- }}]}
- >
-
- ,
- });
-
- initialValues[propPath] = JSON.stringify(value[prop]);
- propsMap.set(propPath, [fieldName, prop]);
- }
-
- let newPropPath = `${group.name}.${def.name}:add`;
- childNode.children.push({
- key: newPropPath,
- title:
-
-
- :
-
-
-
-
- ,
- });
-
- } else {
- childNode.title =
- {renderInput(def)}
- ;
- initialValues[fieldName] = value;
- }
-
- treeNode.children.push(childNode);
}
-
- treeData.push(treeNode);
+ await libLink.messages.setInstanceConfigProp.send(control, data);
}
- return <>
-
- Config
-
-
-
-
-
-
-
-
- >;
+ return ;
}
-