Merge pull request #109 from sschneid/dashboard2

An updated dashboard
This commit is contained in:
Colin 2016-01-13 15:15:56 -08:00
commit ad4e760f56
11 changed files with 881 additions and 907 deletions

5
lib/vmpooler/public/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
lib/vmpooler/public/lib/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,734 @@
var dashboard_data = {};
var dashboard_svg = {};
var date_from = new Date();
var running_data = {};
var weekly_data = {
clone_max: 0,
clone_platform_max: 0
};
var capacity_data = {};
var colorscale = d3.scale.category20();
var stack = d3.layout.stack().values(function(d) { return d.values; });
Date.prototype.yyyymmdd = function() {
var yyyy = this.getFullYear().toString();
var mm = (this.getMonth()+1).toString();
var dd = this.getDate().toString();
return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]);
};
var data_url = {
'capacity': '/dashboard/stats/vmpooler/pool',
'pools' : '/api/v1/vm',
'running' : '/dashboard/stats/vmpooler/running',
'status' : '/api/v1/status',
'summary' : '/api/v1/summary'
};
//--------------------------------------------------------------------
// Everything below this line will be updated in-browser via tick();
//--------------------------------------------------------------------
(function tick() { setTimeout(function() { // <self-update>
// Update "today's" date
date_from.setDate(date_from.getDate() - 6);
// Gather up data from multiple endpoints
$.each([
'capacity',
'pools',
'running',
'status',
'summary'
], function(index, value) {
dashboard_data[value] = (function() {
var dashboard_data__live = null;
var url = data_url[value];
// Get history if this is the first tick() iteration
switch (value) {
case 'capacity': if (! dashboard_data[value]) { url += ('?history=1'); }; break;
case 'running' : if (! dashboard_data[value]) { url += ('?history=1'); }; break;
case 'summary' : if (! dashboard_data[value]) { url += ('?from=' + date_from.yyyymmdd()); }; break;
}
$.ajax({
'url': url,
'async': false,
'global': false,
'dataType': 'json',
'success': function(data) {
dashboard_data__live = data;
}
});
return dashboard_data__live;
})();
});
// Create an array of pool_maj
dashboard_data['tmp'] = {};
dashboard_data['pools_maj'] = [];
dashboard_data['pools'].sort().map(function(pool) {
var pool_maj = pool.split('-', 2)[0];
if (! dashboard_data['tmp'][pool_maj]) {
dashboard_data['pools_maj'].push(pool_maj);
dashboard_data['tmp'][pool_maj] = 1;
}
});
delete dashboard_data['tmp'];
// Create a color swatch for each pool_maj
dashboard_data['color'] = {};
dashboard_data['tmp'] = 0;
dashboard_data['pools_maj'].sort().map(function(pool_maj) {
dashboard_data['color'][pool_maj] = colorscale(dashboard_data['tmp']);
dashboard_data['tmp']++;
});
delete dashboard_data['tmp'];
// #dashboard-numbers
// Numerical metrics (# cloning, running, ready, waiting, etc.)
$('#dashboard-numbers').empty();
var numbers_width = parseInt(d3.select('.col-md-2').style('width')) / 2;
var numbers_height = 75;
var numbers_data = {
label: {
'clone_total': 'cloned today',
'clone_average': 'clone time avg',
'capacity': 'capacity pct',
'total': 'total # of VMs',
'ready': 'ready & waiting',
'cloning': 'being cloned',
'booting': 'booting up',
'running': 'running tests',
'completed': 'waiting to die'
},
key: {
'clone_total': dashboard_data['status']['clone']['count']['total'],
'clone_average': dashboard_data['status']['clone']['duration']['average'] + 's',
'capacity': dashboard_data['status']['capacity']['percent'],
'total': dashboard_data['status']['queue']['total'],
'ready': dashboard_data['status']['queue']['ready'],
'cloning': dashboard_data['status']['queue']['cloning'],
'booting': dashboard_data['status']['queue']['booting'],
'running': dashboard_data['status']['queue']['running'],
'completed': dashboard_data['status']['queue']['completed']
}
};
$.each([
'clone_total',
'clone_average',
'capacity',
'total',
'ready',
'cloning',
'booting',
'running',
'completed'
], function(index, value) {
dashboard_svg[value] = d3.select('#dashboard-numbers')
.append('svg')
.style('float', 'right')
.attr('class', 'col-md-1')
.attr('height', numbers_height);
dashboard_svg[value]
.append('text')
.text(
(numbers_data['label'][value])
)
.attr({
'text-anchor': 'end',
'x': numbers_width - 5,
'y': '50',
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#666'
});
dashboard_svg[value]
.append('text')
.text(
(numbers_data['key'][value])
)
.attr({
'text-anchor': 'end',
'x': numbers_width - 5,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '40px',
'letter-spacing': '-0.025em',
'fill': '#444'
});
});
numbers_data = null;
// #dashboard-running
// By-platform graph of what's been running for the past hour; includes pool_maj legend
$('#dashboard-running').empty();
var running_width = parseInt(d3.select('.col-md-10').style('width'));
var running_height = 160;
if (! running_data['stack']) {
running_data['stack'] = [];
}
// Process 'running' history
dashboard_data['pools_maj'].sort().map(function(pool_maj) {
if (dashboard_data['running'][pool_maj]['history']) {
for (var c = 0; c < dashboard_data['running'][pool_maj]['history'].length; c++) {
if (! running_data['stack'][c]) {
running_data['stack'][c] = {};
}
running_data['stack'][c][pool_maj] = dashboard_data['running'][pool_maj]['history'][c];
}
}
});
if (! running_data['graph']) {
running_data['tmp'] = [];
for (var metric in running_data['stack']) {
for (var c = 0; c < 8; c++) {
running_data['tmp'].push(running_data['stack'][metric]);
}
}
running_data['stack'] = running_data['tmp'];
delete running_data['tmp'];
}
// Process 'running' newest values and add them to the stack
dashboard_data['tmp'] = {};
for (var key in dashboard_data['running']) {
dashboard_data['tmp'][key] = dashboard_data['running'][key]['running'];
}
running_data['stack'].push(dashboard_data['tmp']);
delete dashboard_data['tmp'];
// Calculate 'running' graph stack
running_data['graph'] = stack(
dashboard_data['pools_maj'].sort().map(function(pool_maj) {
return {
name: pool_maj,
values: running_data['stack'].map(function(d) {
return { y: d[pool_maj] };
})
}
})
);
// Calculate 'running' graph shapes
running_data['total'] = d3.max(
running_data['graph'], function(layer) {
return d3.max(layer.values, function(d) {
return d.y0 + d.y;
});
}
);
var running_x = d3.scale.linear().domain([0, 500]).range([5, running_width - 20]);
var running_y = d3.scale.linear().domain([0, running_data['total']]).range([running_height, 0]);
var running_area = d3.svg.area()
.x(function(d, i) { return running_x(i); })
.y0(function(d) { return running_y(d.y0); })
.y1(function(d) { return running_y(d.y0 + d.y); });
// The 'running' SVG
var running_graph = d3.select('#dashboard-running')
.append('svg')
.attr('height', running_height)
.attr('class', 'col-md-10')
.append('g');
dashboard_svg['running'] = running_graph.selectAll('#dashboard-running')
.data(running_data['graph'])
.enter()
.append('g');
// A texture
defs = dashboard_svg['running'].append('svg:defs');
defs.append('svg:pattern')
.attr('id', 'background')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', '500px')
.attr('height', '500px')
.append('svg:image')
.attr('xlink:href', '/img/textured_paper.png')
.attr('x', 0)
.attr('y', 0)
.attr('width', '500px')
.attr('height', '500px');
dashboard_svg['running']
.append('path')
.attr('class', 'area')
.attr('d', function(d) { return running_area(d.values); })
.attr('opacity', '0.75')
.style('fill', 'url(#background)');
dashboard_svg['running']
.append('path')
.attr('class', 'area')
.attr('d', function(d) { return running_area(d.values); })
.attr('opacity', '0.5')
.style('fill', function(d) { return dashboard_data['color'][d.name]; });
// Legend
dashboard_data['pools_maj'].sort().map(function(pool_maj) {
dashboard_svg['legend' + pool_maj] = d3.select('#dashboard-running')
.append('svg')
.attr('class', 'col-md-1')
.attr('height', '20px');
dashboard_svg['legend' + pool_maj]
.append('rect')
.attr({
'x': '5',
'y': '5',
'width': '10',
'height': '10',
'opacity': '0.75',
'fill': 'url(#background)'
});
dashboard_svg['legend' + pool_maj]
.append('rect')
.attr({
'x': '5',
'y': '5',
'width': '10',
'height': '10',
'opacity': '0.5',
'fill': function(d) { return dashboard_data['color'][pool_maj]; }
});
dashboard_svg['legend' + pool_maj]
.append('text')
.text(
(pool_maj)
)
.attr({
'x': '20',
'y': '15',
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#666'
});
}
);
if (running_data['stack'].length > 500) {
running_data['stack'].shift();
}
// #dashboard-weekly
// Weekly graphs (daily clone count, clone/boot avgs, etc.)
$('#dashboard-weekly').empty();
var weekly_width = (parseInt(d3.select('.col-md-2').style('width')) * 2) - 10;
var weekly_height = 100;
// Update based on if it's a new day, first tick() iteration, or neither
if (dashboard_data['summary']['daily'].length == 1) {
weekly_data['clone_count'].pop();
weekly_data['clone_avg'].pop();
weekly_data['boot_avg'].pop();
}
else if (dashboard_data['summary']['daily'].length == 7) {
weekly_data['clone_count'] = [];
weekly_data['clone_avg'] = [];
weekly_data['boot_avg'] = [];
weekly_data['platform_count'] = {};
}
dashboard_data['summary']['daily'].sort().map(function(day) {
weekly_data['clone_count'].push(day['clone']['count']['total']);
weekly_data['clone_avg'].push(day['clone']['duration']['average']);
weekly_data['boot_avg'].push(day['boot']['duration']['average']);
if (day['clone']['count']['total'] > weekly_data['clone_max']) {
weekly_data['clone_max'] = day['clone']['count']['total'];
}
});
// Consolidate clone totals into pool_maj groups
dashboard_data['pools'].sort().map(function(pool) {
var pool_maj = pool.split('-', 2)[0];
if (! weekly_data['platform_count'][pool_maj]) {
weekly_data['platform_count'][pool_maj] = 0;
}
if (dashboard_data['summary']['clone']['count']['pool'][pool]) {
weekly_data['platform_count'][pool_maj] += dashboard_data['summary']['clone']['count']['pool'][pool]['total'];
}
});
dashboard_data['pools_maj'].sort().map(function(pool_maj) {
if (weekly_data['platform_count'][pool_maj] > weekly_data['clone_platform_max']) {
weekly_data['clone_platform_max'] = weekly_data['platform_count'][pool_maj];
}
});
var weekly_x = d3.scale.linear().domain([0, 6]).range([0, weekly_width]);
var weekly_y_clone_count = d3.scale.linear().domain([0, weekly_data['clone_max']]).range([weekly_height, 0]);
var weekly_y_boot_avg = d3.scale.linear().domain([0, Math.max.apply(Math, weekly_data['boot_avg'])]).range([weekly_height, 0]);
var weekly_y_platform_count = d3.scale.linear().domain([0, weekly_data['clone_platform_max']]).range([weekly_height, 0]);
var area_clone_count = d3.svg.area()
.interpolate('linear')
.x(function(d, i) { return weekly_x(i); })
.y0(weekly_height)
.y1(function(d) { return weekly_y_clone_count(d); });
var area_boot_avg = d3.svg.area()
.interpolate('linear')
.x(function(d, i) { return weekly_x(i); })
.y0(weekly_height)
.y1(function(d) { return weekly_y_boot_avg(d); });
// Create some SVGs
for (graph in graphs = ['clone_count', 'clone_boot_avg']) {
dashboard_svg[graphs[graph]] = d3.select('#dashboard-weekly')
.append('svg')
.attr('class', 'col-md-4')
.attr('height', weekly_height);
dashboard_svg[graphs[graph]]
.append('g')
.attr('class', 'x tick')
.attr('transform', 'translate(0,' + (weekly_height) + ')')
.call(
d3.svg.axis()
.scale(weekly_x)
.ticks(7)
.tickSize(-weekly_height)
.outerTickSize(0)
.tickFormat('')
.tickSubdivide(true)
.orient('bottom')
);
}
dashboard_svg['platform_count'] = d3.select('#dashboard-weekly')
.append('svg')
.attr('class', 'col-md-4')
.attr('height', weekly_height);
dashboard_svg['platform_count']
.append('g')
.attr('class', 'x tick')
.attr('transform', 'translate(0,' + (weekly_height) + ')')
.call(
d3.svg.axis()
.scale(weekly_x)
.ticks(0)
.tickSize(-weekly_height)
.orient('bottom')
);
// Area shapes for clone_count and clone/boot time avgs
var area_clone_count = d3.svg.area()
.interpolate('linear')
.x(function(d, i) { return weekly_x(i); })
.y0(weekly_height)
.y1(function(d) { return weekly_y_clone_count(d); });
var area_boot_avg = d3.svg.area()
.interpolate('linear')
.x(function(d, i) { return weekly_x(i); })
.y0(weekly_height)
.y1(function(d) { return weekly_y_boot_avg(d); });
dashboard_svg['clone_count']
.append('path')
.attr({
'class': 'area',
'fill': 'url(#background)',
'opacity': '0.75',
'd': area_clone_count(weekly_data['clone_count'])
});
dashboard_svg['clone_count']
.append('path')
.attr({
'class': 'area',
'fill': 'seagreen',
'opacity': '0.5',
'd': area_clone_count(weekly_data['clone_count'])
});
dashboard_svg['clone_boot_avg']
.append('path')
.attr({
'class': 'area',
'fill': 'url(#background)',
'opacity': '0.75',
'd': area_boot_avg(weekly_data['boot_avg'])
});
dashboard_svg['clone_boot_avg']
.append('path')
.attr({
'class': 'area',
'fill': 'crimson',
'opacity': '0.5',
'd': area_boot_avg(weekly_data['boot_avg'])
});
dashboard_svg['clone_boot_avg']
.append('path')
.attr({
'class': 'area',
'fill': 'gold',
'opacity': '0.75',
'd': area_boot_avg(weekly_data['clone_avg'])
});
// Add a bar to the platform_count raph for each pool_maj
dashboard_data['tmp'] = 0;
dashboard_data['pools_maj'].sort().map(function(pool_maj) {
var x = dashboard_data['tmp'] * (weekly_width / dashboard_data['pools_maj'].length);
var y = weekly_y_platform_count(weekly_data['platform_count'][pool_maj]) - 1;
var width = weekly_width / dashboard_data['pools_maj'].length;
if (y == -1) { y = 0; }
dashboard_svg['platform_count']
.append('rect')
.attr({
'x': x,
'y': y,
'width': width,
'height': weekly_height,
'fill': 'url(#background)',
'opacity': '0.75'
});
dashboard_svg['platform_count']
.append('rect')
.attr({
'x': x,
'y': y,
'width': width,
'height': weekly_height,
'fill': function(d) { return dashboard_data['color'][pool_maj]; },
'opacity': '0.5'
});
dashboard_data['tmp']++;
});
delete dashboard_data['tmp'];
// #dashboard-pool
// Many little graphs showing individual pool capacities
$('#dashboard-pool').empty();
var capacity_col_class = 'col-md-2';
var capacity_width = parseInt(d3.select('.col-md-2').style('width'));
var capacity_height = 47;
if (capacity_width > 250) {
capacity_col_class = 'col-md-1';
capacity_width = parseInt(d3.select('.col-md-1').style('width'));
}
dashboard_data['pools'].sort().map(function(pool) {
var capacity_x = d3.scale.linear().domain([0, 500]).range([5, capacity_width - 5]);
var capacity_y = d3.scale.linear().domain([dashboard_data['capacity'][pool]['size'], 0]).range([0, capacity_height - 15]);
var capacity_area = d3.svg.area()
.interpolate('basis')
.x(function(d, i) { return capacity_x(i); })
.y0(capacity_height - 15)
.y1(function(d) { return capacity_y(d); });
var capacity_path = d3.svg.line()
.interpolate('basis')
.x(function(d, i) { return capacity_x(i); })
.y(function(d) { return capacity_y(d); });
if (! capacity_data[pool]) {
capacity_data[pool] = {};
}
if (! capacity_data[pool]['r']) {
capacity_data[pool]['r'] = [];
}
// Process 'capacity' history
if (dashboard_data['capacity'][pool]['history']) {
capacity_data[pool]['r'] = dashboard_data['capacity'][pool]['history'];
}
capacity_data[pool]['r'].push(dashboard_data['capacity'][pool]['ready']);
var capacity_current = capacity_data[pool]['r'].slice(-1)[0];
var capacity_size = dashboard_data['capacity'][pool]['size'];
var capacity_pct = Math.floor((capacity_current / capacity_size) * 100);
var statuscolor = '#78a830';
if (capacity_pct < 50) { statuscolor = '#f0a800'; }
if (capacity_pct < 25) { statuscolor = '#d84830'; }
// Define 'capacity' SVG
dashboard_svg['capacity' + pool] = d3.select('#dashboard-pool')
.append('svg')
.attr('class', capacity_col_class)
.attr('height', capacity_height);
dashboard_svg['capacity' + pool]
.append('g')
.attr('class', 'x tick')
.attr('transform', 'translate(0,' + (capacity_height - 15) + ')')
.call(
d3.svg.axis()
.scale(capacity_x)
.ticks(4)
.tickSize(-capacity_height)
.outerTickSize(0)
.tickFormat('')
.tickSubdivide(true)
.orient('bottom')
);
// Draw 'capacity' path
dashboard_svg['capacity' + pool]
.append('path')
.attr('class', 'area')
.attr('fill', 'url(#background)')
.attr('opacity', '0.75')
.attr('d', capacity_area(capacity_data[pool]['r']));
dashboard_svg['capacity' + pool]
.append('path')
.attr('class', 'area')
.attr('fill', statuscolor)
.attr('opacity', '0.5')
.attr('d', capacity_area(capacity_data[pool]['r']));
dashboard_svg['capacity' + pool]
.append('path')
.attr('class', 'line')
.attr('stroke', statuscolor)
.attr('stroke-width', '1')
.attr('d', capacity_path(capacity_data[pool]['r']));
// Add labels to 'capacity' graphs
dashboard_svg['capacity' + pool]
.append('text')
.text(
(pool)
)
.attr({
'x': '10',
'y': capacity_height - 33,
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '12px',
'fill': '#444'
});
dashboard_svg['capacity' + pool]
.append('text')
.text(
(capacity_pct + '%')
)
.attr({
'x': '10',
'y': capacity_height - 20,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'letter-spacing': '-0.05em',
'fill': '#444'
});
dashboard_svg['capacity' + pool]
.append('text')
.text(
('(') +
(capacity_current) +
('/') +
(capacity_size) +
(')')
)
.attr({
'x': 45,
'y': capacity_height - 20,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'letter-spacing': '-0.05em',
'fill': '#444'
});
if (capacity_data[pool]['r'].length > 500) {
capacity_data[pool]['r'].shift();
}
});
// Hide the 'loading' screen
$('#loading').hide();
// Refresh!
tick(); }, 5000); })(); // </self-update>

4
lib/vmpooler/public/lib/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,400 +0,0 @@
var numbers_url = '/status';
var numbers_width = 110;
var numbers_height = 50;
var stats_vmpooler_numbers__data = {};
var stats_vmpooler_numbers__svg = {};
d3.json( numbers_url,
function( stats_vmpooler_numbers__data ) {
( function tick() {
setTimeout( function() {
var stats_vmpooler_numbers__data__live = ( function() {
var stats_vmpooler_numbers__data__live = null;
$.ajax( {
'url': numbers_url,
'async': false,
'global': false,
'dataType': 'json',
'success': function( data ) {
stats_vmpooler_numbers__data__live = data;
}
} );
return stats_vmpooler_numbers__data__live;
} )();
$( '#stats-vmpooler-numbers' ).empty();
stats_vmpooler_numbers__svg[ 'clone_total' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'class', 'extra' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'clone_total' ]
.append( 'text' )
.text(
( 'cloned today' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'clone_total' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'clone' ][ 'count' ][ 'total' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'clone_average' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'class', 'extra' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'clone_average' ]
.append( 'text' )
.text(
( 'clone time average' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'clone_average' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'clone' ][ 'duration' ][ 'average' ] + 's' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'capacity' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'class', 'extra' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'capacity' ]
.append( 'text' )
.text(
( 'capacity percent' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'capacity' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'capacity' ][ 'percent' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'total' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'total' ]
.append( 'text' )
.text(
( 'total # of VMs' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'total' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'queue' ][ 'total' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'ready' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'ready' ]
.append( 'text' )
.text(
( 'ready and waiting' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'ready' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'queue' ][ 'ready' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'cloning' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'cloning' ]
.append( 'text' )
.text(
( 'being cloned' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'cloning' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'queue' ][ 'cloning' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'booting' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'booting' ]
.append( 'text' )
.text(
( 'booting up' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'booting' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'queue' ][ 'booting' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'running' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'running' ]
.append( 'text' )
.text(
( 'running tests' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'running' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'queue' ][ 'running' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
stats_vmpooler_numbers__svg[ 'completed' ] = d3.select( '#stats-vmpooler-numbers' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 20px 10px' )
.style( 'float', 'right' )
.style( 'text-align', 'right' )
.attr( 'width', numbers_width + 'px' )
.attr( 'height', numbers_height + 'px' );
stats_vmpooler_numbers__svg[ 'completed' ]
.append( 'text' )
.text(
( 'waiting to die' )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': numbers_height,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
stats_vmpooler_numbers__svg[ 'completed' ]
.append( 'text' )
.text(
( stats_vmpooler_numbers__data__live[ 'queue' ][ 'completed' ] )
)
.attr( {
'text-anchor': 'end',
'x': numbers_width,
'y': '36',
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '50px',
'letter-spacing': '-0.05em',
'fill': '#444'
} );
tick();
}, 5000 );
} )();
}
);

View file

@ -1,187 +0,0 @@
var pool_url = '/dashboard/stats/vmpooler/pool';
var pool_width = 130;
var pool_height = 55;
var stats_vmpooler_pool__data = {};
var stats_vmpooler_pool__svg = {};
d3.json( pool_url+'?history=1',
function( stats_vmpooler_pool__data ) {
var stats_vmpooler_pool__data__keys = [];
for ( var key in stats_vmpooler_pool__data ) {
stats_vmpooler_pool__data__keys.push( key );
}
stats_vmpooler_pool__data__keys.sort().map(
function( pool ) {
stats_vmpooler_pool__data[ pool ][ 'r' ] = stats_vmpooler_pool__data[ pool ][ 'history' ];
}
);
( function tick() {
setTimeout( function() {
var stats_vmpooler_pool__data__live = ( function() {
var stats_vmpooler_pool__data__live = null;
$.ajax( {
'url': pool_url,
'async': false,
'global': false,
'dataType': 'json',
'success': function( data ) {
stats_vmpooler_pool__data__live = data;
}
} );
return stats_vmpooler_pool__data__live;
} )();
$( '#stats-vmpooler-pool' ).empty();
stats_vmpooler_pool__data__keys.sort().map(
function( pool ) {
var x = d3.scale.linear().domain( [ 0, 500 ] ).range( [ 0, pool_width ] );
var y = d3.scale.linear().domain( [ parseInt( stats_vmpooler_pool__data__live[ pool ][ 'size' ] ), 0 ] ).range( [ 0, pool_height - 15 ] );
var area = d3.svg.area()
.interpolate( 'basis' )
.x( function( d, i ) { return x( i ); } )
.y0( pool_height - 15 )
.y1( function( d ) { return y( d ); } );
var path = d3.svg.line()
.interpolate( 'basis' )
.x( function( d, i ) { return x( i ); } )
.y( function( d ) { return y( d ); } );
stats_vmpooler_pool__data[ pool ][ 'r' ].push( parseInt( stats_vmpooler_pool__data__live[ pool ][ 'ready' ] ) );
var pool_current = stats_vmpooler_pool__data[ pool ][ 'r' ].slice( -1 )[ 0 ];
var pool_size = stats_vmpooler_pool__data[ pool ][ 'size' ]
var pool_pct = Math.floor( ( pool_current / pool_size ) * 100 );
var statuscolor = '#78a830';
if ( pool_pct < 50 ) { statuscolor = '#f0a800'; }
if ( pool_pct < 25 ) { statuscolor = '#d84830'; }
stats_vmpooler_pool__svg[ pool ] = d3.select( '#stats-vmpooler-pool' )
.append( 'svg' )
.style( 'margin', '15px 0px 0px 0px' )
.style( 'padding', '0px 10px 0px 10px' )
.attr( 'width', pool_width )
.attr( 'height', pool_height );
defs = stats_vmpooler_pool__svg[ pool ].append( 'svg:defs' );
defs.append( 'svg:pattern' )
.attr( 'id', 'background' )
.attr( 'patternUnits', 'userSpaceOnUse' )
.attr( 'width', '500px' )
.attr( 'height', '500px' )
.append( 'svg:image' )
.attr( 'xlink:href', '/img/textured_paper.png' )
.attr( 'x', 0 )
.attr( 'y', 0 )
.attr( 'width', '500px' )
.attr( 'height', '500px' );
stats_vmpooler_pool__svg[ pool ]
.append( 'path' )
.attr( 'class', 'area' )
.attr( 'fill', 'url( #background )' )
.attr( 'opacity', '0.50' )
.attr( 'd', area( stats_vmpooler_pool__data[ pool ][ 'r' ] ) );
stats_vmpooler_pool__svg[ pool ]
.append( 'g' )
.attr( 'class', 'x tick' )
.attr( 'transform', 'translate( 0,' + ( pool_height - 15 ) + ')' )
.call(
d3.svg.axis()
.scale( x )
.ticks( 4 )
.tickSize( -pool_height )
.outerTickSize( 0 )
.tickFormat( '' )
.tickSubdivide( true )
.orient( 'bottom' )
);
stats_vmpooler_pool__svg[ pool ]
.append( 'text' )
.text(
( pool )
)
.attr( {
'x': '5',
'y': pool_height - 2,
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '12px',
'fill': '#888'
} );
stats_vmpooler_pool__svg[ pool ]
.append( 'text' )
.text(
( pool_pct + '%' )
)
.attr( {
'x': '5',
'y': pool_height - 20,
'font-face': '\'PT Sans\', sans-serif',
'font-weight': 'bold',
'font-size': '12px',
'letter-spacing': '-0.05em',
'fill': '#888'
} );
stats_vmpooler_pool__svg[ pool ]
.append( 'text' )
.text(
( '( ' ) +
( pool_current ) +
( '/' ) +
( pool_size ) +
( ' )' )
)
.attr( {
'x': 40,
'y': pool_height - 20,
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'letter-spacing': '-0.05em',
'fill': '#888'
} );
stats_vmpooler_pool__svg[ pool ]
.append( 'path' )
.attr( 'class', 'area' )
.attr( 'fill', statuscolor )
.attr( 'opacity', '0.35' )
.attr( 'd', area( stats_vmpooler_pool__data[ pool ][ 'r' ] ) );
stats_vmpooler_pool__svg[ pool ]
.append( 'path' )
.attr( 'class', 'line' )
.attr( 'stroke', statuscolor )
.attr( 'stroke-width', '1' )
.attr( 'd', path( stats_vmpooler_pool__data[ pool ][ 'r' ] ) );
if ( stats_vmpooler_pool__data[ pool ][ 'r' ].length > 500 ) {
stats_vmpooler_pool__data[ pool ][ 'r' ].shift();
}
}
)
tick();
}, 5000 );
} )();
}
);

View file

@ -1,210 +0,0 @@
var running_url = '/dashboard/stats/vmpooler/running';
var running_height = 160;
var colorscale = d3.scale.category20();
var color = {};
var stats_vmpooler_running__data = {};
var stats_vmpooler_running__svg = {};
var stats_vmpooler_running__data__total = 0;
d3.json( running_url+'?history=1',
function( stats_vmpooler_running__data ) {
if ( typeof stats_vmpooler_running__data[ 'stack' ] === 'undefined' ) {
stats_vmpooler_running__data[ 'stack' ] = [];
stats_vmpooler_running__data[ 'stack_t' ] = [];
}
for ( var key in stats_vmpooler_running__data ) {
if ( stats_vmpooler_running__data[ key ][ 'history' ] ) {
for ( var c = 0; c < stats_vmpooler_running__data[ key ][ 'history' ].length; c++ ) {
if ( typeof stats_vmpooler_running__data[ 'stack' ][ c ] === 'undefined' ) {
stats_vmpooler_running__data[ 'stack' ][ c ] = {};
}
stats_vmpooler_running__data[ 'stack' ][ c ][ key ] = stats_vmpooler_running__data[ key ][ 'history' ][ c ];
}
}
}
for ( var c in stats_vmpooler_running__data[ 'stack' ] ) {
for ( var n = 0; n < 8; n++ ) {
stats_vmpooler_running__data[ 'stack_t' ].push( stats_vmpooler_running__data[ 'stack' ][ c ] );
}
}
stats_vmpooler_running__data[ 'stack' ] = stats_vmpooler_running__data[ 'stack_t' ];
delete stats_vmpooler_running__data[ 'stack_t' ];
( function tick() {
setTimeout( function() {
var stats_vmpooler_running__data__live = ( function() {
var stats_vmpooler_running__data__live = null;
$.ajax( {
'url': running_url,
'async': false,
'global': false,
'dataType': 'json',
'success': function( data ) {
stats_vmpooler_running__data__live = data;
}
} );
return stats_vmpooler_running__data__live;
} )();
var stats_vmpooler_running__data__keys = [];
for ( var key in stats_vmpooler_running__data__live ) {
stats_vmpooler_running__data__keys.push( key );
for ( var c = 0; c < Object.keys(stats_vmpooler_running__data__keys).length; c++ ) { color[key] = colorscale( c ); }
}
$( '#stats-vmpooler-running' ).empty();
var x = d3.scale.linear().domain( [ 0, 500 ] ).range( [ 0, document.getElementById( 'stats-vmpooler-running' ).offsetWidth ] );
var y = d3.scale.linear().domain( [ 0, stats_vmpooler_running__data__total ] ).range( [ running_height, 0 ] );
var area = d3.svg.area()
.x( function( d, i ) { return x( i ); } )
.y0( function( d ) { return y( d.y0 ); } )
.y1( function( d ) { return y( d.y0 + d.y ); } );
var path = d3.svg.line()
.x( function( d, i ) { return x( i ); } )
.y( function( d ) { return y( d.y0 + d.y ); } );
var stack = d3.layout.stack()
.values( function( d ) { return d.values; } );
if ( typeof stats_vmpooler_running__data[ 'stack' ] === 'undefined' ) {
stats_vmpooler_running__data[ 'stack' ] = [];
}
stats_vmpooler_running__data[ 'tmp' ] = {};
for ( var key in stats_vmpooler_running__data__live ) {
stats_vmpooler_running__data[ 'tmp' ][ key ] = stats_vmpooler_running__data__live[ key ][ 'running' ];
}
stats_vmpooler_running__data[ 'stack' ].push( stats_vmpooler_running__data[ 'tmp' ] );
var stats_vmpooler_running__data__graph = stack(
stats_vmpooler_running__data__keys.sort().map(
function( name ) {
return {
name: name,
values: stats_vmpooler_running__data[ 'stack' ].map( function( d ) {
return { y: d[ name ] };
})
}
}
)
);
stats_vmpooler_running__data__total = d3.max(
stats_vmpooler_running__data__graph, function( layer ) {
return d3.max( layer.values, function( d ) {
return d.y0 + d.y;
} );
}
);
var svg = d3.select( '#stats-vmpooler-running' )
.append( 'svg' )
.attr( 'height', running_height )
.attr( 'width', ( document.getElementById( 'stats-vmpooler-running' ).offsetWidth - 35 ) )
.style( 'margin', '-40px 0px 0px 0px' )
.style( 'padding', '0px 10px 10px 10px' )
.append( 'g' );
var mysvg = svg.selectAll( '#stats-vmpooler-running' )
.data( stats_vmpooler_running__data__graph )
.enter()
.append( 'g' );
defs = mysvg.append( 'svg:defs' );
defs.append( 'svg:pattern' )
.attr( 'id', 'background' )
.attr( 'patternUnits', 'userSpaceOnUse' )
.attr( 'width', '500px' )
.attr( 'height', '500px' )
.append( 'svg:image' )
.attr( 'xlink:href', '/img/textured_paper.png' )
.attr( 'x', 0 )
.attr( 'y', 0 )
.attr( 'width', '500px' )
.attr( 'height', '500px' );
mysvg
.append( 'path' )
.attr( 'class', 'area' )
.attr( 'fill', 'url( #background )' )
.attr( 'opacity', '0.50' )
.attr( 'd', function( d ) { return area( d.values ); } );
mysvg.append( 'path' )
.attr( 'd', function( d ) { return area( d.values ); } )
.attr( 'class', 'area' )
.attr( 'opacity', '0.25' )
.style( 'fill', function( d ) { return color[ d.name ]; } );
mysvg.append( 'path' )
.attr( 'd', function( d ) { return path( d.values ); } )
.attr( 'class', 'line' )
.attr( 'stroke', function( d ) { return '#888'; } )
.attr( 'stroke-width', '1' );
stats_vmpooler_running__data__keys.sort().map(
function( key ) {
stats_vmpooler_running__svg[ key ] = d3.select( '#stats-vmpooler-running' )
.append( 'svg' )
.style( 'margin', '0px 0px 0px 0px' )
.style( 'padding', '0px 10px 10px 10px' )
.attr( 'width', '130px' )
.attr( 'height', '12px' );
stats_vmpooler_running__svg[ key ]
.append( 'rect' )
.attr( {
'x': '5',
'y': '3',
'width': '10',
'height': '10',
'opacity': '0.25',
'fill': function( d ) { return color[ key ]; }
} );
stats_vmpooler_running__svg[ key ]
.append( 'text' )
.text(
( key )
)
.attr( {
'x': '20',
'y': '12',
'font-face': '\'PT Sans\', sans-serif',
'font-size': '12px',
'font-weight': 'bold',
'fill': '#888'
} );
}
);
if ( stats_vmpooler_running__data[ 'stack' ].length > 500 ) {
stats_vmpooler_running__data[ 'stack' ].shift();
}
tick();
}, 5000 );
} )();
}
);

View file

@ -1,75 +1,66 @@
@media screen and (max-width:1500px) { body {
#stats-vmpooler-numbers .extra { margin: 1%;
display: none;
}
} }
body, #site-name {
#content { text-align: right;
background: #fff;
margin-top: 0px;
margin-left: auto;
margin-right: auto;
}
#wrap {
margin-left: 1%;
margin-right: 1%;
}
#header {
margin-top: 5px;
}
#header .logo,
#header .text {
margin-left: 30px;
margin-bottom: 5px;
display: inline-block;
}
#header .text {
float: right;
position: relative;
right: 30px;
top: 25px;
font: 55px 'PT Sans', sans-serif; font: 55px 'PT Sans', sans-serif;
letter-spacing: -0.05em; letter-spacing: -0.05em;
line-height: 50px; padding-top: 15px;
color: #444; padding-right: 5px;
padding-left: 20px;
padding-right: 20px;
} }
#stats-vmpooler-numbers { #dashboard-running .col-md-10 {
position: relative; margin-bottom: 5px;
top: -80px;
right: 15px;
} }
#stats-vmpooler-running { .col-md-1,
margin-bottom: -15px; .col-md-2,
.col-md-3,
.col-md-4,
.col-md-6,
.col-md-10,
.col-md-12 {
padding-left: 0px;
padding-right: 0px;
margin-left: 0px;
margin-right: 0px;
} }
.label { .section_label {
margin-top: 10px; margin-top: 10px;
text-transform: uppercase; margin-bottom: 10px;
text-indent: 25px;
font: 12px 'PT Sans', sans-serif; font: 12px 'PT Sans', sans-serif;
font-weight: bold; text-indent: 10px;
line-height: 20px;
color: #888;
border-bottom: solid 1px #888; border-bottom: solid 1px #888;
} }
.line {
fill: none;
}
.area {
stroke-width: 0;
}
.axis path,
.axis line {
fill: none;
stroke: #fff;
shape-rendering: crispEdges;
}
.tick {
fill: none;
stroke: #ddd;
}
.spinner { .spinner {
background-image: url( '/img/spinner.svg' ); background-image: url( '/img/spinner.svg' );
width: 15px; width: 25px;
height: 15px; height: 25px;
background-size: contain; background-size: contain;
opacity: .7; opacity: .7;
float: left;
margin-right: 5px;
-webkit-animation:rotate 1.5s infinite steps( 12 ); -webkit-animation:rotate 1.5s infinite steps( 12 );
-moz-animation:rotate 1.5s infinite steps( 12 ); -moz-animation:rotate 1.5s infinite steps( 12 );
-ms-animation:rotate 1.5s infinite steps( 12 ); -ms-animation:rotate 1.5s infinite steps( 12 );
@ -132,32 +123,3 @@ body,
transform: rotate( 360deg ); transform: rotate( 360deg );
} }
} }
.module {
margin-top: 5px;
margin-left: 25px;
margin-right: 25px;
font: 13px 'PT Sans', sans-serif;
line-height: 20px;
color: #888;
}
.line {
fill: none;
}
.area {
stroke-width: 0;
}
.axis path,
.axis line {
fill: none;
stroke: #fff;
shape-rendering: crispEdges;
}
.tick {
fill: none;
stroke: #ddd;
}

View file

@ -1,21 +1,63 @@
<!-- stats-vmpooler-running --> <!-- header -->
<div class='label'>VMs running tests</div> <div id='header'>
<div class='row'>
<div id='logo' class='col-md-2'><embed src='/img/logo.gif' style='max-width: 100%; max-height: 100%' /></div>
<div id='site-name' class='col-md-10'><%= site_name %></div>
<div id='stats-vmpooler-running' class='module'> <!-- dashboard-numbers -->
<div class='spinner'></div> Loading data...
<div id='dashboard-numbers'></div>
</div>
</div> </div>
<script src='/lib/stats-vmpooler-running.js'></script> <!-- dashboard-running -->
<div class='row'>
<div class='col-md-10' style='padding-right: 10px'>
<div class='section_label'><b>VMS RUNNING TESTS</b> &nbsp; ( past hour )</div>
</div>
<div class='col-md-2'>
<div class='section_label'><b>LEGEND</b></div>
</div>
</div>
<div class='row'>
<div id='dashboard-running'></div>
</div>
<br /> <br />
<!-- stats-vmpooler-pool --> <!-- dashboard-weekly -->
<div class='label'>individual pool capacity / fullness status</div> <div class='row'>
<div class='col-md-4' style='padding-right: 10px'>
<div id='stats-vmpooler-pool' class='module'> <div class='section_label'><b>VMS CLONED</b> &nbsp; ( by day, past week )</div>
<div class='spinner'></div> Loading data... </div>
<div class='col-md-4' style='padding-right: 10px'>
<div class='section_label'><b>AVG CLONE / BOOT TIME</b> &nbsp; ( by day, past week )</div>
</div>
<div class='col-md-4'>
<div class='section_label'><b>PLATFORM USAGE COUNT</b> &nbsp; ( past week )</div>
</div>
</div> </div>
<script src='/lib/stats-vmpooler-pool.js'></script> <div class='row'>
<div id='dashboard-weekly' style='padding-left: 5px'></div>
</div>
<br />
<!-- dashboard-pool -->
<div class='row'>
<div class='col-md-12'>
<div class='section_label'><b>INDIVIDUAL POOL CAPACITY</b> &nbsp; ( "fullness" status, past hour )</div>
</div>
</div>
<div class='row'>
<div id='dashboard-pool'></div>
</div>
<script src='/lib/dashboard.js'></script>

View file

@ -1,34 +1,46 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'>
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<link rel='stylesheet' type='text/css' href='/dashboard.css' /> <meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<script type='text/javascript' src='http://code.jquery.com/jquery-latest.min.js'></script> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<script type='text/javascript' src='http://d3js.org/d3.v3.min.js'></script> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src='https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js'></script>
<script src='https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js'></script>
<![endif]-->
<link rel='stylesheet' href='/bootstrap.min.css'>
<link rel='stylesheet' href='/vmpooler.css'>
<script type='text/javascript' src='/lib/jquery.min.js'></script>
<script type='text/javascript' src='/lib/bootstrap.min.js'></script>
<script type='text/javascript' src='/lib/d3.min.js'></script>
</head> </head>
<body> <body>
<div id='wrap'> <div id='loading' style='position: absolute; top: 0px; height: 100%; width: 100%; background-color: #fff; z-index: 1;'>
<div id='content'> <div id='logo' style='position: relative; top: 50%; transform: translateY(-50%);'>
<center>
<img src='/img/logo.gif' />
<div id='header'> <br />
<embed src='/img/logo.gif' width='200px' height='150px' class='logo' />
<div class='text'><%= site_name %></div> <div class='spinner'></div>
</center>
<div id='stats-vmpooler-numbers' class='module'></div>
<script src='/lib/stats-vmpooler-numbers.js'></script>
</div> </div>
</div>
<div class='container-fluid'>
<%= yield %> <%= yield %>
</div>
</div> </div>
</body> </body>