<!DOCTYPE html>
<html>
<head lang="en">
	<meta charset="UTF-8" />
	<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
	<meta name="viewport" content="user-scalable=0,initial-scale=1.0,minimal-ui" />
	<title>Pipeline Stats</title>
	<style>
		/*
		* blue: #16314f
		* green: #134f1d
		* red: #87190c
		* red-bright: #a41b0d
		*/
		html {
			box-sizing: border-box;
		}
		*, *:before, *:after {
			box-sizing: inherit;
		}
		html, body, body > .ember-view { margin: 0; padding: 0; width: 100%; height: 100%; }
		p,h1,h2,h3,h4,h5,h6 { margin: 0; padding: 0; }
		body {
			font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
			background: #000;
			color: #ccc;
			font-size: 10px;
		}

		.disconnected-message {
			display: none;
			position: fixed;
			z-index: 800;
			top: 50%;
			left: 50%;
			width: 600px;
			height: 80px;
			margin: -40px 0 0 -300px;
			border: 10px solid #a41b0d;
			background: #87190c;
			box-shadow: 0 0 0 10000px rgba(40,0,0,0.5);
			color: #d6bab7;
			font-weight: 600;
			font-size: 40px;
			text-align: center;
			line-height: 60px;
		}

		body.disconnected .disconnected-message {
			display: block;
		}

		.pulse {
			position: fixed;
			z-index: 1000;
			left: 10px;
			top: 10px;
			background: #16314f;
			width: 10px;
			height: 10px;
			border-radius: 100%;
			display: none;
		}
		.disconnected .pulse {
			background: #a41b0d;
			width: 20px;
			height: 20px;
		}

		.node {
			float: left;
			width: 20%;
			height: 25%;
			padding: 5px;
			text-align: center;
		}
		.node-inner {
			height: 100%;
			background: #191919;
			background: rgba(25,25,25,0.8);
			position: relative;
		}
		.node-content {
			position: relative;
			height: 100%;
			z-index: 10;
			padding: 10px 5px;
			text-shadow: 0 1px 1px rgba(0,0,0,0.8);
		}
		.node .name {
			font-size: 180%;
			font-weight: 300;
		}
		.node .waiting {
			font-size: 420%;
			font-weight: 600;
			text-shadow: 0 0 8px rgba(0,0,0,0.8);
			position: absolute;
			top: 35%;
			left: 0;
			right: 0;
		}
		.node .time {
			font-size: 220%;
			position: absolute;
			bottom: 10px;
			left: 0;
			right: 0;
		}
		.node.has-error {
			box-shadow: inset 0 0 0 3px #a41b0d;
		}

		.bar-graph {
			position: absolute;
			z-index: 1;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
		}
		.bar-graph > span {
			display: block;
			float: right;
			width: 10%;
			height: 100%;
		}
		.bar-graph span span {
			display: block;
			color: rgba(255,255,255,0.25);
			font-size: 10px;
			line-height: 200%;
			overflow: hidden;
		}
		.bar-graph .success {
			background: #134f1d;
		}
		.bar-graph .error {
			background: #87190c;
		}
	</style>
</head>
<body>
	{% raw %}
	<script type="text/x-handlebars" data-template-name="stats">
		<div class="pulse"></div>
		<div class="disconnected-message">DISCONNECTED</div>
		{{#each node in Pipeline.Data.nodes}}
			<div {{bind-attr class=":node hasError" style=node.sizeStyle}}>
				<div class="node-inner">
					<div class="node-content">
						<h1 class="name">{{unbound node.name}}</h1>
						<h2 class="waiting">{{unbound node.waiting}}</h2>
						<p class="time">{{unbound node.avgTimeFormatted}} / {{unbound node.percentileFormatted}}</p>
					</div>
					{{bar-graph data=node.latestHistogram}}
				</div>
			</div>
		{{/each}}
	</script>

	<script type="text/x-handlebars" data-template-name="components/bar-graph">
		{{#each bar in bars}}
			<span>
				<span class="blank" {{bind-attr style=bar.blank_style}}></span>
				<span class="success" {{bind-attr style=bar.success_style}}>{{unbound bar.success_count}}</span>
				<span class="error" {{bind-attr style=bar.error_style}}>{{unbound bar.error_count}}</span>
			</span>
		{{/each}}
	</script>
	{% endraw %}

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ember.js/1.6.1/ember.min.js"></script>
<script>
	window.PipelineSettings = {
		url: '/json/',
		refreshInterval: 1000
	};
</script>
<script>
	window.Utils = {
		getISODuration: function(text) {
			var iso8601 = /^P(?:(\d+(?:[\.,]\d{0,3})?W)|(\d+(?:[\.,]\d{0,3})?Y)?(\d+(?:[\.,]\d{0,3})?M)?(\d+(?:[\.,]\d{0,3})?D)?(?:T(\d+(?:[\.,]\d{0,3})?H)?(\d+(?:[\.,]\d{0,3})?M)?(\d+(?:[\.,]\d{0,6})?S)?)?)$/;
			var matches = text.match(iso8601);
			if (matches === null) {
				throw '"' + text + '" is an invalid ISO 8601 duration';
			}
			return {
				weeks: parseFloat(matches[1], 10),
				years: parseFloat(matches[2], 10),
				months: parseFloat(matches[3], 10),
				days: parseFloat(matches[4], 10),
				hours: parseFloat(matches[5], 10),
				minutes: parseFloat(matches[6], 10),
				seconds: parseFloat(matches[7], 10),
				milliseconds: parseFloat(matches[8], 10)
			};
		},

		formatISODuration: function(d) {
			var hours = 0;
			var minutes = 0;
			var seconds = 0;
			if (d.seconds) { seconds += parseInt(d.seconds) }
			if (d.minutes) { minutes += parseFloat(d.minutes) }
			if (d.hours) { hours += parseFloat(d.hours) }
			if (d.days) { hours += parseFloat(d.days)     * 24 }
			if (d.weeks) { hours += parseFloat(d.weeks)   * 24 * 7 }
			if (d.months) { hours += parseFloat(d.months) * 24 * 7 * 4 }
			if (d.years) { hours += parseFloat(d.years)   * 24 * 7 * 4 * 12 }
			seconds = seconds.toString();
			minutes = minutes.toString();
			hours = hours.toString();
			if (seconds.length == 1) { seconds = '0'+seconds }
			if (minutes.length == 1) { minutes = '0'+minutes }
			if (hours.length == 1) { hours = '0'+hours }
			return hours + ':' + minutes + ':' + seconds
		}
	};
</script>
<script>
	window.Pipeline = Ember.Application.create();
	Pipeline.Router.map(function() {
		this.resource('stats', { path: '/' });
	});

	Pipeline.StatsRoute = Ember.Route.extend({
		model: function(params) {
			return Pipeline.Data.load();
		}
	});

	Pipeline.Data = Ember.Object.create({
		nodes: [],
		lastMinutes: [],
		maxHistogramValue: 0,

		load: function() {
			var that = this;
			return Ember.$.getJSON(PipelineSettings.url, function(data) {
				var nodes = [];
				var maxHistogramValue = 0;
				that.set('lastMinutes', data['last_minutes']);
				data['sorted_process_statistics'].forEach(function(node) {
					var histogram = node[1]['histogram'];
					Ember.$.each(data['last_minutes'], function(i, minute) {
						var count = histogram[minute]['success_count'] + histogram[minute]['error_count'];
						if (maxHistogramValue < count) {
							maxHistogramValue = count;
						}
					});
					nodes.push(Pipeline.Node.create({
						'name': node[0],
						'histogram': node[1]['histogram'],
						'wat': node[1]['avg_time_taken'],
						'avgTime': Utils.getISODuration(node[1]['avg_time_taken']),
						'percentile': Utils.getISODuration(node[1]['95_percentile']),
						'waiting': node[1]['waiting']
					}));
				});
				that.set('maxHistogramValue', maxHistogramValue);
				nodes.forEach(function(node) {
					var histogram = node.get('histogram');
					Ember.$.each(node.get('histogram'), function(i, item) {
						if (maxHistogramValue > 0) {
							histogram[i]['success_percentage'] = (item['success_count'] / maxHistogramValue) * 100;
							histogram[i]['error_percentage'] = (item['error_count'] / maxHistogramValue) * 100;
						} else {
							histogram[i]['success_percentage'] = 0;
							histogram[i]['error_percentage'] = 0;
						}
					});
					node.set('histogram', histogram)
				});
				that.set('nodes', nodes);
			})
			.done(function() {
				Ember.$('body').removeClass('disconnected');
			})
			.fail(function() {
				Ember.$('body').addClass('disconnected');
			})
			.always(function() {
				var pulse = Ember.$('.pulse');
				pulse.show();
				Ember.run.later(this, function() { pulse.hide(); }, 100);
				Ember.run.later(that, function() {
					this.load();
				}, PipelineSettings.refreshInterval);
			});
		}
	});

	Pipeline.Node = Ember.Object.extend({
		sizeStyle: function() {
			var len = Pipeline.Data.get('nodes.length');
			if (len <= 6) {
				return 'width: 33.333%; height: 50%; font-size: 160%;';
			} else if (len <= 9) {
				return 'width: 33.333%; height: 33.333%; font-size: 140%;';
			} else if (len <= 12) {
				return 'width: 25%; height: 33.333%; font-size: 120%;';
			} else if (len <= 16) {
				return 'width: 25%; height: 25%; font-size: 110%;';
			} else if (len <= 20) {
				return 'width: 20%; height: 25%; font-size: 100%;';
			} else if (len <= 25) {
				return 'width: 20%; height: 20%; font-size: 90%;';
			} else {
				return 'width: 16.666%; height: 20%; font-size: 80%;';
			}
		}.property('Pipeline.Data.nodes'),

		latestHistogram: function() {
			var node = this;
			return Pipeline.Data.get('lastMinutes').map(function(minute) {
				return node.get('histogram')[minute];
			});
		}.property('Pipeline.Data.lastMinutes', 'histogram'),

		avgTimeFormatted: function() {
			return Utils.formatISODuration(this.get('avgTime'));
		}.property('avgTime'),

		percentileFormatted: function() {
			return Utils.formatISODuration(this.get('percentile'));
		}.property('percentile'),

		hasError: function() {
			var error = false;
			Ember.$.each(this.get('histogram'), function(i, item) {
				if (item['error_count'] > 0) {
					error = true;
				}
			});
			return error;
		}.property('histogram')
	});

	Pipeline.BarGraphComponent = Ember.Component.extend({
		classNames: 'bar-graph',

		bars: function() {
			return this.get('data').map(function(item) {
				return {
					'success_count': item['success_count'],
					'error_count': item['error_count'],
					'blank_style': 'height: '+parseInt(100-parseInt(item.success_percentage)-parseInt(item.error_percentage))+'%;',
					'success_style': 'height: '+parseInt(item.success_percentage)+'%;',
					'error_style': 'height: '+parseInt(item.error_percentage)+'%;'
				}
			});
		}.property('data')
	});
</script>
</body>
</html>