diff --git a/server/server.py b/server/server.py index 7404594..12a0f3c 100755 --- a/server/server.py +++ b/server/server.py @@ -69,6 +69,14 @@ class WebSockets(object): for target in targets: target.send(msgstr) + def BroadcastTargets(self): + self.Broadcast(self.masters, { + 'type': 'targets', + 'data': { + 'targets': list(self.targets.keys()), + }, + }) + class BaseWSHandler(websocket.WebSocket): def opened(self, image_types): @@ -92,6 +100,7 @@ def GetSlaveWSHandler(image_types, websockets): websockets.slaves.remove(self) if self._hostname: del websockets.targets[self._hostname] + websockets.BroadcastTargets() def received_message(self, msg): parsed = json.loads(str(msg)) @@ -108,6 +117,7 @@ def GetSlaveWSHandler(image_types, websockets): if 'hostname' in parsed['data']: self._hostname = parsed['data']['hostname'] websockets.targets[self._hostname] = self + websockets.BroadcastTargets() return SlaveWSHandler @@ -117,6 +127,12 @@ def GetMasterWSHandler(image_types, websockets): def opened(self): super().opened(image_types) websockets.masters.add(self) + self.send(json.dumps({ + 'type': 'targets', + 'data': { + 'targets': list(websockets.targets.keys()), + }, + })) def closed(self, code, reason=None): websockets.masters.remove(self) diff --git a/server/static/control.css b/server/static/control.css index 8cd57cf..24c5af8 100644 --- a/server/static/control.css +++ b/server/static/control.css @@ -35,13 +35,17 @@ header { padding: 2px; } -hostname, lastSeen, uptime, timestamp, volumeID, reboot { +hostname, lastReport, uptime, timestamp, volumeID, reboot { font-family: "droid-sans-mono"; display: table-cell; padding: 2px; text-align: right; } +instanceSection.live hostname { + color: #3299bb; +} + volumeID { cursor: pointer; } diff --git a/server/static/control.js b/server/static/control.js index 4d1ee68..38cb5e0 100644 --- a/server/static/control.js +++ b/server/static/control.js @@ -25,6 +25,8 @@ ImageController.prototype.onMessage_ = function(msg) { return this.onImageTypes_(msg['data']); case 'report': return this.onReport_(msg['data']); + case 'targets': + return this.onTargets_(msg['data']); } }; @@ -50,7 +52,7 @@ ImageController.prototype.addImageType_ = function(type) { this.insertSorted_(this.container_, value.section, type); let headers = this.createNode_(value.section, 'headers'); this.createNode_(headers, 'header', 'Hostname'); - this.createNode_(headers, 'header', 'Last seen'); + this.createNode_(headers, 'header', 'Last report'); this.createNode_(headers, 'header', 'Uptime'); this.createNode_(headers, 'header', 'Current image'); this.createNode_(headers, 'header', 'Current volume ID'); @@ -70,8 +72,8 @@ ImageController.prototype.onReport_ = function(msg) { this.addInstance_(type, msg['hostname']); } let instance = type.instances.get(msg['hostname']); - instance.last_report = Math.floor(Date.now() / 1000); - instance.last_seen.innerText = this.formatSeconds_(0); + instance.last_report_timestamp = Math.floor(Date.now() / 1000); + instance.last_report.innerText = this.formatSeconds_(0); instance.uptime.innerText = this.formatSeconds_(msg['uptime_seconds']); instance.timestamp.innerText = msg['timestamp']; let volume_id_len = localStorage.getItem('volume_id_len') || Number.POSITIVE_INFINITY; @@ -86,7 +88,7 @@ ImageController.prototype.addInstance_ = function(type, hostname) { }; this.insertSorted_(type.section, value.section, hostname); this.createNode_(value.section, 'hostname', hostname); - value.last_seen = this.createNode_(value.section, 'lastSeen'); + value.last_report = this.createNode_(value.section, 'lastReport'); value.uptime = this.createNode_(value.section, 'uptime'); value.timestamp = this.createNode_(value.section, 'timestamp'); value.volume_id = this.createNode_(value.section, 'volumeID'); @@ -104,12 +106,25 @@ ImageController.prototype.addInstance_ = function(type, hostname) { type.instances.set(hostname, value); }; +ImageController.prototype.onTargets_ = function(msg) { + let targets = new Set(msg['targets']); + for (let [type, type_value] of this.image_types_) { + for (let [instance, instance_value] of type_value.instances) { + if (targets.has(instance)) { + instance_value.section.classList.add('live'); + } else { + instance_value.section.classList.remove('live'); + } + } + } +}; + ImageController.prototype.onTick_ = function() { let now = Math.floor(Date.now() / 1000); - for (let [type, type_section] of this.image_types_) { - for (let [instance, instance_section] of type_section.instances) { - instance_section.last_seen.innerText = - this.formatSeconds_(now - instance_section.last_report); + for (let [type, type_value] of this.image_types_) { + for (let [instance, instance_value] of type_value.instances) { + instance_value.last_report.innerText = + this.formatSeconds_(now - instance_value.last_report_timestamp); } } };