2016-05-05 00:33:30 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
2016-05-10 23:55:34 +00:00
|
|
|
let ImageController = function(container) {
|
|
|
|
|
this.container_ = container;
|
|
|
|
|
this.image_types_ = new Map();
|
|
|
|
|
|
2016-05-11 00:24:44 +00:00
|
|
|
this.connect_();
|
|
|
|
|
|
|
|
|
|
this.timer_ = setInterval((e) => this.onTick_(), 250);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.connect_ = function() {
|
2016-05-10 23:55:34 +00:00
|
|
|
this.ws_ = new WebSocket('wss://' + location.host + '/ws/master', 'iconograph-master');
|
|
|
|
|
this.ws_.addEventListener('message', (e) => this.onMessage_(JSON.parse(e.data)));
|
2016-05-11 00:24:44 +00:00
|
|
|
this.ws_.addEventListener('close', (e) => this.onClose_());
|
|
|
|
|
};
|
2016-05-10 23:55:34 +00:00
|
|
|
|
2016-05-11 00:24:44 +00:00
|
|
|
ImageController.prototype.onClose_ = function() {
|
|
|
|
|
setTimeout((e) => this.connect_(), 5000);
|
2016-05-10 23:55:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.onMessage_ = function(msg) {
|
|
|
|
|
switch (msg['type']) {
|
|
|
|
|
case 'image_types':
|
|
|
|
|
return this.onImageTypes_(msg['data']);
|
2016-05-11 05:21:42 +00:00
|
|
|
case 'new_manifest':
|
|
|
|
|
return this.onNewManfiest_(msg['data']);
|
2016-05-10 23:55:34 +00:00
|
|
|
case 'report':
|
|
|
|
|
return this.onReport_(msg['data']);
|
2016-05-11 00:55:01 +00:00
|
|
|
case 'targets':
|
|
|
|
|
return this.onTargets_(msg['data']);
|
2016-05-10 23:55:34 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.onImageTypes_ = function(msg) {
|
|
|
|
|
let image_types = new Set(msg['image_types']);
|
|
|
|
|
for (let type of image_types) {
|
|
|
|
|
if (!this.image_types_.has(type)) {
|
|
|
|
|
this.addImageType_(type);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (let [type, value] of this.image_types_) {
|
|
|
|
|
if (!image_types.has(type)) {
|
|
|
|
|
this.removeImageType_(type);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.addImageType_ = function(type) {
|
|
|
|
|
let value = {
|
|
|
|
|
section: document.createElement('imageTypeSection'),
|
|
|
|
|
instances: new Map(),
|
2016-05-11 05:21:42 +00:00
|
|
|
images: [],
|
2016-05-10 23:55:34 +00:00
|
|
|
};
|
|
|
|
|
this.insertSorted_(this.container_, value.section, type);
|
2016-05-11 00:12:14 +00:00
|
|
|
let headers = this.createNode_(value.section, 'headers');
|
|
|
|
|
this.createNode_(headers, 'header', 'Hostname');
|
2016-05-11 00:55:01 +00:00
|
|
|
this.createNode_(headers, 'header', 'Last report');
|
2016-05-11 00:12:14 +00:00
|
|
|
this.createNode_(headers, 'header', 'Uptime');
|
|
|
|
|
this.createNode_(headers, 'header', 'Current image');
|
|
|
|
|
this.createNode_(headers, 'header', 'Current volume ID');
|
|
|
|
|
this.createNode_(headers, 'header', 'Next image');
|
|
|
|
|
this.createNode_(headers, 'header', 'Next volume ID');
|
2016-05-10 23:55:34 +00:00
|
|
|
this.image_types_.set(type, value);
|
2016-05-11 05:21:42 +00:00
|
|
|
|
|
|
|
|
this.fetchManifest_(type);
|
2016-05-10 23:55:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.removeImageType_ = function(type) {
|
|
|
|
|
this.container_.removeChild(this.image_types_.get(type).section);
|
|
|
|
|
this.image_types_.delete(type);
|
|
|
|
|
};
|
|
|
|
|
|
2016-05-11 05:21:42 +00:00
|
|
|
ImageController.prototype.onNewManifest_ = function(msg) {
|
|
|
|
|
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.send();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.onFetchManifest_ = function(type, wrapper) {
|
|
|
|
|
let manifest = JSON.parse(wrapper.inner);
|
|
|
|
|
this.image_types_.get(type).images = manifest.images;
|
|
|
|
|
};
|
|
|
|
|
|
2016-05-10 23:55:34 +00:00
|
|
|
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 instance = type.instances.get(msg['hostname']);
|
2016-05-11 00:55:01 +00:00
|
|
|
instance.last_report_timestamp = Math.floor(Date.now() / 1000);
|
|
|
|
|
instance.last_report.innerText = this.formatSeconds_(0);
|
2016-05-10 23:55:34 +00:00
|
|
|
instance.uptime.innerText = this.formatSeconds_(msg['uptime_seconds']);
|
|
|
|
|
instance.timestamp.innerText = msg['timestamp'];
|
2016-05-11 00:12:14 +00:00
|
|
|
let volume_id_len = localStorage.getItem('volume_id_len') || Number.POSITIVE_INFINITY;
|
|
|
|
|
instance.volume_id.innerText = msg['volume_id'].substring(0, volume_id_len);
|
2016-05-10 23:55:34 +00:00
|
|
|
instance.next_timestamp.innerText = msg['next_timestamp'];
|
2016-05-11 00:12:14 +00:00
|
|
|
instance.next_volume_id.innerText = msg['next_volume_id'].substring(0, volume_id_len);
|
2016-05-10 23:55:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.addInstance_ = function(type, hostname) {
|
|
|
|
|
let value = {
|
|
|
|
|
section: document.createElement('instanceSection'),
|
|
|
|
|
};
|
|
|
|
|
this.insertSorted_(type.section, value.section, hostname);
|
|
|
|
|
this.createNode_(value.section, 'hostname', hostname);
|
2016-05-11 00:55:01 +00:00
|
|
|
value.last_report = this.createNode_(value.section, 'lastReport');
|
2016-05-10 23:55:34 +00:00
|
|
|
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');
|
2016-05-11 00:24:44 +00:00
|
|
|
value.reboot = this.createNode_(value.section, 'reboot', 'Reboot');
|
|
|
|
|
|
2016-05-10 23:55:34 +00:00
|
|
|
value.volume_id.addEventListener(
|
|
|
|
|
'click', (e) => this.onVolumeIDClick_(e.target.innerText));
|
|
|
|
|
value.next_volume_id.addEventListener(
|
|
|
|
|
'click', (e) => this.onVolumeIDClick_(e.target.innerText));
|
2016-05-11 00:24:44 +00:00
|
|
|
value.reboot.addEventListener(
|
|
|
|
|
'click', (e) => this.sendReboot_(hostname));
|
|
|
|
|
|
2016-05-10 23:55:34 +00:00
|
|
|
type.instances.set(hostname, value);
|
|
|
|
|
};
|
|
|
|
|
|
2016-05-11 00:55:01 +00:00
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2016-05-10 23:55:34 +00:00
|
|
|
ImageController.prototype.onTick_ = function() {
|
|
|
|
|
let now = Math.floor(Date.now() / 1000);
|
2016-05-11 00:55:01 +00:00
|
|
|
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);
|
2016-05-10 23:55:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.onVolumeIDClick_ = function(volume_id) {
|
|
|
|
|
let base_url = localStorage.getItem('volume_id_url');
|
|
|
|
|
if (!base_url) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
open(base_url.replace('VOLUMEID', volume_id));
|
|
|
|
|
};
|
|
|
|
|
|
2016-05-11 00:24:44 +00:00
|
|
|
ImageController.prototype.sendReboot_ = function(hostname) {
|
|
|
|
|
this.ws_.send(JSON.stringify({
|
|
|
|
|
'type': 'command',
|
|
|
|
|
'target': hostname,
|
|
|
|
|
'data': {
|
|
|
|
|
'command': 'reboot',
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
2016-05-10 23:55:34 +00:00
|
|
|
ImageController.prototype.insertSorted_ = function(parent, new_child, key) {
|
|
|
|
|
let insert_before = null;
|
|
|
|
|
for (var i = 0; i < parent.childNodes.length; i++) {
|
|
|
|
|
let child = parent.childNodes[i];
|
|
|
|
|
if (child.getAttribute('data-key') > key) {
|
|
|
|
|
insert_before = child;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_child.setAttribute('data-key', key);
|
|
|
|
|
parent.insertBefore(new_child, insert_before);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.createNode_ = function(parent, tag_name, text_content) {
|
|
|
|
|
let node = document.createElement(tag_name);
|
|
|
|
|
node.innerText = text_content || null;
|
|
|
|
|
parent.appendChild(node);
|
|
|
|
|
return node;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageController.TIERS_ = [
|
|
|
|
|
[ 60 * 60 * 24 * 7, 'w' ],
|
|
|
|
|
[ 60 * 60 * 24, 'd' ],
|
|
|
|
|
[ 60 * 60, 'h' ],
|
|
|
|
|
[ 60, 'm' ],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
ImageController.prototype.formatSeconds_ = function(seconds) {
|
|
|
|
|
for (let [threshold, suffix] of ImageController.TIERS_) {
|
|
|
|
|
if (seconds > threshold) {
|
|
|
|
|
return Math.floor(seconds / threshold) + suffix;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return seconds + 's';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2016-05-05 00:33:30 +00:00
|
|
|
document.addEventListener('DOMContentLoaded', (e) => {
|
2016-05-10 23:55:34 +00:00
|
|
|
new ImageController(document.getElementsByTagName('imageContainer')[0]);
|
2016-05-05 00:33:30 +00:00
|
|
|
});
|