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