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) {
-