Initial checkin.
This commit is contained in:
41
console/console.css
Normal file
41
console/console.css
Normal 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
12
console/console.html
Normal 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
175
console/console.js
Normal 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
78
console/lib.js
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user