From 2be91cbd3a24f58c5d9ce6b57de3b22004b68a3e Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 2 Jun 2021 16:19:18 +0200 Subject: [PATCH] Add slave update subscriptions Allow the web interface to listen for updates to the slave list and apply it live to the places where slaves are shown in the web interface. --- packages/ctl/ctl.js | 2 + packages/lib/link/messages.js | 36 ++++++++--- packages/lib/users.js | 6 ++ packages/master/src/ControlConnection.js | 18 ++++++ packages/master/src/Master.js | 18 ++++++ packages/master/src/WsServer.js | 2 + .../src/components/AssignInstanceModal.jsx | 12 ++-- packages/web_ui/src/components/SlavesPage.jsx | 40 ++++++++---- packages/web_ui/src/model/slave.jsx | 45 ++++++++++++++ packages/web_ui/src/util/websocket.jsx | 61 +++++++++++++++++++ test/integration/clusterio.js | 55 ++++++++++++++++- test/integration/index.js | 9 +++ 12 files changed, 275 insertions(+), 29 deletions(-) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index f691fe08..04c50c73 100755 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -918,6 +918,8 @@ class Control extends libLink.Link { this.keepOpen = false; } + async slaveUpdateEventHandler() { } + async instanceUpdateEventHandler() { } async saveListUpdateEventHandler() { } diff --git a/packages/lib/link/messages.js b/packages/lib/link/messages.js index 4e3c17e1..72dbe3a8 100644 --- a/packages/lib/link/messages.js +++ b/packages/lib/link/messages.js @@ -331,6 +331,14 @@ messages.setMasterConfigProp = new Request({ }, }); +let slaveProperties = { + "agent": { type: "string" }, + "version": { type: "string" }, + "name": { type: "string" }, + "id": { type: "integer" }, + "connected": { type: "boolean" }, +}; + messages.listSlaves = new Request({ type: "list_slaves", links: ["control-master"], @@ -341,18 +349,25 @@ messages.listSlaves = new Request({ items: { additionalProperties: false, required: ["agent", "version", "name", "id", "connected"], - properties: { - "agent": { type: "string" }, - "version": { type: "string" }, - "name": { type: "string" }, - "id": { type: "integer" }, - "connected": { type: "boolean" }, - }, + properties: slaveProperties, }, }, }, }); +messages.setSlaveSubscriptions = new Request({ + type: "set_slave_subscriptions", + links: ["control-master"], + permission: "core.slave.subscribe", + requestProperties: { + "all": { type: "boolean" }, + "slave_ids": { + type: "array", + items: { type: "integer" }, + }, + }, +}); + let instanceProperties = { "name": { type: "string" }, "id": { type: "integer" }, @@ -1083,6 +1098,13 @@ messages.logMessage = new Event({ }, }); +messages.slaveUpdate = new Event({ + type: "slave_update", + links: ["master-control"], + eventProperties: slaveProperties, +}); + + messages.instanceInitialized = new Event({ type: "instance_initialized", links: ["instance-slave"], diff --git a/packages/lib/users.js b/packages/lib/users.js index c6c0060a..3f91ab11 100644 --- a/packages/lib/users.js +++ b/packages/lib/users.js @@ -322,6 +322,12 @@ definePermission({ description: "Get the full list of slaves in the cluster.", grantByDefault: true, }); +definePermission({ + name: "core.slave.subscribe", + title: "Subscribe to slave updates", + description: "Subscribe to be notified on updates on the details of slaves.", + grantByDefault: true, +}); definePermission({ name: "core.slave.generate_token", title: "Generate slave token", diff --git a/packages/master/src/ControlConnection.js b/packages/master/src/ControlConnection.js index 4f1c4fbd..ad53cb0b 100644 --- a/packages/master/src/ControlConnection.js +++ b/packages/master/src/ControlConnection.js @@ -39,6 +39,11 @@ class ControlConnection extends BaseConnection { */ this.user = user; + this.slaveSubscriptions = { + all: false, + slave_ids: [], + }; + this.instanceSubscriptions = { all: false, instance_ids: [], @@ -107,6 +112,19 @@ class ControlConnection extends BaseConnection { return { list }; } + async setSlaveSubscriptionsRequestHandler(message) { + this.slaveSubscriptions = message.data; + } + + slaveUpdated(slave, update) { + if ( + this.slaveSubscriptions.all + || this.slaveSubscriptions.slave_ids.includes(slave.id) + ) { + libLink.messages.slaveUpdate.send(this, update); + } + } + generateSlaveToken(slaveId) { return jwt.sign({ aud: "slave", slave: slaveId }, this._master.config.get("master.auth_secret")); } diff --git a/packages/master/src/Master.js b/packages/master/src/Master.js index 8f97c975..f7590049 100644 --- a/packages/master/src/Master.js +++ b/packages/master/src/Master.js @@ -360,6 +360,24 @@ class Master { } } + slaveUpdated(slave) { + let update = { + agent: slave.agent, + version: slave.version, + id: slave.id, + name: slave.name, + connected: this.wsServer.slaveConnections.has(slave.id), + }; + + for (let controlConnection of this.wsServer.controlConnections) { + if (controlConnection.connector.closing) { + continue; + } + + controlConnection.slaveUpdated(slave, update); + } + } + addInstanceHooks(instance) { instance.config.on("fieldChanged", (group, field, prev) => { if (group.name === "instance" && field === "name") { diff --git a/packages/master/src/WsServer.js b/packages/master/src/WsServer.js index 3c77824f..47342420 100644 --- a/packages/master/src/WsServer.js +++ b/packages/master/src/WsServer.js @@ -274,9 +274,11 @@ ${err.stack}` connector.on("close", () => { if (this.slaveConnections.get(data.id) === connection) { this.slaveConnections.delete(data.id); + this.master.slaveUpdated(this.master.slaves.get(data.id)); } }); this.slaveConnections.set(data.id, connection); + this.master.slaveUpdated(this.master.slaves.get(data.id)); } else if (type === "register_control") { diff --git a/packages/web_ui/src/components/AssignInstanceModal.jsx b/packages/web_ui/src/components/AssignInstanceModal.jsx index 8a0b2399..ad06f474 100644 --- a/packages/web_ui/src/components/AssignInstanceModal.jsx +++ b/packages/web_ui/src/components/AssignInstanceModal.jsx @@ -5,24 +5,20 @@ import libLink from "@clusterio/lib/link"; import ControlContext from "./ControlContext"; import { notifyErrorHandler } from "../util/notify"; +import { useSlaveList } from "../model/slave"; const { Paragraph } = Typography; export default function AssignInstanceModal(props) { let [visible, setVisible] = useState(false); - let [slaves, setSlaves] = useState([]); - let [loading, setLoading] = useState(true); + let [slaveList] = useSlaveList(); let [applying, setApplying] = useState(false); let [form] = Form.useForm(); let control = useContext(ControlContext); function open() { setVisible(true); - libLink.messages.listSlaves.send(control).then((result) => { - setSlaves(result.list); - setLoading(false); - }); } function handleAssign() { @@ -73,8 +69,8 @@ export default function AssignInstanceModal(props) {
-