Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 890b2464ff42f9d28b443dc4f7c641fe724b2a67 0 parents
@jgallen23 authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules
50 bin/dashboard.js
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+
+var opt = require('optimist')
+ .usage('Start dashboard server\n$0 <config.json>')
+ .demand(1)
+ .options('h', {
+ desc: 'Show help info',
+ alias: 'help',
+ type: 'boolean'
+ })
+ .options('p', {
+ desc: 'Port to run server',
+ alias: 'port',
+ default: 8000
+ });
+
+var argv = opt.argv;
+
+if (argv.help) {
+ opt.showHelp();
+}
+
+var static = require('node-static');
+var path = require('path');
+var fs = require('fs');
+
+var publicPath = path.join(__dirname, '../public');
+var file = new(static.Server)(publicPath);
+
+var configFile = argv._[0];
+
+var startServer = function(err, configStr) {
+ if (err) throw err;
+ var config = JSON.parse(configStr);
+ require('http').createServer(function (request, response) {
+ if (request.url == '/config.json') {
+ response.setHeader('Content-Type', 'application/json');
+ response.end(configStr);
+ } else if (config.icon && request.url == '/ui/images/icon.png') {
+ fs.createReadStream(config.icon).pipe(response);
+ } else {
+ request.addListener('end', function () {
+ file.serve(request, response);
+ });
+ }
+ }).listen(argv.port);
+ console.log('Server started on port ' + argv.port);
+};
+
+fs.readFile(configFile, 'utf8', startServer);
22 package.json
@@ -0,0 +1,22 @@
+{
+ "name": "cube-dashboard",
+ "author": "Greg Allen <@jgaui> (http://jga.me)",
+ "description": "a dashboard for cube",
+ "homepage": "https://github.com/jgallen23/cube-dashboard",
+ "version": "0.0.1",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jgallen23/cube-dashboard.git"
+ },
+ "dependencies": {
+ "node-static": "*",
+ "optimist": "*"
+ },
+ "devDependencies": {
+ },
+ "engines": {
+ "node": "*"
+ },
+ "bin": { "cube-dashboard": "./bin/dashboard.js" },
+ "keywords": ["cube", "dashboard", "report"]
+}
37 public/index.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Dashboard</title>
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta name="apple-mobile-web-app-status-bar-style" content="black" />
+ <link rel="apple-touch-icon-precomposed" href="/ui/images/icon.png"/>
+ <meta name="viewport" content="width = device-width, initial-scale = 1.0, user-scalable = no" />
+ <!-- just for dev -->
+ <script src="/ui/vendor/live.js"></script>
+ <link rel="stylesheet" href="/ui/stylesheets/dashboard.css"/>
+ </head>
+ <body>
+ <header>
+ <select id="dashboards">
+ </select>
+ <select id="step">
+ <option value="1e4">10 seconds</option>
+ <option value="6e4">1 minute</option>
+ <option value="3e5">5 minutes</option>
@cwarden
cwarden added a note

Any reason to not include 1 hour and 1 day as options?

@jgallen23 Owner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ </select>
+ </header>
+ <div id="dashboard"></div>
+ <script src="/ui/vendor/d3.v2.min.js"></script>
+ <script src="/ui/vendor/cubism.v1.min.js"></script>
+ <script src="/ui/vendor/aug.min.js"></script>
+ <script src="/ui/scripts/dashboards.js"></script>
+ <script src="/ui/scripts/dashboard.js"></script>
+ <script>
+ d3.json('/config.json', function(config) {
+ window.dashboards = new DashboardSelect('#dashboards', config.dashboards);
+ window.dashboard = new Dashboard("#dashboard", config.host, dashboards.getCurrentDashboard());
+ });
+ </script>
+
+ </body>
+</html>
BIN  public/ui/images/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 public/ui/scripts/business.js
@@ -0,0 +1,18 @@
+var metrics = [
+ { title: 'Requests', metric: cube.metric("sum(request)") },
+ { title: 'Searches', metric: cube.metric("sum(search)") },
+ { title: 'Logins', metric: cube.metric("sum(login)") },
+ { title: 'New Users', metric: cube.metric("sum(newUser)") },
+ { title: 'New Items', metric: cube.metric("sum(newItem)") },
+ { title: 'Applications', metric: cube.metric("sum(comment)") },
+ { title: 'Likes', metric: cube.metric("sum(likeItem)") },
+ { title: 'User Selected', metric: cube.metric("sum(userSelected)") },
+ { title: 'Item Accepted', metric: cube.metric("sum(itemAccepted)") },
+ { title: 'Item Expired', metric: cube.metric("sum(itemExpired)") },
+ { title: 'Messages', metric: cube.metric("sum(message)") },
+ { title: 'Releases', metric: cube.metric("sum(release)") },
+ { title: '500s', metric: cube.metric("sum(error.eq(type, '500'))") },
+ { title: '404s', metric: cube.metric("sum(error.eq(type, '404'))") },
+ { title: 'JS Errors', metric: cube.metric("sum(error.eq(type, 'js'))") }
+];
+setDashboardMetrics(metrics, true);
105 public/ui/scripts/dashboard.js
@@ -0,0 +1,105 @@
+var Dashboard = function(elementSelector, host, options) {
+ this.selector = elementSelector;
+ this.host = host;
+ this.options = aug(true, {}, Dashboard.defaults, options);
+ this.setup();
+ if (options.metrics) {
+ this.setMetrics(options.metrics);
+ }
+};
+
+Dashboard.defaults = {
+ host: '',
+ showTotals: false,
+ height: 35
+};
+
+Dashboard.prototype.setup = function() {
+ var step = +cubism.option("step", 1e4);
+ var context = cubism.context()
+ .step(step)
+ .size(window.innerWidth - 4);
+
+ //$("window").resize(function() {
+ //window.location.reload();
+ //});;
+
+ this.cube = context.cube(this.host);
+ this.horizon = context.horizon();
+
+ // Add top and bottom axes to display the time.
+ d3.select(this.selector).selectAll(".axis")
+ .data(["top", "bottom"])
+ .enter().append("div")
+ .attr("class", function(d) { return d + " axis"; })
+ .each(function(d) { d3.select(this).call(context.axis().ticks(12).orient(d)); });
+
+ // Add a mouseover rule.
+ d3.select(this.selector)
+ .append("div")
+ .attr("class", "rule")
+ .call(context.rule());
+
+ //
+
+ // On mousemove, reposition the chart values to match the rule.
+ context.on("focus", function(i) {
+ d3.selectAll(".value").style("right", i == null ? null : context.size() - i + 5 + "px");
+ });
+
+ d3.selectAll("#step option").property("selected", function() {
+ return this.value == step;
+ });
+
+ d3.select("#step").on("change", function() {
+ window.location = "?step=" + this.value + "&" + location.search.replace(/[?&]step=[^&]*(&|$)/g, "$1").substring(1);
+ });
+};
+
+Dashboard.prototype.fetchTotals = function(metrics) {
+ var self = this;
+
+ var getTotal = function(index, expression, start, stop) {
+ var format = d3.time.format.iso;
+ var url = self.host+'/1.0/metric?expression='+expression+'&start='+format(start)+'&stop='+format(stop)+'&step=3600000&cachebuster='+ (+new Date());
+ d3.json(url, function(response) {
+ var val = 0;
+ for (var i = 0, c = response.length; i < c; i++) {
+ var res = response[i];
+ val += res.value;
+ }
+ var el = d3.selectAll('.horizon .title .totals')[0][index];
+ el.innerHTML = val;
+ });
+ };
+
+ var start = d3.time.day.floor(new Date());
+ var stop = d3.time.day.offset(start, 1);
+
+ for (var i = 0, c = metrics.length; i < c; i++) {
+ var metric = metrics[i];
+ var expression = metric.expression.toString();
+ getTotal(i, expression, start, stop);
+ }
+
+ setTimeout(this.fetchTotals, 60*1000);
+};
+
+Dashboard.prototype.setMetrics = function(metrics) {
+ var self = this;
+ d3.select(this.selector)
+ .insert("div", ".bottom")
+ .selectAll(".horizon")
+ .data(metrics)
+ .enter().append("div")
+ .attr("class", "horizon")
+ .call(self.horizon
+ .height(self.options.height)
+ .title(function(d) { return d.title; })
+ .metric(function(d) { return self.cube.metric(d.expression); }));
+
+ if (this.options.showTotals) {
+ d3.selectAll('.horizon .title').append('span').attr('class', 'totals');
+ this.fetchTotals(metrics);
+ }
+};
31 public/ui/scripts/dashboards.js
@@ -0,0 +1,31 @@
+var DashboardSelect = function(selector, dashboards) {
+ this.selector = selector;
+ this.dashboards = dashboards;
+
+ this.currentIndex = cubism.option("dashboard", 0);
+ this.setup();
+};
+
+DashboardSelect.prototype.setup = function() {
+
+ var select = d3.select(this.selector)[0][0];
+ for (var i = 0, c = this.dashboards.length; i < c; i++) {
+ var dashboard = this.dashboards[i];
+
+ var option = document.createElement('option');
+ option.setAttribute('value', i);
+ option.innerHTML = dashboard.name;
+ select.appendChild(option);
+ }
+ select.selectedIndex = this.currentIndex;
+ select.addEventListener('change', function() {
+ window.location = "?dashboard=" + select.value + "&" + location.search.replace(/[?&]dashboard=[^&]*(&|$)/g, "$1").substring(1);
+ });
+
+ document.title = this.getCurrentDashboard().name + ' Dashboard';
+
+};
+
+DashboardSelect.prototype.getCurrentDashboard = function() {
+ return this.dashboards[this.currentIndex];
+};
145 public/ui/stylesheets/dashboard.css
@@ -0,0 +1,145 @@
+body {
+ font-family: Arial;
+ margin: 0;
+ padding: 0;
+}
+header {
+ font-weight: 500;
+ height: 45px;
+ line-height: 45px;
+ padding: 0 10px;
+ background-color: #2c2c2c;
+ background-image: -moz-linear-gradient(top, #333333, #222222);
+ background-image: -ms-linear-gradient(top, #333333, #222222);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));
+ background-image: -webkit-linear-gradient(top, #333333, #222222);
+ background-image: -o-linear-gradient(top, #333333, #222222);
+ background-image: linear-gradient(top, #333333, #222222);
+ color: #fff;
+}
+ header #dashboards {
+ font-size: 20px;
+ }
+ header #step {
+ float: right;
+ margin-top: 15px;
+ }
+
+.group {
+ margin-bottom: 1em;
+}
+
+.axis {
+ font: 10px sans-serif;
+ position: static;
+ pointer-events: none;
+ z-index: 2;
+}
+
+.axis text {
+ -webkit-transition: fill-opacity 250ms linear;
+}
+
+.axis path {
+ display: none;
+}
+
+.axis line {
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
+
+.axis.top {
+ background-image: linear-gradient(top, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -o-linear-gradient(top, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -moz-linear-gradient(top, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -webkit-linear-gradient(top, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -ms-linear-gradient(top, #fff 0%, rgba(255,255,255,0) 100%);
+ top: 0px;
+ padding: 0 0 24px 0;
+}
+
+.axis.bottom {
+ background-image: linear-gradient(bottom, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -o-linear-gradient(bottom, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -moz-linear-gradient(bottom, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -webkit-linear-gradient(bottom, #fff 0%, rgba(255,255,255,0) 100%);
+ background-image: -ms-linear-gradient(bottom, #fff 0%, rgba(255,255,255,0) 100%);
+ bottom: 30px;
+ padding: 24px 0 0 0;
+}
+
+.horizon {
+ border-bottom: solid 1px #000;
+ overflow: hidden;
+ position: relative;
+}
+
+.horizon:first-child {
+ border-top: solid 1px #000;
+}
+
+:not(.horizon) + .horizon {
+ border-top: solid 1px #000;
+}
+
+.horizon canvas {
+ display: block;
+}
+
+.horizon .title,
+.horizon .value {
+ top: 0;
+ line-height: 20px;
+ position: absolute;
+ white-space: nowrap;
+ background: rgba(0, 0, 0, 0.6);
+ height: 20px;
+ font-size: 12px;
+ color: #fff;
+}
+.horizon .title {
+ border-radius: 0 0 5px 0;
+ padding: 0 10px 0 5px;
+}
+
+.horizon .value {
+ border-radius: 5px;
+ padding: 2px 4px;
+}
+
+.horizon .title {
+ left: 0;
+}
+
+.totals {
+ padding-left: 3px;
+ font-size: 12px;
+}
+.totals:before {
+ content: '('
+}
+.totals:after {
+ content: ')'
+}
+
+.horizon .value {
+ right: 0;
+}
+
+.line {
+ background: #000;
+ z-index: 1;
+}
+
+@media all and (max-width: 1439px) {
+ body { margin: 0px auto; }
+ .axis { position: static; }
+ .axis.top, .axis.bottom { padding: 0; }
+}
+@media all and (max-width: 400px) {
+ .horizon .title,
+ .horizon .value {
+ font-size: 12px;
+ }
+}
8 public/ui/vendor/aug.min.js
@@ -0,0 +1,8 @@
+/*!
+ * aug.js - A javascript library to extend existing objects and prototypes
+ * v0.0.5
+ * https://github.com/jgallen23/aug
+ * copyright JGA 2011
+ * MIT License
+ */
+var aug=function(){var b,c,d,e,f,g,h=!1,i=Array.prototype.slice.call(arguments),j=i.shift(),k=0;typeof j=="boolean"&&(h=!0,j=i.shift());for(g=i.length;k<g;k++){if((b=i[k])===null)continue;for(c in b){d=j[c],e=b[c];if(j===e)continue;h&&e&&typeof e=="object"?(e instanceof Array?f=d&&d instanceof Array?d:[]:f=d&&typeof d=="object"?d:{},j[c]=aug(h,f,e)):j[c]=e}}return j};typeof module!="undefined"&&(module.exports=aug)
1  public/ui/vendor/cubism.v1.min.js
@@ -0,0 +1 @@
+(function(a){function d(a){return a}function e(){}function h(a){return Math.floor(a/1e3)}function i(a){var b=a.indexOf("|"),c=a.substring(0,b),d=c.lastIndexOf(","),e=c.lastIndexOf(",",d-1),f=c.lastIndexOf(",",e-1),g=c.substring(f+1,e)*1e3,h=c.substring(d+1)*1e3;return a.substring(b+1).split(",").slice(1).map(function(a){return+a})}function j(a){if(!(a instanceof e))throw new Error("invalid context");this.context=a}function m(a,b){return function(c,d,e,f){a(new Date(+c+b),new Date(+d+b),e,f)}}function n(a,b){j.call(this,a),b=+b;var c=b+"";this.valueOf=function(){return b},this.toString=function(){return c}}function p(a,b){function c(b,c){if(c instanceof j){if(b.context!==c.context)throw new Error("mismatch context")}else c=new n(b.context,c);j.call(this,b.context),this.left=b,this.right=c,this.toString=function(){return b+" "+a+" "+c}}var d=c.prototype=Object.create(j.prototype);return d.valueAt=function(a){return b(this.left.valueAt(a),this.right.valueAt(a))},d.shift=function(a){return new c(this.left.shift(a),this.right.shift(a))},d.on=function(a,b){return arguments.length<2?this.left.on(a):(this.left.on(a,b),this.right.on(a,b),this)},function(a){return new c(this,a)}}function s(a){return a&16777214}function t(a){return(a+1&16777214)-1}var b=a.cubism={version:"1.1.0"},c=0;b.option=function(a,c){var d=b.options(a);return d.length?d[0]:c},b.options=function(a,b){var c=location.search.substring(1).split("&"),d=[],e=-1,f=c.length,g;while(++e<f)(g=c[e].split("="))[0]==a&&d.push(decodeURIComponent(g[1]));return d.length||arguments.length<2?d:b},b.context=function(){function p(){var c=Date.now();return g=new Date(Math.floor((c-j-k)/b)*b),f=new Date(g-d*b),i=new Date(Math.floor((c-j)/b)*b),h=new Date(i-d*b),m.domain([f,g]),a}var a=new e,b=1e4,d=1440,f,g,h,i,j=5e3,k=5e3,l=d3.dispatch("prepare","beforechange","change","focus"),m=a.scale=d3.time.scale().range([0,d]),n,o;return a.start=function(){n&&clearTimeout(n);var c=+i+j-Date.now();return c<k&&(c+=b),n=setTimeout(function e(){i=new Date(Math.floor((Date.now()-j)/b)*b),h=new Date(i-d*b),l.prepare.call(a,h,i),setTimeout(function(){m.domain([f=h,g=i]),l.beforechange.call(a,h,i),l.change.call(a,h,i),l.focus.call(a,o)},k),n=setTimeout(e,b)},c),a},a.stop=function(){return n=clearTimeout(n),a},n=setTimeout(a.start,10),a.step=function(a){return arguments.length?(b=+a,p()):b},a.size=function(a){return arguments.length?(m.range([0,d=+a]),p()):d},a.serverDelay=function(a){return arguments.length?(j=+a,p()):j},a.clientDelay=function(a){return arguments.length?(k=+a,p()):k},a.focus=function(b){return l.focus.call(a,o=b),a},a.on=function(b,c){return arguments.length<2?l.on(b):(l.on(b,c),c!=null&&(/^prepare(\.|$)/.test(b)&&c.call(a,h,i),/^beforechange(\.|$)/.test(b)&&c.call(a,f,g),/^change(\.|$)/.test(b)&&c.call(a,f,g),/^focus(\.|$)/.test(b)&&c.call(a,o)),a)},d3.select(window).on("keydown.context-"+ ++c,function(){switch(!d3.event.metaKey&&d3.event.keyCode){case 37:o==null&&(o=d-1),o>0&&a.focus(--o);break;case 39:o==null&&(o=d-2),o<d-1&&a.focus(++o);break;default:return}d3.event.preventDefault()}),p()};var f=e.prototype;f.constant=function(a){return new n(this,+a)},f.cube=function(a){arguments.length||(a="");var b={},c=this;return b.metric=function(b){return c.metric(function(c,d,e,f){d3.json(a+"/1.0/metric"+"?expression="+encodeURIComponent(b)+"&start="+g(c)+"&stop="+g(d)+"&step="+e,function(a){if(!a)return f(new Error("unable to load data"));f(null,a.map(function(a){return a.value}))})},b+="")},b.toString=function(){return a},b};var g=d3.time.format.iso;f.graphite=function(a){function d(a){var d=Math.round(c.step()/1e3);return d===10?this:(d=d%3600?d%60?d+"sec":d/60+"min":d/3600+"hour",b.metric("summarize("+this+",'"+d+"','"+a+"')"))}arguments.length||(a="");var b={},c=this;return b.metric=function(b){var e=c.metric(function(c,d,e,f){d3.text(a+"/render?format=raw"+"&target="+encodeURIComponent("alias("+b+",'')")+"&from="+h(c-2*e)+"&until="+h(d-1e3),function(a){if(!a)return f(new Error("unable to load data"));f(null,i(a))})},b+="");return e.summarize=d,e},b.find=function(b,c){d3.json(a+"/metrics/find?format=completer"+"&query="+encodeURIComponent(b),function(a){if(!a)return c(new Error("unable to find metrics"));c(null,a.metrics.map(function(a){return a.path}))})},b.toString=function(){return a},b};var k=j.prototype;k.valueAt=function(){return NaN},k.alias=function(a){return this.toString=function(){return a},this},k.extent=function(){var a=0,b=this.context.size(),c,d=Infinity,e=-Infinity;while(++a<b)c=this.valueAt(a),c<d&&(d=c),c>e&&(e=c);return[d,e]},k.on=function(a,b){return arguments.length<2?null:this},k.shift=function(){return this},k.on=function(){return arguments.length<2?null:this},f.metric=function(a,b){function r(b,c){var d=Math.min(k,Math.round((b-g)/i));if(!d||q)return;q=!0,d=Math.min(k,d+l);var f=new Date(c-d*i);a(f,c,i,function(a,b){q=!1;if(a)return console.warn(a);var d=isFinite(g)?Math.round((f-g)/i):0;for(var h=0,j=b.length;h<j;++h)n[h+d]=b[h];o.change.call(e,g,c)})}function s(a,b){isFinite(g)||(g=a),n.splice(0,Math.max(0,Math.min(k,Math.round((a-g)/i)))),g=a,h=b}var d=this,e=new j(d),f=".metric-"+ ++c,g=-Infinity,h,i=d.step(),k=d.size(),n=[],o=d3.dispatch("change"),p=0,q;return e.valueAt=function(a){return n[a]},e.shift=function(b){return d.metric(m(a,+b))},e.on=function(a,b){return arguments.length?(b==null?o.on(a)!=null&&--p==0&&d.on("prepare"+f,null).on("beforechange"+f,null):o.on(a)==null&&++p==1&&d.on("prepare"+f,r).on("beforechange"+f,s),o.on(a,b),b!=null&&/^change(\.|$)/.test(a)&&b.call(d,g,h),e):o.on(a)},arguments.length>1&&(e.toString=function(){return b}),e};var l=6,o=n.prototype=Object.create(j.prototype);o.valueAt=function(){return+this},o.extent=function(){return[+this,+this]},k.add=p("+",function(a,b){return a+b}),k.subtract=p("-",function(a,b){return a-b}),k.multiply=p("*",function(a,b){return a*b}),k.divide=p("/",function(a,b){return a/b}),f.horizon=function(){function o(o){o.on("mousemove.horizon",function(){a.focus(d3.mouse(this)[0])}).on("mouseout.horizon",function(){a.focus(null)}),o.append("canvas").attr("width",f).attr("height",g),o.append("span").attr("class","title").text(k),o.append("span").attr("class","value"),o.each(function(k,o){function B(c,d){w.save();var i=r.extent();A=i.every(isFinite),t!=null&&(i=t);var j=0,k=Math.max(-i[0],i[1]);if(this===a){if(k==y){j=f-l;var m=(c-u)/v;if(m<f){var n=e.getContext("2d");n.clearRect(0,0,f,g),n.drawImage(w.canvas,m,0,f-m,g,0,0,f-m,g),w.clearRect(0,0,f,g),w.drawImage(n.canvas,0,0)}}u=c}h.domain([0,y=k]),w.clearRect(j,0,f-j,g);var o;for(var p=0;p<z;++p){w.fillStyle=s[z+p];var q=(p-z+1)*g;h.range([z*g+q,q]),q=h(0);for(var x=j,B=f,C;x<B;++x){C=r.valueAt(x);if(C<=0){o=!0;continue}w.fillRect(x,C=h(C),1,q-C)}}if(o){b==="offset"&&(w.translate(0,g),w.scale(1,-1));for(var p=0;p<z;++p){w.fillStyle=s[z-1-p];var q=(p-z+1)*g;h.range([z*g+q,q]),q=h(0);for(var x=j,B=f,C;x<B;++x){C=r.valueAt(x);if(C>=0)continue;w.fillRect(x,h(-C),1,q-h(-C))}}}w.restore()}function C(a){a==null&&(a=f-1);var b=r.valueAt(a);x.datum(b).text(isNaN(b)?null:m)}var p=this,q=++c,r=typeof i=="function"?i.call(p,k,o):i,s=typeof n=="function"?n.call(p,k,o):n,t=typeof j=="function"?j.call(p,k,o):j,u=-Infinity,v=a.step(),w=d3.select(p).select("canvas"),x=d3.select(p).select(".value"),y,z=s.length>>1,A;w.datum({id:q,metric:r}),w=w.node().getContext("2d"),a.on("change.horizon-"+q,B),a.on("focus.horizon-"+q,C),r.on("change.horizon-"+q,function(a,b){B(a,b),C(),A&&r.on("change.horizon-"+q,d)})})}var a=this,b="offset",e=document.createElement("canvas"),f=e.width=a.size(),g=e.height=30,h=d3.scale.linear().interpolate(d3.interpolateRound),i=d,j=null,k=d,m=d3.format(".2s"),n=["#08519c","#3182bd","#6baed6","#bdd7e7","#bae4b3","#74c476","#31a354","#006d2c"];return o.remove=function(b){function c(b){b.metric.on("change.horizon-"+b.id,null),a.on("change.horizon-"+b.id,null),a.on("focus.horizon-"+b.id,null)}b.on("mousemove.horizon",null).on("mouseout.horizon",null),b.selectAll("canvas").each(c).remove(),b.selectAll(".title,.value").remove()},o.mode=function(a){return arguments.length?(b=a+"",o):b},o.height=function(a){return arguments.length?(e.height=g=+a,o):g},o.metric=function(a){return arguments.length?(i=a,o):i},o.scale=function(a){return arguments.length?(h=a,o):h},o.extent=function(a){return arguments.length?(j=a,o):j},o.title=function(a){return arguments.length?(k=a,o):k},o.format=function(a){return arguments.length?(m=a,o):m},o.colors=function(a){return arguments.length?(n=a,o):n},o},f.comparison=function(){function o(o){o.on("mousemove.comparison",function(){a.focus(d3.mouse(this)[0])}).on("mouseout.comparison",function(){a.focus(null)}),o.append("canvas").attr("width",b).attr("height",e),o.append("span").attr("class","title").text(j),o.append("span").attr("class","value primary"),o.append("span").attr("class","value change"),o.each(function(j,o){function B(c,d){x.save(),x.clearRect(0,0,b,e);var g=r.extent(),h=u.extent(),i=v==null?g:v;f.domain(i).range([e,0]),A=g.concat(h).every(isFinite);var j=c/a.step()&1?t:s;x.fillStyle=m[2];for(var k=0,l=b;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o<p&&x.fillRect(j(k),o,1,p-o)}x.fillStyle=m[0];for(k=0;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o>p&&x.fillRect(j(k),p,1,o-p)}x.fillStyle=m[3];for(k=0;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o<=p&&x.fillRect(j(k),o,1,n)}x.fillStyle=m[1];for(k=0;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o>p&&x.fillRect(j(k),o-n,1,n)}x.restore()}function C(a){a==null&&(a=b-1);var c=r.valueAt(a),d=u.valueAt(a),e=(c-d)/d;y.datum(c).text(isNaN(c)?null:k),z.datum(e).text(isNaN(e)?null:l).attr("class","value change "+(e>0?"positive":e<0?"negative":""))}function D(a,b){B(a,b),C(),A&&(r.on("change.comparison-"+q,d),u.on("change.comparison-"+q,d))}var p=this,q=++c,r=typeof g=="function"?g.call(p,j,o):g,u=typeof h=="function"?h.call(p,j,o):h,v=typeof i=="function"?i.call(p,j,o):i,w=d3.select(p),x=w.select("canvas"),y=w.select(".value.primary"),z=w.select(".value.change"),A;x.datum({id:q,primary:r,secondary:u}),x=x.node().getContext("2d"),r.on("change.comparison-"+q,D),u.on("change.comparison-"+q,D),a.on("change.comparison-"+q,B),a.on("focus.comparison-"+q,C)})}var a=this,b=a.size(),e=120,f=d3.scale.linear().interpolate(d3.interpolateRound),g=function(a){return a[0]},h=function(a){return a[1]},i=null,j=d,k=q,l=r,m=["#9ecae1","#225b84","#a1d99b","#22723a"],n=1.5;return o.remove=function(b){function c(b){b.primary.on("change.comparison-"+b.id,null),b.secondary.on("change.comparison-"+b.id,null),a.on("change.comparison-"+b.id,null),a.on("focus.comparison-"+b.id,null)}b.on("mousemove.comparison",null).on("mouseout.comparison",null),b.selectAll("canvas").each(c).remove(),b.selectAll(".title,.value").remove()},o.height=function(a){return arguments.length?(e=+a,o):e},o.primary=function(a){return arguments.length?(g=a,o):g},o.secondary=function(a){return arguments.length?(h=a,o):h},o.scale=function(a){return arguments.length?(f=a,o):f},o.extent=function(a){return arguments.length?(i=a,o):i},o.title=function(a){return arguments.length?(j=a,o):j},o.formatPrimary=function(a){return arguments.length?(k=a,o):k},o.formatChange=function(a){return arguments.length?(l=a,o):l},o.colors=function(a){return arguments.length?(m=a,o):m},o.strokeWidth=function(a){return arguments.length?(n=a,o):n},o};var q=d3.format(".2s"),r=d3.format("+.0%");f.axis=function(){function f(g){var h=++c,i,j=g.append("svg").datum({id:h}).attr("width",a.size()).attr("height",Math.max(28,-f.tickSize())).append("g").attr("transform","translate(0,"+(d.orient()==="top"?27:4)+")").call(d);a.on("change.axis-"+h,function(){j.call(d),i||(i=d3.select(j.node().appendChild(j.selectAll("text").node().cloneNode(!0))).style("display","none").text(null))}),a.on("focus.axis-"+h,function(a){if(i)if(a==null)i.style("display","none"),j.selectAll("text").style("fill-opacity",null);else{i.style("display",null).attr("x",a).text(e(b.invert(a)));var c=i.node().getComputedTextLength()+6;j.selectAll("text").style("fill-opacity",function(d){return Math.abs(b(d)-a)<c?0:1})}})}var a=this,b=a.scale,d=d3.svg.axis().scale(b),e=a.step()<6e4?u:a.step()<864e5?v:w;return f.remove=function(b){function c(b){a.on("change.axis-"+b.id,null),a.on("focus.axis-"+b.id,null)}b.selectAll("svg").each(c).remove()},d3.rebind(f,d,"orient","ticks","tickSubdivide","tickSize","tickPadding","tickFormat")};var u=d3.time.format("%I:%M:%S %p"),v=d3.time.format("%I:%M %p"),w=d3.time.format("%B %d");f.rule=function(){function b(b){var d=++c,e=b.append("div").datum({id:d}).attr("class","line").style("position","fixed").style("top",0).style("right",0).style("bottom",0).style("width","1px").style("pointer-events","none");a.on("focus.rule-"+d,function(a){e.style("display",a==null?"none":null).style("left",function(){return this.parentNode.getBoundingClientRect().left+a+"px"})})}var a=this;return b.remove=function(b){function c(b){a.on("focus.rule-"+b.id,null)}b.selectAll(".line").each(c).remove()},b}})(this);
4 public/ui/vendor/d3.v2.min.js
4 additions, 0 deletions not shown
233 public/ui/vendor/live.js
@@ -0,0 +1,233 @@
+/*
+ Live.js - One script closer to Designing in the Browser
+ Written for Handcraft.com by Martin Kool (@mrtnkl).
+
+ Version 4.
+ Recent change: Made stylesheet and mimetype checks case insensitive.
+
+ http://livejs.com
+ http://livejs.com/license (MIT)
+ @livejs
+
+ Include live.js#css to monitor css changes only.
+ Include live.js#js to monitor js changes only.
+ Include live.js#html to monitor html changes only.
+ Mix and match to monitor a preferred combination such as live.js#html,css
+
+ By default, just include live.js to monitor all css, js and html changes.
+
+ Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
+ as a page reload due to a change in html or css would not re-include the bookmarklet.
+ To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
+*/
+(function () {
+
+ var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
+ resources = {},
+ pendingRequests = {},
+ currentLinkElements = {},
+ oldLinkElements = {},
+ interval = 1000,
+ loaded = false,
+ active = { "html": 1, "css": 1, "js": 1 };
+
+ var Live = {
+
+ // performs a cycle per interval
+ heartbeat: function () {
+ if (document.body) {
+ // make sure all resources are loaded on first activation
+ if (!loaded) Live.loadresources();
+ Live.checkForChanges();
+ }
+ setTimeout(Live.heartbeat, interval);
+ },
+
+ // loads all local css and js resources upon first activation
+ loadresources: function () {
+
+ // helper method to assert if a given url is local
+ function isLocal(url) {
+ var loc = document.location,
+ reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
+ return url.match(reg);
+ }
+
+ // gather all resources
+ var scripts = document.getElementsByTagName("script"),
+ links = document.getElementsByTagName("link"),
+ uris = [];
+
+ // track local js urls
+ for (var i = 0; i < scripts.length; i++) {
+ var script = scripts[i], src = script.getAttribute("src");
+ if (src && isLocal(src))
+ uris.push(src);
+ if (src && src.match(/\blive.js#/)) {
+ for (var type in active)
+ active[type] = src.match("[#,|]" + type) != null
+ if (src.match("notify"))
+ alert("Live.js is loaded.");
+ }
+ }
+ if (!active.js) uris = [];
+ if (active.html) uris.push(document.location.href);
+
+ // track local css urls
+ for (var i = 0; i < links.length && active.css; i++) {
+ var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
+ if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
+ uris.push(href);
+ currentLinkElements[href] = link;
+ }
+ }
+
+ // initialize the resources info
+ for (var i = 0; i < uris.length; i++) {
+ var url = uris[i];
+ Live.getHead(url, function (url, info) {
+ resources[url] = info;
+ });
+ }
+
+ // add rule for morphing between old and new css files
+ var head = document.getElementsByTagName("head")[0],
+ style = document.createElement("style"),
+ rule = "transition: all .3s ease-out;"
+ css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
+ style.setAttribute("type", "text/css");
+ head.appendChild(style);
+ style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
+
+ // yep
+ loaded = true;
+ },
+
+ // check all tracking resources for changes
+ checkForChanges: function () {
+ for (var url in resources) {
+ if (pendingRequests[url])
+ continue;
+
+ Live.getHead(url, function (url, newInfo) {
+ var oldInfo = resources[url],
+ hasChanged = false;
+ resources[url] = newInfo;
+ for (var header in oldInfo) {
+ // do verification based on the header type
+ var oldValue = oldInfo[header],
+ newValue = newInfo[header],
+ contentType = newInfo["Content-Type"];
+ switch (header.toLowerCase()) {
+ case "etag":
+ if (!newValue) break;
+ // fall through to default
+ default:
+ hasChanged = oldValue != newValue;
+ break;
+ }
+ // if changed, act
+ if (hasChanged) {
+ Live.refreshResource(url, contentType);
+ break;
+ }
+ }
+ });
+ }
+ },
+
+ // act upon a changed url of certain content type
+ refreshResource: function (url, type) {
+ switch (type.toLowerCase()) {
+ // css files can be reloaded dynamically by replacing the link element
+ case "text/css":
+ var link = currentLinkElements[url],
+ html = document.body.parentNode,
+ head = link.parentNode,
+ next = link.nextSibling,
+ newLink = document.createElement("link");
+
+ html.className = html.className.replace(/\s*livejs\-loading/gi, '') + ' livejs-loading';
+ newLink.setAttribute("type", "text/css");
+ newLink.setAttribute("rel", "stylesheet");
+ newLink.setAttribute("href", url + "?now=" + new Date() * 1);
+ next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
+ currentLinkElements[url] = newLink;
+ oldLinkElements[url] = link;
+
+ // schedule removal of the old link
+ Live.removeoldLinkElements();
+ break;
+
+ // check if an html resource is our current url, then reload
+ case "text/html":
+ if (url != document.location.href)
+ return;
+
+ // local javascript changes cause a reload as well
+ case "text/javascript":
+ case "application/javascript":
+ case "application/x-javascript":
+ document.location.reload();
+ }
+ },
+
+ // removes the old stylesheet rules only once the new one has finished loading
+ removeoldLinkElements: function () {
+ var pending = 0;
+ for (var url in oldLinkElements) {
+ // if this sheet has any cssRules, delete the old link
+ try {
+ var link = currentLinkElements[url],
+ oldLink = oldLinkElements[url],
+ html = document.body.parentNode,
+ sheet = link.sheet || link.styleSheet,
+ rules = sheet.rules || sheet.cssRules;
+ if (rules.length >= 0) {
+ oldLink.parentNode.removeChild(oldLink);
+ delete oldLinkElements[url];
+ setTimeout(function () {
+ html.className = html.className.replace(/\s*livejs\-loading/gi, '');
+ }, 100);
+ }
+ } catch (e) {
+ pending++;
+ }
+ if (pending) setTimeout(Live.removeoldLinkElements, 50);
+ }
+ },
+
+ // performs a HEAD request and passes the header info to the given callback
+ getHead: function (url, callback) {
+ pendingRequests[url] = true;
+ var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
+ xhr.open("HEAD", url, true);
+ xhr.onreadystatechange = function () {
+ delete pendingRequests[url];
+ if (xhr.readyState == 4 && xhr.status != 304) {
+ xhr.getAllResponseHeaders();
+ var info = {};
+ for (var h in headers) {
+ var value = xhr.getResponseHeader(h);
+ // adjust the simple Etag variant to match on its significant part
+ if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
+ if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
+ info[h] = value;
+ }
+ callback(url, info);
+ }
+ }
+ xhr.send();
+ }
+ };
+
+ // start listening
+ if (document.location.protocol != "file:") {
+ if (!window.liveJsLoaded)
+ Live.heartbeat();
+
+ window.liveJsLoaded = true;
+ }
+ else if (window.console)
+ console.log("Live.js doesn't support the file protocol. It needs http.");
+})();
21 sample-config.json
@@ -0,0 +1,21 @@
+{
+ "host": "http://localhost:1081",
+ "icon": "path/to/your/ios/icon.png",
+ "dashboards": [
+ {
+ "name": "Site",
+ "showTotals": true,
+ "metrics": [
+ { "title": "Requests", "expression": "sum(request)" },
+ { "title": "Logins", "expression": "sum(login)" }
+ ]
+ },
+ {
+ "name": "Cube",
+ "metrics": [
+ { "title": "Cube Compute", "expression": "sum(cube_compute)" },
+ { "title": "Cube Request", "expression": "sum(cube_request)" }
+ ]
+ }
+ ]
+}
Please sign in to comment.
Something went wrong with that request. Please try again.