dasht 0.1.0

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.agignore +3 -0
  3. data/Gemfile +14 -0
  4. data/Gemfile.lock +88 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +329 -0
  7. data/Rakefile +51 -0
  8. data/VERSION +1 -0
  9. data/assets/css/style.css +123 -0
  10. data/assets/images/background.jpg +0 -0
  11. data/assets/js/Chart.min.js +11 -0
  12. data/assets/js/dasht.js +141 -0
  13. data/assets/js/jquery-1.11.3.min.js +5 -0
  14. data/assets/js/masonry.pkgd.min.js +9 -0
  15. data/assets/js/mustache.min.js +1 -0
  16. data/assets/js/underscore-min.js +6 -0
  17. data/assets/plugins/chart.css +11 -0
  18. data/assets/plugins/chart.html +2 -0
  19. data/assets/plugins/chart.js +50 -0
  20. data/assets/plugins/map.css +17 -0
  21. data/assets/plugins/map.html +4 -0
  22. data/assets/plugins/map.js +125 -0
  23. data/assets/plugins/scroll.css +2 -0
  24. data/assets/plugins/scroll.html +2 -0
  25. data/assets/plugins/scroll.js +14 -0
  26. data/assets/plugins/value.css +18 -0
  27. data/assets/plugins/value.html +4 -0
  28. data/assets/plugins/value.js +26 -0
  29. data/examples/simple_heroku_dashboard.rb +34 -0
  30. data/lib/dasht.rb +25 -0
  31. data/lib/dasht/array_monkeypatching.rb +5 -0
  32. data/lib/dasht/base.rb +117 -0
  33. data/lib/dasht/board.rb +108 -0
  34. data/lib/dasht/collector.rb +33 -0
  35. data/lib/dasht/list.rb +93 -0
  36. data/lib/dasht/log_thread.rb +94 -0
  37. data/lib/dasht/metric.rb +82 -0
  38. data/lib/dasht/rack_app.rb +74 -0
  39. data/lib/dasht/reloader.rb +28 -0
  40. data/screenshot_1.png +0 -0
  41. data/test/helper.rb +34 -0
  42. data/test/test_list.rb +65 -0
  43. data/test/test_metric.rb +58 -0
  44. data/tests.rb +6 -0
  45. data/views/dashboard.erb +27 -0
  46. metadata +189 -0
@@ -0,0 +1,4 @@
1
+ {{#title}}
2
+ <div class="title">{{title}}</div>
3
+ {{/title}}
4
+ <div class="map"></div>
@@ -0,0 +1,125 @@
1
+ Dasht.map_geocoder_cache = {}
2
+
3
+ Dasht.map_plot_address = function(map, markers, geocoder, address) {
4
+ // Maybe pull the location from cache.
5
+ var location;
6
+ if (location = Dasht.map_geocoder_cache[address]) {
7
+ Dasht.map_plot_location(map, markers, address, location);
8
+ return;
9
+ }
10
+
11
+ geocoder.geocode({ "address": address }, function(results, status) {
12
+ // Don't plot if the lookup failed.
13
+ if (status != google.maps.GeocoderStatus.OK) return;
14
+ var location = results[0].geometry.location;
15
+ Dasht.map_geocoder_cache[address] = location;
16
+ Dasht.map_plot_location(map, markers, address, location);
17
+ });
18
+ }
19
+
20
+ Dasht.map_plot_ip = function(map, markers, ip) {
21
+ // Maybe pull the location from cache.
22
+ var location;
23
+ if (location = Dasht.map_geocoder_cache[ip]) {
24
+ Dasht.map_plot_location(map, markers, ip, location);
25
+ return;
26
+ }
27
+
28
+ // http://freegeoip.net/json/
29
+ jQuery.ajax({
30
+ url: 'http://104.236.251.84/json/' + ip,
31
+ type: 'POST',
32
+ dataType: 'jsonp',
33
+ success: function(response) {
34
+ var location = new google.maps.LatLng(response.latitude, response.longitude);
35
+ Dasht.map_geocoder_cache[ip] = location;
36
+ Dasht.map_plot_location(map, markers, ip, location);
37
+ },
38
+ error: function (xhr, ajaxOptions, thrownError) {
39
+ alert("Failed!");
40
+ }
41
+ });
42
+ }
43
+
44
+ Dasht.map_plot_location = function(map, markers, address_or_ip, location) {
45
+ var location_exists = _.any(_.values(markers), function(marker) {
46
+ return _.isEqual(marker.position, location);
47
+ });
48
+ if (location_exists) return;
49
+
50
+ // Drop a pin.
51
+ var marker = new google.maps.Marker({
52
+ map: map,
53
+ animation: google.maps.Animation.DROP,
54
+ position: location
55
+ });
56
+
57
+ // Keep track of markers.
58
+ markers[address_or_ip] = marker;
59
+ }
60
+
61
+ Dasht.map_init = function(el, options) {
62
+ // Initialize.
63
+ var old_data = undefined;
64
+ var styles = [
65
+ {
66
+ stylers: [
67
+ { hue: "#ffffff" },
68
+ { saturation: -100 },
69
+ { lightness: 20 },
70
+ { gamma: 0.5 }
71
+ ]
72
+ },
73
+ {
74
+ featureType: "water",
75
+ stylers: [
76
+ { hue: "#ffffff" },
77
+ { saturation: 80 },
78
+ { lightness: 100 },
79
+ { gamma: 0.43 }
80
+ ]
81
+ }
82
+ ];
83
+
84
+ var mapOptions = {
85
+ zoom: 4,
86
+ center: new google.maps.LatLng(39.8282, -98.5795),
87
+ styles: styles,
88
+ disableDefaultUI: true
89
+ };
90
+
91
+ var map_el = $(el).find(".map").get()[0];
92
+ var num_entries = options["n"] || 10;
93
+ var map = new google.maps.Map(map_el, mapOptions);
94
+ var markers = {};
95
+ var geocoder = new google.maps.Geocoder();
96
+ var ip_regex = /\d+\.\d+\.\d+\.\d+/;
97
+
98
+ Dasht.fill_tile($(el).find(".map"));
99
+
100
+ // Update values.
101
+ setTimeout(function() {
102
+ Dasht.get_value(options, function(new_data) {
103
+ new_data = new_data[0];
104
+ if (_.isEqual(old_data, new_data)) return;
105
+
106
+ // Remove old markers.
107
+ var old_markers = _.difference(_.keys(markers), new_data);
108
+ _.each(old_markers, function(address) {
109
+ markers[address].setMap(null);
110
+ delete markers[address];
111
+ });
112
+
113
+ // Plot each marker.
114
+ _.each(new_data, function(item, index) {
115
+ if (item.search(ip_regex) >= 0) {
116
+ Dasht.map_plot_ip(map, markers, item);
117
+ } else {
118
+ Dasht.map_plot_address(map, markers, geocoder, item);
119
+ }
120
+ });
121
+
122
+ old_data = new_data;
123
+ });
124
+ }, 1000);
125
+ }
@@ -0,0 +1,2 @@
1
+ @media (max-width: 640px) {
2
+ }
@@ -0,0 +1,2 @@
1
+ <div class="title">{{title}}</div>
2
+ <div class="metric" data-metric="{{metric}}" data-resolution="{{resolution}}" data-refresh="{{refresh}}" />
@@ -0,0 +1,14 @@
1
+ Dasht.scroll_init = function(el, options) {
2
+ var old_value = undefined;
3
+ var num_entries = options["n"] || 10;
4
+ var metric = $(el).find(".metric");
5
+ $(el).on('update', function(event, value) {
6
+ value = value.slice(-1 * num_entries);
7
+ if (_.isEqual(old_value, value)) return;
8
+ metric.animate({ opacity: 0.8 }, 0, function() {
9
+ metric.html(value.join("<br />"));
10
+ metric.animate({ opacity: 1.0 }, 400);
11
+ });
12
+ old_value = value;
13
+ });
14
+ }
@@ -0,0 +1,18 @@
1
+ .value-tile .value {
2
+ font-family: 'Lato', sans-serif;
3
+ color: rgba(255, 255, 255, 0.9);
4
+ margin: auto;
5
+ font-size: 80px;
6
+ font-weight: bold;
7
+ margin: 20px 20px 0px 20px;
8
+ display: flex;
9
+ align-items: center;
10
+ }
11
+
12
+ @media (max-width: 640px) {
13
+ .value-tile .value {
14
+ margin-top: 20px;
15
+ margin-bottom: 20px;
16
+ font-size: 30px;
17
+ }
18
+ }
@@ -0,0 +1,4 @@
1
+ {{#title}}
2
+ <div class="title">{{title}}</div>
3
+ {{/title}}
4
+ <div class="value fontsize-large" />
@@ -0,0 +1,26 @@
1
+ Dasht.value_update = function(el, value) {
2
+ $(el).css("opacity", 0.7);
3
+ $(el).html(value.toLocaleString());
4
+ $(el).animate({ "opacity": 1.0 });
5
+ }
6
+
7
+ Dasht.value_init = function(el, options) {
8
+ // Initialize.
9
+ var value = $(el).find(".value");
10
+ var value_el = value.get()[0];
11
+ var old_data = undefined;
12
+
13
+ // Set the value height to be tile height minus title height.
14
+ Dasht.fill_tile($(el).find(".title"), true, false);
15
+ Dasht.fill_tile(value);
16
+
17
+ // Update values.
18
+ setTimeout(function() {
19
+ Dasht.get_value(options, function(new_data) {
20
+ new_data = new_data[0];
21
+ if (_.isEqual(old_data, new_data)) return;
22
+ Dasht.value_update(value, new_data);
23
+ old_data = new_data;
24
+ });
25
+ }, 1000);
26
+ }
@@ -0,0 +1,34 @@
1
+ require 'dasht'
2
+
3
+ application = ARGV[0]
4
+
5
+ dasht do |d|
6
+ # Consume Heroku logs.
7
+ d.start "heroku logs --tail --app #{application}" do |l|
8
+ # Track some metrics.
9
+ l.count :lines, /.+/
10
+
11
+ l.count :bytes, /.+/ do |match|
12
+ match[0].length
13
+ end
14
+
15
+ l.append :visitors, /Started GET .* for (\d+\.\d+\.\d+\.\d+) at/ do |matches|
16
+ matches[1]
17
+ end
18
+ end
19
+
20
+ counter = 0
21
+ d.interval :counter do
22
+ sleep 1
23
+ counter += 1
24
+ end
25
+
26
+ # Publish a board.
27
+ d.board do |b|
28
+ b.value :counter, :title => "Counter"
29
+ b.value :lines, :title => "Number of Lines"
30
+ b.value :bytes, :title => "Number of Bytes"
31
+ b.chart :bytes, :title => "Chart of Bytes", :periods => 10
32
+ b.map :visitors, :title => "Visitors", :width => 12, :height => 9
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ require 'rack'
2
+ require 'thread'
3
+ require 'erb'
4
+ require 'json'
5
+
6
+ require 'dasht/array_monkeypatching'
7
+ require 'dasht/reloader'
8
+ require 'dasht/board'
9
+ require 'dasht/list'
10
+ require 'dasht/metric'
11
+ require 'dasht/collector'
12
+ require 'dasht/rack_app'
13
+ require 'dasht/log_thread'
14
+ require 'dasht/base'
15
+
16
+ class DashtSingleton
17
+ def self.run(&block)
18
+ @@instance ||= Dasht::Base.new
19
+ @@instance.run(&block)
20
+ end
21
+ end
22
+
23
+ def dasht(&block)
24
+ DashtSingleton.run(&block)
25
+ end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def dasht_sum
3
+ self.compact.inject(:+) || 0;
4
+ end
5
+ end
@@ -0,0 +1,117 @@
1
+ module Dasht
2
+ class Base
3
+ attr_accessor :metrics
4
+ attr_accessor :rack_app
5
+ attr_accessor :reloader
6
+ attr_accessor :boards
7
+
8
+ # Settings.
9
+ attr_accessor :port
10
+ attr_accessor :background
11
+ attr_accessor :default_resolution
12
+ attr_accessor :default_refresh
13
+ attr_accessor :default_width
14
+ attr_accessor :default_height
15
+ attr_accessor :history
16
+
17
+ def initialize
18
+ @boards = {}
19
+ @log_threads = {}
20
+ @metrics = Metrics.new(self)
21
+ @reloader = Reloader.new(self)
22
+ @rack_app = RackApp.new(self)
23
+ end
24
+
25
+ def log(s)
26
+ if s.class < Exception
27
+ print "\n#{s}\n"
28
+ print s.backtrace.join("\n")
29
+ else
30
+ print "\r#{s}\n"
31
+ end
32
+ end
33
+
34
+ ### DATA INGESTION ###
35
+
36
+ def start(command, &block)
37
+ log_thread = @log_threads[command] = LogThread.new(self, command)
38
+ yield(log_thread) if block
39
+ log_thread
40
+ end
41
+
42
+ def tail(path)
43
+ start("tail -F -n 0 \"#{path}\"")
44
+ end
45
+
46
+ def interval(metric, &block)
47
+ Thread.new do
48
+ begin
49
+ while true
50
+ value = block.call
51
+ set(metric, value, :last) if value
52
+ end
53
+ rescue => e
54
+ log e
55
+ raise e
56
+ end
57
+ end
58
+ end
59
+
60
+ ### DASHBOARD ###
61
+
62
+ def views_path
63
+ File.join(File.dirname(__FILE__), "..", "..", "views")
64
+ end
65
+
66
+ def system_plugins_path
67
+ File.join(File.dirname(__FILE__), "..", "..", "assets", "plugins")
68
+ end
69
+
70
+ def user_plugins_path
71
+ File.join(File.dirname($PROGRAM_NAME), "plugins")
72
+ end
73
+
74
+ def board(name = "default", &block)
75
+ name = name.to_s
76
+ board = @boards[name] = Board.new(self, name)
77
+ yield(board) if block
78
+ board
79
+ end
80
+
81
+ ### RUN & RELOAD ###
82
+
83
+ def run(&block)
84
+ if @already_running
85
+ begin
86
+ reload(&block)
87
+ rescue => e
88
+ log e
89
+ end
90
+ return
91
+ end
92
+
93
+ @already_running = true
94
+ @reloader.run
95
+
96
+ block.call(self)
97
+
98
+ @log_threads.values.map(&:run)
99
+ @rack_app.run(port)
100
+ end
101
+
102
+ def reload(&block)
103
+ @boards = {}
104
+ @log_threads.values.map(&:terminate)
105
+ @log_threads = {}
106
+
107
+ begin
108
+ block.call(self)
109
+ rescue => e
110
+ log e
111
+ raise e
112
+ end
113
+
114
+ @log_threads.values.map(&:run)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,108 @@
1
+ module Dasht
2
+ class Board
3
+ attr_accessor :parent
4
+ attr_accessor :name
5
+ attr_accessor :tiles
6
+ attr_accessor :background
7
+ attr_accessor :default_resolution
8
+ attr_accessor :default_refresh
9
+ attr_accessor :default_width
10
+ attr_accessor :default_height
11
+
12
+ def initialize(parent, name)
13
+ @parent = parent
14
+ @name = name
15
+ @tiles = []
16
+ end
17
+
18
+ def to_html
19
+ # Load the erb.
20
+ path = File.join(parent.views_path, "dashboard.erb")
21
+ @erb = ERB.new(IO.read(path))
22
+ @erb.result(binding)
23
+ end
24
+
25
+ def emit_plugin_css
26
+ _emit_css(parent.system_plugins_path)
27
+ end
28
+
29
+ def emit_plugin_html
30
+ _emit_html(parent.system_plugins_path)
31
+ end
32
+
33
+ def emit_plugin_js
34
+ _emit_js(parent.system_plugins_path)
35
+ end
36
+
37
+ def method_missing(method, *args, &block)
38
+ begin
39
+ metric = args.shift
40
+ options = args.pop || {}
41
+ @tiles << {
42
+ :type => method,
43
+ :metric => metric,
44
+ :resolution => self.default_resolution || parent.default_resolution || 60,
45
+ :refresh => self.default_refresh || parent.default_refresh || 5,
46
+ :width => self.default_width || parent.default_width || 3,
47
+ :height => self.default_height || parent.default_height || 3,
48
+ :extra_args => args
49
+ }.merge(options)
50
+ rescue => e
51
+ super(method, *args, &block)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def emit_tile_js
58
+ s = "<script>\n"
59
+ s += "$(function() {\n";
60
+ @tiles.map do |options|
61
+ s += "Dasht.add_tile(#{options.to_json});\n"
62
+ end
63
+ s += "});"
64
+ s += "</script>\n"
65
+ s
66
+ end
67
+
68
+ def emit_board_js
69
+ s = "<script>"
70
+ if background = self.background || parent.background
71
+ s += "$('body').css('background', #{background.to_json});\n"
72
+ end
73
+ s += "</script>\n"
74
+ s
75
+ end
76
+
77
+ def _emit_css(plugin_path)
78
+ s = ""
79
+ Dir[File.join(plugin_path, "*.css")].each do |path|
80
+ name = File.basename(path)
81
+ s += "<link rel='stylesheet' type='text/css' href='/assets/plugins/#{name}'>\n"
82
+ end
83
+ return s
84
+ end
85
+
86
+ def _emit_html(plugin_path)
87
+ s = ""
88
+ Dir[File.join(plugin_path, "*.html")].each do |path|
89
+ name = File.basename(path).gsub(".html", "")
90
+ s += "<script id='#{name}-template' type='x-tmpl-mustache'>\n"
91
+ s += "<div class='tile #{name}-tile width-{{width}} height-{{height}}'>\n"
92
+ s += IO.read(path)
93
+ s += "</div>\n"
94
+ s += "</script>\n"
95
+ end
96
+ return s
97
+ end
98
+
99
+ def _emit_js(plugin_path)
100
+ s = ""
101
+ Dir[File.join(plugin_path, "*.js")].each do |path|
102
+ name = File.basename(path)
103
+ s += "<script src='/assets/plugins/#{name}'></script>\n"
104
+ end
105
+ s
106
+ end
107
+ end
108
+ end