Initial checkin.

This commit is contained in:
Ian Gulliver
2016-01-24 22:16:49 -08:00
parent 5fd0d0768e
commit 65c37d7a9a
8 changed files with 1322 additions and 0 deletions

41
console/console.css Normal file
View File

@@ -0,0 +1,41 @@
@import url(https://fonts.googleapis.com/css?family=Roboto+Mono);
body {
margin: 0;
}
.expr {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1;
}
.expr form {
margin: 0;
}
.expr input {
width: 100%;
border: 0;
padding: 5px;
font-family: 'Roboto Mono';
font-size: 15px;
border-bottom: 1px solid #757575;
background: rgba(255,255,255,0.7);
}
.expr input:focus {
outline: none;
border-bottom: 1px solid #5264AE;
}
.chart {
width: 900px;
height: 500px;
display: inline-block;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}

12
console/console.html Normal file
View File

@@ -0,0 +1,12 @@
<html>
<head>
<title>YATSDB Console</title>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script src="/console/lib.js"></script>
<script src="/console/console.js"></script>
<link rel="stylesheet" type="text/css" href="/console/console.css">
</head>
<body>
<div id="container"></div>
</body>
</html>

175
console/console.js Normal file
View File

@@ -0,0 +1,175 @@
var Console = function(container) {
this.container_ = container;
this.client_ = new TSDBClient();
this.loadJSAPI_();
};
Console.prototype.loadJSAPI_ = function() {
google.load('visualization', '1.1', {
packages: ['corechart'],
callback: this.jsapiLoaded_.bind(this),
});
};
Console.prototype.jsapiLoaded_ = function() {
var exprContainer = document.createElement('div');
exprContainer.className = 'expr';
this.container_.appendChild(exprContainer);
var form = document.createElement('form');
exprContainer.appendChild(form);
this.exprInput_ = document.createElement('input');
form.appendChild(this.exprInput_);
form.addEventListener('submit', this.onExprChange_.bind(this));
var chartContainer = document.createElement('div');
this.container_.appendChild(chartContainer);
this.charts_ = [
{
'title': 'Last 20 minutes',
'seconds': 120 * 10,
'resolution': 'full',
},
{
'title': 'Last 2 hours',
'seconds': 120 * 60,
'resolution': 'minute',
},
{
'title': 'Last 5 days',
'seconds': 120 * 60 * 60,
'resolution': 'hour',
},
{
'title': 'Last 120 days',
'seconds': 120 * 60 * 60 * 24,
'resolution': 'day',
},
];
for (var i = 0; i < this.charts_.length; i++) {
var obj = this.charts_[i];
obj.container = document.createElement('div');
obj.container.className = 'chart';
chartContainer.appendChild(obj.container);
}
this.expr_ = decodeURIComponent(window.location.hash.substring(1));
// Ensure that the URL is properly encoded for copy & paste.
window.location.hash = encodeURIComponent(this.expr_);
this.exprInput_.value = this.expr_;
window.addEventListener('hashchange', this.onHashChange_.bind(this));
this.loadCharts_();
};
Console.prototype.onExprChange_ = function(e) {
window.location.hash = encodeURIComponent(this.exprInput_.value);
e.preventDefault();
};
Console.prototype.onHashChange_ = function(e) {
this.expr_ = decodeURIComponent(window.location.hash.substring(1));
this.exprInput_.value = this.expr_;
this.loadCharts_();
};
Console.prototype.loadCharts_ = function() {
if (!this.expr_) {
return;
}
for (var i = 0; i < this.charts_.length; i++) {
var options = this.charts_[i];
if (options.instance) {
options.instance.destroy();
}
options.instance = new Chart(this.client_, this.expr_, options);
}
};
var Chart = function(client, expr, options) {
this.options_ = options;
this.watch_ = client.watch(
expr, this.options_.seconds, this.options_.resolution,
this.drawChart_.bind(this));
};
Chart.prototype.drawChart_ = function(data) {
var dataTable = new google.visualization.DataTable();
dataTable.addColumn('date', 'Timestamp');
for (var i = data.length - 1; i >= 0; i--) {
var ts = data[i];
if (!ts.timestamps_values.length) {
data.splice(i, 1);
continue;
}
var tags = [];
for (var key in ts.tags) {
tags.push(key + '=' + ts.tags[key]);
}
dataTable.addColumn('number', tags.join(','));
}
for (var i = 0; i < data.length; i++) {
var ts = data[i];
for (var j = 0; j < ts.timestamps_values.length; j++) {
var row = [new Date(ts.timestamps_values[j][0] * 1000)];
for (var k = 0; k < i; k++) {
row.push(null);
}
row.push(ts.timestamps_values[j][1]);
for (var k = i + 1; k < data.length; k++) {
row.push(null);
}
dataTable.addRow(row);
}
};
var options = {
title: this.options_.title,
legend: {
position: 'bottom'
},
hAxis: {
gridlines: {
count: -1,
},
},
explorer: {
actions: [
'dragToZoom',
'rightClickToReset',
],
},
};
// It'd be great to use the material design charts here, but they fail to load
// ~25% of the time, which is a non-starter.
if (this.chartObj_) {
this.chartObj_.clearChart();
}
this.chartObj_ = new google.visualization.LineChart(this.options_.container);
this.chartObj_.draw(dataTable, options);
};
Chart.prototype.destroy = function() {
this.watch_.destroy();
if (this.chartObj_) {
this.chartObj_.clearChart();
}
};
document.addEventListener('DOMContentLoaded', function(e) {
new Console(document.getElementById('container'));
});

78
console/lib.js Normal file
View File

@@ -0,0 +1,78 @@
var TSDBClient = function(opt_baseURL) {
this.baseURL_ = opt_baseURL || '/api';
};
TSDBClient.prototype.watch = function(
expr, windowSeconds, resolution, callback) {
return new TSDBWatch(
this.baseURL_, expr, windowSeconds, resolution, callback);
};
var TSDBWatch = function(baseURL, expr, windowSeconds, resolution, callback) {
this.url_ = baseURL + '/get?start=-' + windowSeconds.toString() +
'&resolution=' + encodeURIComponent(resolution) +
'&expr=' + encodeURIComponent(expr);
this.callback_ = callback;
this.refresh_ = windowSeconds / 40;
this.delay_ = TSDBWatch.MIN_DELAY;
this.sendRequest_();
};
TSDBWatch.MIN_DELAY = 0.5;
TSDBWatch.MAX_DELAY = 32.0;
TSDBWatch.DELAY_MULT = 2;
TSDBWatch.prototype.destroy = function() {
if (this.timer_) {
window.clearTimeout(this.timer_);
}
};
TSDBWatch.prototype.sendRequest_ = function() {
this.timer_ = null;
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.timeout = 30 * 1000;
xhr.open('GET', this.url_);
xhr.addEventListener('load', this.onLoad_.bind(this));
xhr.addEventListener('error', this.retry_.bind(this));
xhr.addEventListener('timeout', this.retry_.bind(this));
xhr.send();
};
TSDBWatch.prototype.retry_ = function(e) {
if (this.timer_) {
console.log('Duplicate retry call');
return;
}
this.timer_ = window.setTimeout(
this.sendRequest_.bind(this),
this.delay_ * 1000);
this.delay_ = Math.min(this.delay_ * TSDBWatch.DELAY_MULT,
TSDBWatch.MAX_DELAY);
};
TSDBWatch.prototype.onLoad_ = function(e) {
var data = e.target.response;
if (!data) {
this.retry_();
return;
}
this.delay_ = TSDBWatch.MIN_DELAY;
this.callback_(data);
this.timer_ = window.setTimeout(
this.sendRequest_.bind(this),
this.refresh_ * 1000);
}