pulse_meter_visualizer 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. data/.gitignore +19 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +8 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +3 -0
  9. data/README.md +40 -0
  10. data/Rakefile +72 -0
  11. data/examples/basic.ru +145 -0
  12. data/examples/basic_sensor_data.rb +96 -0
  13. data/lib/pulse_meter/visualize/app.rb +78 -0
  14. data/lib/pulse_meter/visualize/base.rb +15 -0
  15. data/lib/pulse_meter/visualize/coffee/application.coffee +40 -0
  16. data/lib/pulse_meter/visualize/coffee/collections/page_info_list.coffee +17 -0
  17. data/lib/pulse_meter/visualize/coffee/collections/sensor_info_list.coffee +4 -0
  18. data/lib/pulse_meter/visualize/coffee/collections/widget_list.coffee +14 -0
  19. data/lib/pulse_meter/visualize/coffee/extensions.coffee +26 -0
  20. data/lib/pulse_meter/visualize/coffee/models/dinamic_widget.coffee +34 -0
  21. data/lib/pulse_meter/visualize/coffee/models/page_info.coffee +2 -0
  22. data/lib/pulse_meter/visualize/coffee/models/sensor_info.coffee +2 -0
  23. data/lib/pulse_meter/visualize/coffee/models/widget.coffee +54 -0
  24. data/lib/pulse_meter/visualize/coffee/presenters/area.coffee +2 -0
  25. data/lib/pulse_meter/visualize/coffee/presenters/gauge.coffee +11 -0
  26. data/lib/pulse_meter/visualize/coffee/presenters/line.coffee +2 -0
  27. data/lib/pulse_meter/visualize/coffee/presenters/pie.coffee +20 -0
  28. data/lib/pulse_meter/visualize/coffee/presenters/series.coffee +44 -0
  29. data/lib/pulse_meter/visualize/coffee/presenters/table.coffee +10 -0
  30. data/lib/pulse_meter/visualize/coffee/presenters/timeline.coffee +13 -0
  31. data/lib/pulse_meter/visualize/coffee/presenters/widget.coffee +65 -0
  32. data/lib/pulse_meter/visualize/coffee/router.coffee +21 -0
  33. data/lib/pulse_meter/visualize/coffee/views/dynamic_chart.coffee +91 -0
  34. data/lib/pulse_meter/visualize/coffee/views/dynamic_widget.coffee +58 -0
  35. data/lib/pulse_meter/visualize/coffee/views/page_title.coffee +17 -0
  36. data/lib/pulse_meter/visualize/coffee/views/page_titles.coffee +15 -0
  37. data/lib/pulse_meter/visualize/coffee/views/sensor_info_list.coffee +19 -0
  38. data/lib/pulse_meter/visualize/coffee/views/widget.coffee +99 -0
  39. data/lib/pulse_meter/visualize/coffee/views/widget_chart.coffee +13 -0
  40. data/lib/pulse_meter/visualize/coffee/views/widget_list.coffee +15 -0
  41. data/lib/pulse_meter/visualize/dsl/base.rb +131 -0
  42. data/lib/pulse_meter/visualize/dsl/errors.rb +40 -0
  43. data/lib/pulse_meter/visualize/dsl/layout.rb +27 -0
  44. data/lib/pulse_meter/visualize/dsl/page.rb +33 -0
  45. data/lib/pulse_meter/visualize/dsl/sensor.rb +20 -0
  46. data/lib/pulse_meter/visualize/dsl/widget.rb +37 -0
  47. data/lib/pulse_meter/visualize/dsl/widgets/area.rb +20 -0
  48. data/lib/pulse_meter/visualize/dsl/widgets/gauge.rb +12 -0
  49. data/lib/pulse_meter/visualize/dsl/widgets/line.rb +21 -0
  50. data/lib/pulse_meter/visualize/dsl/widgets/pie.rb +16 -0
  51. data/lib/pulse_meter/visualize/dsl/widgets/table.rb +19 -0
  52. data/lib/pulse_meter/visualize/layout.rb +79 -0
  53. data/lib/pulse_meter/visualize/page.rb +25 -0
  54. data/lib/pulse_meter/visualize/public/css/application.css +56 -0
  55. data/lib/pulse_meter/visualize/public/css/bootstrap.css +4883 -0
  56. data/lib/pulse_meter/visualize/public/css/bootstrap.min.css +729 -0
  57. data/lib/pulse_meter/visualize/public/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  58. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  59. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  60. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  61. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  62. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_75_ffffff_1x400.png +0 -0
  63. data/lib/pulse_meter/visualize/public/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  64. data/lib/pulse_meter/visualize/public/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png +0 -0
  65. data/lib/pulse_meter/visualize/public/css/images/ui-icons_222222_256x240.png +0 -0
  66. data/lib/pulse_meter/visualize/public/css/images/ui-icons_2e83ff_256x240.png +0 -0
  67. data/lib/pulse_meter/visualize/public/css/images/ui-icons_454545_256x240.png +0 -0
  68. data/lib/pulse_meter/visualize/public/css/images/ui-icons_888888_256x240.png +0 -0
  69. data/lib/pulse_meter/visualize/public/css/images/ui-icons_cd0a0a_256x240.png +0 -0
  70. data/lib/pulse_meter/visualize/public/css/images/ui-icons_f6cf3b_256x240.png +0 -0
  71. data/lib/pulse_meter/visualize/public/css/jquery-ui-1.8.16.bootstrap.css +1320 -0
  72. data/lib/pulse_meter/visualize/public/favicon.ico +208 -0
  73. data/lib/pulse_meter/visualize/public/img/glyphicons-halflings-white.png +0 -0
  74. data/lib/pulse_meter/visualize/public/img/glyphicons-halflings.png +0 -0
  75. data/lib/pulse_meter/visualize/public/js/application.js +973 -0
  76. data/lib/pulse_meter/visualize/public/js/backbone-min.js +38 -0
  77. data/lib/pulse_meter/visualize/public/js/bootstrap.js +1835 -0
  78. data/lib/pulse_meter/visualize/public/js/jquery-1.7.2.min.js +4 -0
  79. data/lib/pulse_meter/visualize/public/js/jquery-ui-1.8.16.bootstrap.min.js +791 -0
  80. data/lib/pulse_meter/visualize/public/js/jquery-ui-1.8.23.custom.min.js +21 -0
  81. data/lib/pulse_meter/visualize/public/js/jquery-ui-timepicker-addon.js +1687 -0
  82. data/lib/pulse_meter/visualize/public/js/json2.js +487 -0
  83. data/lib/pulse_meter/visualize/public/js/underscore-min.js +32 -0
  84. data/lib/pulse_meter/visualize/sensor.rb +63 -0
  85. data/lib/pulse_meter/visualize/series_extractor.rb +107 -0
  86. data/lib/pulse_meter/visualize/views/main.haml +30 -0
  87. data/lib/pulse_meter/visualize/views/sensors.haml +76 -0
  88. data/lib/pulse_meter/visualize/views/widgets/area.haml +53 -0
  89. data/lib/pulse_meter/visualize/views/widgets/extend_options.haml +11 -0
  90. data/lib/pulse_meter/visualize/views/widgets/gauge.haml +13 -0
  91. data/lib/pulse_meter/visualize/views/widgets/line.haml +54 -0
  92. data/lib/pulse_meter/visualize/views/widgets/pie.haml +13 -0
  93. data/lib/pulse_meter/visualize/views/widgets/table.haml +45 -0
  94. data/lib/pulse_meter/visualize/widget.rb +38 -0
  95. data/lib/pulse_meter/visualize/widgets/gauge.rb +47 -0
  96. data/lib/pulse_meter/visualize/widgets/pie.rb +36 -0
  97. data/lib/pulse_meter/visualize/widgets/timeline.rb +114 -0
  98. data/lib/pulse_meter/visualizer.rb +38 -0
  99. data/lib/pulse_meter_visualizer.rb +2 -0
  100. data/pulse_meter_visualizer.gemspec +41 -0
  101. data/spec/pulse_meter/visualize/app_spec.rb +27 -0
  102. data/spec/pulse_meter/visualize/dsl/layout_spec.rb +64 -0
  103. data/spec/pulse_meter/visualize/dsl/page_spec.rb +62 -0
  104. data/spec/pulse_meter/visualize/dsl/sensor_spec.rb +30 -0
  105. data/spec/pulse_meter/visualize/dsl/widget_spec.rb +6 -0
  106. data/spec/pulse_meter/visualize/dsl/widgets/area_spec.rb +44 -0
  107. data/spec/pulse_meter/visualize/dsl/widgets/gauge_spec.rb +22 -0
  108. data/spec/pulse_meter/visualize/dsl/widgets/line_spec.rb +44 -0
  109. data/spec/pulse_meter/visualize/dsl/widgets/pie_spec.rb +35 -0
  110. data/spec/pulse_meter/visualize/dsl/widgets/table_spec.rb +36 -0
  111. data/spec/pulse_meter/visualize/layout_spec.rb +54 -0
  112. data/spec/pulse_meter/visualize/page_spec.rb +153 -0
  113. data/spec/pulse_meter/visualize/sensor_spec.rb +120 -0
  114. data/spec/pulse_meter/visualize/series_extractor_spec.rb +80 -0
  115. data/spec/pulse_meter/visualize/widgets/area_spec.rb +6 -0
  116. data/spec/pulse_meter/visualize/widgets/gauge_spec.rb +63 -0
  117. data/spec/pulse_meter/visualize/widgets/line_spec.rb +6 -0
  118. data/spec/pulse_meter/visualize/widgets/pie_spec.rb +73 -0
  119. data/spec/pulse_meter/visualize/widgets/table_spec.rb +6 -0
  120. data/spec/pulse_meter/visualizer_spec.rb +42 -0
  121. data/spec/shared_examples/dsl_widget.rb +106 -0
  122. data/spec/shared_examples/widget.rb +97 -0
  123. data/spec/spec_helper.rb +36 -0
  124. metadata +518 -0
@@ -0,0 +1,32 @@
1
+ // Underscore.js 1.3.3
2
+ // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
+ // Underscore is freely distributable under the MIT license.
4
+ // Portions of Underscore are inspired or borrowed from Prototype,
5
+ // Oliver Steele's Functional, and John Resig's Micro-Templating.
6
+ // For all details and documentation:
7
+ // http://documentcloud.github.com/underscore
8
+ (function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
9
+ c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
10
+ g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
11
+ c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
12
+ a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
13
+ c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
14
+ a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
15
+ function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
16
+ (e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
17
+ j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
18
+ 0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
19
+ e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
20
+ i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
21
+ 1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
22
+ i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
23
+ g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
24
+ return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
25
+ c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
26
+ function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
27
+ b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
28
+ b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
29
+ function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
30
+ u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
31
+ b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
32
+ this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
@@ -0,0 +1,63 @@
1
+ require "pulse_meter/visualize/series_extractor"
2
+
3
+ module PulseMeter
4
+ module Visualize
5
+ class Sensor < Base
6
+ def last_value(now, need_incomplete=false)
7
+ sensor = real_sensor
8
+
9
+ sensor_data = if need_incomplete
10
+ sensor.timeline_within(now - sensor.interval, now).first
11
+ else
12
+ sensor.timeline_within(now - sensor.interval * 2, now).first
13
+ end
14
+
15
+ if sensor_data.is_a?(PulseMeter::SensorData)
16
+ sensor_data.value
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ def last_point_data(now, need_incomplete=false)
23
+ extractor.point_data(last_value(now, need_incomplete))
24
+ end
25
+
26
+ def timeline_data(from, till, need_incomplete = false)
27
+ sensor = real_sensor
28
+ timeline_data = sensor.timeline_within(from, till)
29
+ timeline_data.pop unless need_incomplete
30
+ extractor.series_data(timeline_data)
31
+ end
32
+
33
+ def annotation
34
+ real_sensor.annotation || ''
35
+ end
36
+
37
+ def type
38
+ real_sensor.class
39
+ end
40
+
41
+ def interval
42
+ real_sensor.interval
43
+ end
44
+
45
+ def value
46
+ real_sensor.value
47
+ end
48
+
49
+ def extractor
50
+ PulseMeter::Visualize.extractor(self)
51
+ end
52
+
53
+ protected
54
+
55
+ def real_sensor
56
+ # TODO add !temporarily! caching if this will be called too frequently
57
+ PulseMeter::Sensor::Base.restore(name)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,107 @@
1
+ module PulseMeter
2
+ module Visualize
3
+ module SeriesExtractor
4
+ class Simple
5
+ def initialize(sensor)
6
+ @sensor = sensor
7
+ end
8
+
9
+ def opts_to_add
10
+ opts = {}
11
+ opts[:color] = @sensor.color if @sensor.color
12
+ opts[:name] = @sensor.annotation
13
+ opts
14
+ end
15
+
16
+ def series_data(timeline_data)
17
+ [{
18
+ data: timeline_data.map{|sd| {x: sd.start_time.to_i*1000, y: to_float(sd.value)}}
19
+ }.merge(opts_to_add)]
20
+ end
21
+
22
+ def point_data(value)
23
+ [{
24
+ y: to_float(value)
25
+ }.merge(opts_to_add)]
26
+ end
27
+
28
+ protected
29
+
30
+ def to_float(val)
31
+ val && val.to_f
32
+ end
33
+ end
34
+
35
+
36
+ class Hashed < Simple
37
+
38
+ def parse_data(value)
39
+ if value
40
+ if value.is_a?(String)
41
+ JSON.parse(value)
42
+ else
43
+ value
44
+ end
45
+ else
46
+ {}
47
+ end
48
+ end
49
+
50
+ def point_data(value)
51
+ data = parse_data(value)
52
+ data.keys.map do |k|
53
+ {
54
+ y: to_float(data[k]),
55
+ name: series_title(k)
56
+ }
57
+ end
58
+ end
59
+
60
+ def series_title(key)
61
+ annotation = @sensor.annotation
62
+ if annotation && !annotation.empty?
63
+ "#{annotation}: #{key}"
64
+ else
65
+ key
66
+ end
67
+ end
68
+
69
+ def series_data(timeline_data)
70
+ series_data = {}
71
+ parsed_data = timeline_data.map do |sd|
72
+ data = parse_data(sd.value)
73
+ data.keys.each{|k| series_data[k] ||= {name: series_title(k), data: []}}
74
+ [sd.start_time.to_i*1000, data]
75
+ end
76
+
77
+ series_names = series_data.keys.sort
78
+ parsed_data.each do |data|
79
+ series_names.each do |k|
80
+ series_data[k][:data] << {x: data[0], y: to_float(data[1][k])}
81
+ end
82
+ end
83
+ series_data.values
84
+ end
85
+ end
86
+ end
87
+
88
+ SPECIAL_SERIES_EXTRACTORS = {
89
+ 'HashedCounter' => SeriesExtractor::Hashed,
90
+ 'HashedIndicator' => SeriesExtractor::Hashed,
91
+ 'MultiPercentile' => SeriesExtractor::Hashed
92
+ }.freeze
93
+
94
+ DEFAULT_SERIES_EXTRACTOR = SeriesExtractor::Simple
95
+
96
+ def extractor(sensor)
97
+ key = sensor.type.to_s.split('::').last
98
+ extractor_class = SPECIAL_SERIES_EXTRACTORS[key] || DEFAULT_SERIES_EXTRACTOR
99
+ extractor_class.new(sensor)
100
+ end
101
+
102
+ module_function :extractor
103
+
104
+ end
105
+ end
106
+
107
+
@@ -0,0 +1,30 @@
1
+ !!!
2
+ %head
3
+ %title= @title
4
+ :javascript
5
+ var ROOT = "#{url('/')}";
6
+ = include_gon
7
+ - %w{jquery-1.7.2.min.js jquery-ui-1.8.16.bootstrap.min.js jquery-ui-timepicker-addon.js json2.js underscore-min.js backbone-min.js application.js bootstrap.js}.each do |jsfile|
8
+ %script{type: 'text/javascript', src: url("/js/#{jsfile}")}
9
+ - %w{bootstrap.min.css application.css jquery-ui-1.8.16.bootstrap.css}.each do |cssfile|
10
+ %link{rel: 'stylesheet', href: url("/css/#{cssfile}"), type: 'text/css', media: 'screen'}
11
+ %script{type: 'text/javascript', src: "https://www.google.com/jsapi"}
12
+ :javascript
13
+ google.load("visualization", "1", {packages:["corechart", "table", "gauge"]});
14
+ google.setOnLoadCallback(drawChart);
15
+ function drawChart() {
16
+ document.startApp();
17
+ }
18
+ %body
19
+ - %w(area line table pie gauge).each do |wtype|
20
+ = partial "widgets/#{wtype}"
21
+ = partial "sensors"
22
+
23
+ .navbar.navbar-fixed-top
24
+ .navbar-inner
25
+ .container-fluid
26
+ %a.brand{href: '#/custom'}= @title
27
+ %ul.nav#page-titles
28
+
29
+ .container-fluid#main
30
+ .row-fluid#widgets
@@ -0,0 +1,76 @@
1
+ %script#sensor-list{type: 'text/template'}
2
+ %table#sensors.table.table-striped
3
+ %tr
4
+ %th
5
+ %th Title
6
+ %th Type
7
+ %th Interval
8
+ <% _.each(sensors, function(sensor) { %>
9
+ %tr
10
+ %td
11
+ :plain
12
+ <input class="sensor-box" type="checkbox" id="<%- sensor.id %>" />
13
+ %td
14
+ <%- sensor.annotation %>
15
+ %sub <%- sensor.id %>
16
+ %td <%- sensor.type %>
17
+ %td <%- sensor.interval.humanize() %>
18
+ <% }) %>
19
+
20
+ %script#dynamic-widget-error{type: 'text/template'}
21
+ .alert.alert-error
22
+ .button.close{'data-dismiss' => 'alert'} &times;
23
+ <%- error %>
24
+
25
+ %script#dynamic-widget-plotarea{type: 'text/template'}
26
+ .well
27
+ #chart
28
+ %h3 Choose sensors to plot
29
+ #dynamic-chart-controls
30
+ .form-inline
31
+ %button#refresh-chart.btn.btn-mini
32
+ %i.icon-refresh
33
+ Refresh chart
34
+ .form-inline
35
+ Timespan:
36
+ %select#extend-timespan-val.btn-mini.span3
37
+ = partial "widgets/extend_options"
38
+ %button#extend-timespan.btn.btn-mini
39
+ %i.icon-arrow-left
40
+ Extend
41
+ %button#reset-timespan.btn.btn-mini
42
+ %i.icon-arrow-right
43
+ Reset
44
+ .form-inline
45
+ %label
46
+ From:
47
+ %span#start-time
48
+ %input.datepicker
49
+ %label
50
+ Till:
51
+ %span#end-time
52
+ %input.datepicker
53
+ %button#set-interval.btn.btn-mini
54
+ %i.icon-time
55
+ Set interval
56
+
57
+
58
+ %script#dynamic-widget{type: 'text/template'}
59
+ .span6
60
+ #sensor-controls.form-horizontal
61
+ %p.form-inline
62
+ Chart type
63
+ %select#chart-type{value: 'area'}
64
+ %option{value: 'area'} Area
65
+ %option{value: 'line'} Line
66
+ %option{value: 'table'} Table
67
+ %button#draw.btn.btn-mini.btn-primary
68
+ %i.icon-play
69
+ Draw
70
+ %button#refresh.btn.btn-mini
71
+ %i.icon-refresh
72
+ Refresh sensor list
73
+ #sensor-list-area
74
+ .span6
75
+ #dynamic-plotarea
76
+ #errors
@@ -0,0 +1,53 @@
1
+ %script.widget-template{type: 'text/template', 'data-widget-type' => 'area'}
2
+ #plotarea
3
+ #chart-controls
4
+ .form-inline
5
+ %button#refresh.btn.btn-mini
6
+ %i.icon-refresh
7
+ Refresh
8
+ %a#configure-button.btn.btn-mini{href: "#configure", role: 'button', 'data-toggle' => 'modal'}
9
+ %i.icon-wrench
10
+ Configure...
11
+ .modal.hide.fade#configure
12
+ .modal-header
13
+ %button.close{type: 'button', 'data-dismiss' => 'modal', 'aria-hidden' => 'true'} &times;
14
+ %h3 Configure
15
+ .modal-body
16
+ %p
17
+ %label
18
+ Cutoff min:
19
+ %input.btn-mini#cutoff-min
20
+ %span.space
21
+ %label
22
+ Cutoff max:
23
+ %input.btn-mini#cutoff-max
24
+ %hr
25
+ %p
26
+ %label
27
+ Refresh:
28
+ %input.btn-mini#need-refresh{type: :checkbox, checked: :true}
29
+ %hr
30
+ %p
31
+ %label
32
+ Timespan:
33
+ %select#extend-timespan-val.btn-mini.span3
34
+ = partial "widgets/extend_options"
35
+ %button#extend-timespan.btn.btn-mini
36
+ %i.icon-arrow-left
37
+ Extend
38
+ %button#reset-timespan.btn.btn-mini
39
+ %i.icon-arrow-right
40
+ Reset
41
+ %hr
42
+ %p
43
+ %label
44
+ From:
45
+ %span#start-time
46
+ %input.datepicker
47
+ %label
48
+ Till:
49
+ %span#end-time
50
+ %input.datepicker
51
+ %button#set-interval.btn.btn-mini
52
+ %i.icon-time
53
+ Set interval
@@ -0,0 +1,11 @@
1
+ %option{value: 60, selected: true} 1 minute
2
+ %option{value: 60 * 5} 5 minutes
3
+ %option{value: 60 * 30} 30 minutes
4
+ %option{value: 60 * 60 * 2} 2 hours
5
+ %option{value: 60 * 60 * 6 } 6 hours
6
+ %option{value: 60 * 60 * 24} 1 day
7
+ %option{value: 60 * 60 * 24 * 2} 2 days
8
+ %option{value: 60 * 60 * 24 * 7} 1 week
9
+ %option{value: 60 * 60 * 24 * 7 * 2} 2 weeks
10
+ %option{value: 60 * 60 * 24 * 30} 1 month
11
+
@@ -0,0 +1,13 @@
1
+ %script.widget-template{type: 'text/template', 'data-widget-type' => 'gauge'}
2
+ #plotarea
3
+ #chart-controls
4
+ .form-inline
5
+ %button#refresh.btn.btn-mini
6
+ %i.icon-refresh
7
+ Refresh
8
+ %span.space
9
+ %label
10
+ Refresh:
11
+ %input.btn-mini#need-refresh{type: :checkbox, checked: :true}
12
+ %hr
13
+
@@ -0,0 +1,54 @@
1
+ %script.widget-template{type: 'text/template', 'data-widget-type' => 'line'}
2
+ #plotarea
3
+ #chart-controls
4
+ .form-inline
5
+ %button#refresh.btn.btn-mini
6
+ %i.icon-refresh
7
+ Refresh
8
+ %a#configure-button.btn.btn-mini{href: "#configure", role: 'button', 'data-toggle' => 'modal'}
9
+ %i.icon-wrench
10
+ Configure...
11
+ .modal.hide.fade#configure
12
+ .modal-header
13
+ %button.close{type: 'button', 'data-dismiss' => 'modal', 'aria-hidden' => 'true'} &times;
14
+ %h3 Configure
15
+ .modal-body
16
+ %p
17
+ %label
18
+ Cutoff min:
19
+ %input.btn-mini#cutoff-min
20
+ %span.space
21
+ %label
22
+ Cutoff max:
23
+ %input.btn-mini#cutoff-max
24
+ %hr
25
+ %p
26
+ %label
27
+ Refresh:
28
+ %input.btn-mini#need-refresh{type: :checkbox, checked: :true}
29
+ %hr
30
+ %p
31
+ %label
32
+ Timespan:
33
+ %select#extend-timespan-val.btn-mini.span3
34
+ = partial "widgets/extend_options"
35
+ %button#extend-timespan.btn.btn-mini
36
+ %i.icon-arrow-left
37
+ Extend
38
+ %button#reset-timespan.btn.btn-mini
39
+ %i.icon-arrow-right
40
+ Reset
41
+ %hr
42
+ %p
43
+ %label
44
+ From:
45
+ %span#start-time
46
+ %input.datepicker
47
+ %label
48
+ Till:
49
+ %span#end-time
50
+ %input.datepicker
51
+ %button#set-interval.btn.btn-mini
52
+ %i.icon-time
53
+ Set interval
54
+