Send command to reboot into specific versions (still needs client support)

This commit is contained in:
Ian Gulliver
2016-05-11 18:48:38 +00:00
parent 6244e64b80
commit 072dee1264
3 changed files with 158 additions and 57 deletions

View File

@@ -1,29 +1,74 @@
body {
font-family: "proxima-nova", sans-serif;
color: #424242;
}
imageTypeSection {
container {
font-family: "proxima-nova", sans-serif;
color: #424242;
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
}
overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
max-height: 100%;
overflow: scroll;
}
instances, versions {
display: table;
border-spacing: 3px;
margin: 10px;
}
imageTypeSection::before {
instances::before, versions::before {
display: block;
font-size: x-large;
font-weight: bold;
content: attr(data-key);
}
headers, instanceSection {
versions {
display: none;
padding: 10px;
background: white;
border: 1px solid black;
}
versions.live {
display: table;
position: relative;
}
close {
position: absolute;
top: 5px;
right: 5px;
font-size: xx-large;
cursor: pointer;
}
versionList {
display: table-row-group;
}
headers, instance, version {
display: table-row;
}
instanceSection:nth-child(2n) {
instance:nth-child(2n) {
background-color: #e9e9e9;
}
instanceSection:hover {
instance:hover {
background-color: #ff9900 !important;
}
@@ -35,7 +80,7 @@ header {
padding: 2px;
}
hostname, lastReport, uptime, timestamp, volumeID, reboot {
hostname, lastReport, uptime, timestamp, volumeID, reboot, command {
font-family: "droid-sans-mono";
display: table-cell;
padding: 2px;
@@ -46,7 +91,18 @@ volumeID {
cursor: pointer;
}
instanceSection.live reboot {
instance.live reboot {
cursor: pointer;
color: #3299bb;
}
command {
cursor: pointer;
color: #3299bb;
}
hostnameLabel {
font-family: "droid-sans-mono";
font-size: x-large;
font-weight: bold;
}

View File

@@ -2,6 +2,7 @@
let ImageController = function(container) {
this.container_ = container;
this.overlay_ = this.createNode_(this.container_, 'overlay');
this.image_types_ = new Map();
this.connect_();
@@ -20,20 +21,20 @@ ImageController.prototype.onClose_ = function() {
};
ImageController.prototype.onMessage_ = function(msg) {
switch (msg['type']) {
switch (msg.type) {
case 'image_types':
return this.onImageTypes_(msg['data']);
return this.onImageTypes_(msg.data);
case 'new_manifest':
return this.onNewManfiest_(msg['data']);
return this.onNewManfiest_(msg.data);
case 'report':
return this.onReport_(msg['data']);
return this.onReport_(msg.data);
case 'targets':
return this.onTargets_(msg['data']);
return this.onTargets_(msg.data);
}
};
ImageController.prototype.onImageTypes_ = function(msg) {
let image_types = new Set(msg['image_types']);
let image_types = new Set(msg.image_types);
for (let type of image_types) {
if (!this.image_types_.has(type)) {
this.addImageType_(type);
@@ -46,14 +47,14 @@ ImageController.prototype.onImageTypes_ = function(msg) {
};
};
ImageController.prototype.addImageType_ = function(type) {
let value = {
section: document.createElement('imageTypeSection'),
ImageController.prototype.addImageType_ = function(name) {
let type = {
name: name,
section: document.createElement('instances'),
instances: new Map(),
images: [],
};
this.insertSorted_(this.container_, value.section, type);
let headers = this.createNode_(value.section, 'headers');
this.insertSorted_(this.container_, type.section, name);
let headers = this.createNode_(type.section, 'headers');
this.createNode_(headers, 'header', 'Hostname');
this.createNode_(headers, 'header', 'Last report');
this.createNode_(headers, 'header', 'Uptime');
@@ -61,75 +62,110 @@ ImageController.prototype.addImageType_ = function(type) {
this.createNode_(headers, 'header', 'Current volume ID');
this.createNode_(headers, 'header', 'Next image');
this.createNode_(headers, 'header', 'Next volume ID');
this.image_types_.set(type, value);
type.version_section = this.createNode_(this.overlay_, 'versions');
type.version_section.setAttribute('data-key', name);
type.version_hostname = this.createNode_(type.version_section, 'hostnameLabel');
let close = this.createNode_(type.version_section, 'close', '✗');
close.addEventListener(
'click', (e) => type.version_section.classList.remove('live'));
headers = this.createNode_(type.version_section, 'headers');
this.createNode_(headers, 'header', 'Image');
this.createNode_(headers, 'header', 'Volume ID');
type.versions = this.createNode_(type.version_section, 'versionList');
this.image_types_.set(name, type);
this.fetchManifest_(type);
};
ImageController.prototype.removeImageType_ = function(type) {
this.container_.removeChild(this.image_types_.get(type).section);
this.image_types_.delete(type);
this.container_.removeChild(type.section);
this.image_types_.delete(type.name);
};
ImageController.prototype.onNewManifest_ = function(msg) {
this.fetchManifest_(msg['image_type']);
this.fetchManifest_(msg.image_type);
};
ImageController.prototype.fetchManifest_ = function(type) {
let xhr = new XMLHttpRequest();
xhr.addEventListener('load', (e) => this.onFetchManifest_(type, xhr.response));
xhr.responseType = 'json';
xhr.open('GET', 'https://' + location.host + '/image/' + type + '/manifest.json');
xhr.open('GET', 'https://' + location.host + '/image/' + type.name + '/manifest.json');
xhr.send();
};
ImageController.prototype.onFetchManifest_ = function(type, wrapper) {
let manifest = JSON.parse(wrapper.inner);
this.image_types_.get(type).images = manifest.images;
let volume_id_len = localStorage.getItem('volume_id_len') || Number.POSITIVE_INFINITY;
type.versions.innerHTML = '';
for (let image of manifest.images) {
image.volume_id = image.volume_id || '';
let version = this.createNode_(type.versions, 'version');
this.createNode_(version, 'timestamp', image.timestamp);
let volume_id =
this.createNode_(version, 'volumeID', image.volume_id.substring(0, volume_id_len));
volume_id.addEventListener(
'click', (e) => this.onVolumeIDClick_(e.target.innerText));
let reboot_into = this.createNode_(version, 'command', 'Reboot into');
reboot_into.addEventListener(
'click', (e) => {
this.sendReboot_(type.version_hostname.innerText, image.timestamp);
type.version_section.classList.remove('live');
});
}
};
ImageController.prototype.onReport_ = function(msg) {
let type = this.image_types_.get(msg['image_type']);
if (!type.instances.has(msg['hostname'])) {
this.addInstance_(type, msg['hostname']);
let type = this.image_types_.get(msg.image_type);
if (!type.instances.has(msg.hostname)) {
this.addInstance_(type, msg.hostname);
}
let instance = type.instances.get(msg['hostname']);
let instance = type.instances.get(msg.hostname);
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'];
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;
instance.volume_id.innerText = msg['volume_id'].substring(0, volume_id_len);
instance.next_timestamp.innerText = msg['next_timestamp'];
instance.next_volume_id.innerText = msg['next_volume_id'].substring(0, volume_id_len);
msg.volume_id = msg.volume_id || '';
instance.volume_id.innerText = msg.volume_id.substring(0, volume_id_len);
instance.next_timestamp.innerText = msg.next_timestamp;
msg.next_volume_id = msg.next_volume_id || '';
instance.next_volume_id.innerText = msg.next_volume_id.substring(0, volume_id_len);
};
ImageController.prototype.addInstance_ = function(type, hostname) {
let value = {
section: document.createElement('instanceSection'),
let instance = {
section: document.createElement('instance'),
};
this.insertSorted_(type.section, value.section, hostname);
this.createNode_(value.section, 'hostname', hostname);
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');
value.next_timestamp = this.createNode_(value.section, 'timestamp');
value.next_volume_id = this.createNode_(value.section, 'volumeID');
value.reboot = this.createNode_(value.section, 'reboot', 'Reboot');
this.insertSorted_(type.section, instance.section, hostname);
this.createNode_(instance.section, 'hostname', hostname);
instance.last_report = this.createNode_(instance.section, 'lastReport');
instance.uptime = this.createNode_(instance.section, 'uptime');
instance.timestamp = this.createNode_(instance.section, 'timestamp');
instance.volume_id = this.createNode_(instance.section, 'volumeID');
instance.next_timestamp = this.createNode_(instance.section, 'timestamp');
instance.next_volume_id = this.createNode_(instance.section, 'volumeID');
instance.reboot = this.createNode_(instance.section, 'reboot', 'Reboot');
instance.reboot_into = this.createNode_(instance.section, 'reboot', 'Reboot into');
value.volume_id.addEventListener(
instance.volume_id.addEventListener(
'click', (e) => this.onVolumeIDClick_(e.target.innerText));
value.next_volume_id.addEventListener(
instance.next_volume_id.addEventListener(
'click', (e) => this.onVolumeIDClick_(e.target.innerText));
value.reboot.addEventListener(
instance.reboot.addEventListener(
'click', (e) => this.sendReboot_(hostname));
instance.reboot_into.addEventListener(
'click', (e) => this.onRebootIntoClick_(type, hostname));
type.instances.set(hostname, value);
type.instances.set(hostname, instance);
};
ImageController.prototype.onTargets_ = function(msg) {
let targets = new Set(msg['targets']);
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)) {
@@ -159,14 +195,23 @@ ImageController.prototype.onVolumeIDClick_ = function(volume_id) {
open(base_url.replace('VOLUMEID', volume_id));
};
ImageController.prototype.sendReboot_ = function(hostname) {
this.ws_.send(JSON.stringify({
ImageController.prototype.onRebootIntoClick_ = function(type, hostname) {
type.version_hostname.innerText = hostname;
type.version_section.classList.add('live');
};
ImageController.prototype.sendReboot_ = function(hostname, opt_timestamp) {
let command = {
'type': 'command',
'target': hostname,
'data': {
'command': 'reboot',
},
}));
};
if (opt_timestamp) {
command.data.timestamp = opt_timestamp;
}
this.ws_.send(JSON.stringify(command));
};
ImageController.prototype.insertSorted_ = function(parent, new_child, key) {
@@ -208,5 +253,5 @@ ImageController.prototype.formatSeconds_ = function(seconds) {
document.addEventListener('DOMContentLoaded', (e) => {
new ImageController(document.getElementsByTagName('imageContainer')[0]);
new ImageController(document.getElementsByTagName('container')[0]);
});

View File

@@ -8,6 +8,6 @@
<link rel="stylesheet" type="text/css" href="/static/control.css">
</head>
<body>
<imageContainer></imageContainer>
<container></container>
</body>
</html>