mindreframer-riemann-dash 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +52 -0
  4. data/LICENSE +21 -0
  5. data/README.markdown +52 -0
  6. data/Rakefile.rb +11 -0
  7. data/bin/riemann-dash +7 -0
  8. data/example/config.rb +17 -0
  9. data/lib/riemann/dash.rb +5 -0
  10. data/lib/riemann/dash/app.rb +32 -0
  11. data/lib/riemann/dash/config.rb +154 -0
  12. data/lib/riemann/dash/controller/css.rb +5 -0
  13. data/lib/riemann/dash/controller/index.rb +20 -0
  14. data/lib/riemann/dash/public/clock.js +45 -0
  15. data/lib/riemann/dash/public/dash.js +287 -0
  16. data/lib/riemann/dash/public/format.js +24 -0
  17. data/lib/riemann/dash/public/jquery-1.7.2.min.js +4 -0
  18. data/lib/riemann/dash/public/jquery-ui-1.9.0.custom.min.js +6 -0
  19. data/lib/riemann/dash/public/jquery.json-2.2.min.js +31 -0
  20. data/lib/riemann/dash/public/jquery.quickfit.js +144 -0
  21. data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +26 -0
  22. data/lib/riemann/dash/public/keys.js +46 -0
  23. data/lib/riemann/dash/public/mustache.js +597 -0
  24. data/lib/riemann/dash/public/persistence.js +30 -0
  25. data/lib/riemann/dash/public/profile.js +33 -0
  26. data/lib/riemann/dash/public/subs.js +164 -0
  27. data/lib/riemann/dash/public/toastr.css +174 -0
  28. data/lib/riemann/dash/public/toastr.js +207 -0
  29. data/lib/riemann/dash/public/toolbar.js +217 -0
  30. data/lib/riemann/dash/public/underscore-min.js +5 -0
  31. data/lib/riemann/dash/public/util.js +34 -0
  32. data/lib/riemann/dash/public/vendor/smoothie.js +374 -0
  33. data/lib/riemann/dash/public/view.js +704 -0
  34. data/lib/riemann/dash/public/views/gauge.js +76 -0
  35. data/lib/riemann/dash/public/views/grid.js +279 -0
  36. data/lib/riemann/dash/public/views/help.js +28 -0
  37. data/lib/riemann/dash/public/views/timeseries.js +107 -0
  38. data/lib/riemann/dash/public/views/title.js +35 -0
  39. data/lib/riemann/dash/public/x.png +0 -0
  40. data/lib/riemann/dash/rack/static.rb +16 -0
  41. data/lib/riemann/dash/version.rb +4 -0
  42. data/lib/riemann/dash/views/css.scss +393 -0
  43. data/lib/riemann/dash/views/index.erubis +203 -0
  44. data/lib/riemann/dash/views/layout.erubis +21 -0
  45. data/riemann-dash.gemspec +28 -0
  46. data/sh/c +1 -0
  47. data/sh/env.rb +2 -0
  48. data/sh/test +1 -0
  49. data/test/config_test.rb +106 -0
  50. data/test/fixtures/config/basic_config.rb +2 -0
  51. data/test/fixtures/config/ws_config.rb +1 -0
  52. data/test/fixtures/ws_config/dummy_config.json +1 -0
  53. data/test/fixtures/ws_config/pretty_printed_config.json +6 -0
  54. data/test/test_helper.rb +10 -0
  55. metadata +202 -0
@@ -0,0 +1,76 @@
1
+ (function() {
2
+ var fitopts = {min: 6, max: 1000};
3
+
4
+ var Gauge = function(json) {
5
+ view.View.call(this, json);
6
+ this.query = json.query;
7
+ this.title = json.title;
8
+ this.clickFocusable = true;
9
+ this.el.addClass('gauge');
10
+ this.el.append(
11
+ '<div class="box">' +
12
+ '<div class="quickfit metric value">?</div>' +
13
+ '<h2 class="quickfit"></div>' +
14
+ '</div>'
15
+ );
16
+
17
+ this.box = this.el.find('.box');
18
+ this.el.find('h2').text(this.title);
19
+
20
+ if (this.query) {
21
+ var reflowed = false;
22
+ var me = this;
23
+ var value = this.el.find('.value');
24
+ this.sub = subs.subscribe(this.query, function(e) {
25
+ me.box.attr('class', 'box state ' + e.state);
26
+ value.text(format.float(e.metric));
27
+ value.attr('title', e.description);
28
+
29
+ // The first time, do a full-height reflow.
30
+ if (reflowed) {
31
+ value.quickfit(fitopts);
32
+ } else {
33
+ me.reflow();
34
+ reflowed = true;
35
+ }
36
+ });
37
+ }
38
+ }
39
+
40
+ view.inherit(view.View, Gauge);
41
+ view.Gauge = Gauge;
42
+ view.types.Gauge = Gauge;
43
+
44
+ Gauge.prototype.json = function() {
45
+ return $.extend(view.View.prototype.json.call(this), {
46
+ type: 'Gauge',
47
+ title: this.title,
48
+ query: this.query
49
+ });
50
+ }
51
+
52
+ Gauge.prototype.editForm = function() {
53
+ return Mustache.render('<label for="title">Title</label>' +
54
+ '<input type="text" name="title" value="{{title}}" /><br />' +
55
+ '<label for="query">Query</label>' +
56
+ '<input type="text" name="query" value="{{query}}" />',
57
+ this)
58
+ }
59
+
60
+ Gauge.prototype.reflow = function() {
61
+ // Size metric
62
+ var value = this.el.find('.value');
63
+ value.quickfit({min: 6, max: 1000, font_height_scale: 1});
64
+
65
+ // Size title
66
+ var title = this.el.find('h2');
67
+ title.quickfit(fitopts);
68
+ }
69
+
70
+ Gauge.prototype.delete = function() {
71
+ if (this.sub) {
72
+ subs.unsubscribe(this.sub);
73
+ }
74
+ view.View.prototype.delete.call(this);
75
+ }
76
+ })();
@@ -0,0 +1,279 @@
1
+ (function() {
2
+ var fitopts = {min: 6, max: 1000};
3
+
4
+ var Grid = function(json) {
5
+ view.View.call(this, json);
6
+ this.query = json.query;
7
+ this.title = json.title;
8
+ this.max = json.max || "all";
9
+ this.clickFocusable = true;
10
+
11
+ // Initial display
12
+ this.el.addClass('grid');
13
+ this.el.append(
14
+ '<h2></h2>' +
15
+ '<div class="container"><table></table></div>'
16
+ );
17
+ this.box = this.el.find('.box');
18
+ this.el.find('h2').text(this.title);
19
+
20
+ // State
21
+ this.hosts = [];
22
+ this.services = [];
23
+ this.events = {};
24
+ if (this.max === "service" || this.max === "host") {
25
+ this.currentMax = {};
26
+ }
27
+
28
+ // Subscribe
29
+ if (this.query) {
30
+ var me = this;
31
+ this.sub = subs.subscribe(this.query, function(e) {
32
+ me.update.call(me, e);
33
+ });
34
+ }
35
+ }
36
+
37
+ view.inherit(view.View, Grid);
38
+ view.Grid = Grid;
39
+ view.types.Grid = Grid;
40
+
41
+ Grid.prototype.json = function() {
42
+ return $.extend(view.View.prototype.json.call(this), {
43
+ type: 'Grid',
44
+ title: this.title,
45
+ query: this.query,
46
+ max: this.max,
47
+ });
48
+ }
49
+
50
+ Grid.prototype.editForm = function() {
51
+ return Mustache.render('<label for="title">Title</label>' +
52
+ '<input type="text" name="title" value="{{title}}" /><br />' +
53
+ '<label for="query">Query</label><br />' +
54
+ '<textarea name="query" class="query">{{query}}</textarea><br />' +
55
+ '<label for="max">Max</label>' +
56
+ '<input type="text" name="max" value="{{max}}" /><br />' +
57
+ '<span class="desc">"all", "host", "service", or any number.</span>',
58
+ this)
59
+ }
60
+
61
+ // Returns all events, flat.
62
+ Grid.prototype.allEvents = function() {
63
+ var events = [];
64
+ for (host in this.events) {
65
+ for (service in this.events[host]) {
66
+ events.push(this.events[host][service]);
67
+ }
68
+ }
69
+ }
70
+
71
+ // What is the maximum for this event?
72
+ Grid.prototype.eventMax = function(event) {
73
+ if (this.max === "host") {
74
+ return this.currentMax[event.host] || -1/0;
75
+ } else if (this.max === "service") {
76
+ return this.currentMax[event.service] || -1/0;
77
+ } else {
78
+ return this.currentMax || -1/0;
79
+ }
80
+ }
81
+
82
+ // Update a single jq element with information about an event.
83
+ Grid.prototype.renderElement = function(element, event) {
84
+ if (event === undefined) {
85
+ return;
86
+ }
87
+
88
+ // State
89
+ element.attr('class', "state box " + event.state);
90
+
91
+ // Metric
92
+ element.find('.metric').text(format.float(event.metric));
93
+
94
+ // Description
95
+ element.attr('title', event.state + ' at ' + event.time + "\n\n" +
96
+ event.description);
97
+
98
+ // Bar chart
99
+ if (event.metric) {
100
+ element.find('.bar').css('width',
101
+ (event.metric / this.eventMax(event) * 100) + "%");
102
+ }
103
+ }
104
+
105
+
106
+ // Render a single event if there's been no change to table structure.
107
+ Grid.prototype.partialRender = function(event) {
108
+ var table = this.el.find('table');
109
+ var hostIndex = this.hosts.indexOf(event.host);
110
+ var serviceIndex = this.services.indexOf(event.service);
111
+ var row = this.el.find('tbody tr')[hostIndex];
112
+ var td = $($(row).find('td')[serviceIndex]);
113
+
114
+ this.renderElement(td, event);
115
+ }
116
+
117
+ // Rerender the table
118
+ Grid.prototype.render = util.slur(200, function() {
119
+ var table = this.el.find('table');
120
+ table.empty();
121
+
122
+ // Header
123
+ table.append("<thead><tr><th></th></tr></thead>");
124
+ var row = table.find("thead tr");
125
+ this.services.forEach(function(service) {
126
+ var element = $('<th>');
127
+ element.text(service);
128
+ row.append(element);
129
+ });
130
+
131
+ this.hosts.forEach(function(host) {
132
+ row = $("<tr><th></th>");
133
+ table.append(row);
134
+ row.find('th').text(host);
135
+ this.services.forEach(function(service) {
136
+ var event = this.events[host][service];
137
+ var element = $('<td><span class="bar"><span class="metric"/></span></td>');
138
+ this.renderElement(element, event);
139
+ row.append(element);
140
+ }, this);
141
+ }, this);
142
+ });
143
+
144
+ // Update cached maxima with a new event. Returns true if maxima changed.
145
+ Grid.prototype.updateMax = function(event) {
146
+ if (event !== undefined &&
147
+ (event.metric === undefined ||
148
+ event.metric <= (this.eventMax(event) || -1/0))) {
149
+ // We haven't bumped our max; no change.
150
+ return false;
151
+ }
152
+
153
+ var e;
154
+ if (this.max === "all") {
155
+ this.currentMax = -1/0;
156
+ for (host in this.events) {
157
+ for (service in this.events[host]) {
158
+ e = this.events[host][service];
159
+ this.currentMax = Math.max(e.metric, this.currentMax || -1/0);
160
+ }
161
+ }
162
+ } else if (this.max === "host" ) {
163
+ this.currentMax = {};
164
+ for (host in this.events) {
165
+ for (service in this.events[host]) {
166
+ e = this.events[host][service];
167
+ this.currentMax[e.host] =
168
+ Math.max(e.metric, this.currentMax[e.host] || -1/0)
169
+ }
170
+ }
171
+ } else if (this.max === "service") {
172
+ this.currentMax = {};
173
+ for (host in this.events) {
174
+ for (service in this.events[host]) {
175
+ e = this.events[host][service];
176
+ this.currentMax[e.service] =
177
+ Math.max(e.metric, this.currentMax[e.service] || -1/0);
178
+ }
179
+ }
180
+ } else {
181
+ this.currentMax = this.max;
182
+ return false;
183
+ }
184
+
185
+ return true;
186
+ }
187
+
188
+ // Stores an event in the internal state tables. Returns true if we
189
+ // haven't seen this host/service before.
190
+ Grid.prototype.saveEvent = function(e) {
191
+ // Host list
192
+ if (this.hosts.indexOf(e.host) === -1) {
193
+ this.hosts.push(e.host);
194
+ this.hosts = _.uniq(this.hosts.sort(), true);
195
+ }
196
+
197
+ // Services list
198
+ if (this.services.indexOf(e.service) === -1) {
199
+ this.services.push(e.service);
200
+ this.services = _.uniq(this.services.sort(), true);
201
+ }
202
+
203
+ // Events map
204
+ if (this.events[e.host] === undefined) {
205
+ // New host
206
+ this.events[e.host] = {};
207
+ }
208
+ if (this.events[e.host][e.service] === undefined) {
209
+ // New event
210
+ var newEvent = true;
211
+ } else {
212
+ var newEvent = false;
213
+ }
214
+
215
+ // Store event
216
+ this.events[e.host][e.service] = e;
217
+
218
+ return newEvent;
219
+ }
220
+
221
+ // Add an event.
222
+ Grid.prototype.add = function(e) {
223
+ var newEvent = this.saveEvent(e);
224
+ var newMax = this.updateMax(e);
225
+
226
+ if (newEvent || newMax) {
227
+ this.render();
228
+ } else {
229
+ this.partialRender(e);
230
+ }
231
+ }
232
+
233
+ // Remove an event.
234
+ Grid.prototype.remove = function(e) {
235
+ delete this.events[e.host][e.service];
236
+ if (_.isEmpty(this.events[e.host])) {
237
+ delete this.events[e.host];
238
+ };
239
+
240
+ // Recompute hosts
241
+ this.hosts = _.keys(this.events).sort();
242
+
243
+ // Recompute services
244
+ var services = {};
245
+ for (var host in this.events) {
246
+ for (var service in this.events[host]) {
247
+ services[this.events[host][service].service] = true;
248
+ }
249
+ }
250
+ this.services = _.keys(services).sort();
251
+
252
+ this.updateMax();
253
+ this.render();
254
+ }
255
+
256
+ // Accept an event.
257
+ Grid.prototype.update = function(e) {
258
+ if (e.state === "expired") {
259
+ this.remove(e);
260
+ } else {
261
+ this.add(e);
262
+ }
263
+ }
264
+
265
+ Grid.prototype.reflow = function() {
266
+ // this.el.find('table').height(
267
+ // this.height() -
268
+ // this.el.find('h2').height()
269
+ // );
270
+ }
271
+
272
+ Grid.prototype.delete = function() {
273
+ if (this.sub != undefined) {
274
+ subs.unsubscribe(this.sub);
275
+ }
276
+ this.update = function() {};
277
+ view.View.prototype.delete.call(this);
278
+ }
279
+ })();
@@ -0,0 +1,28 @@
1
+ (function() {
2
+ var Help = function(json) {
3
+ view.View.call(this, json);
4
+ this.clickFocusable = true;
5
+ this.el.addClass("help");
6
+ this.el.append('<div class="box">' +
7
+ "<p>Welcome to Riemann-Dash.</p>" +
8
+ "<p>Click to select a view. Escape unfocuses. Use the arrow keys to move a view. Use Control+arrow to <i>split</i> a view in the given direction.</p>" +
9
+ "<p>To edit a view, hit e. Use enter, or click 'apply', to apply your changes. Escape cancels.</p>" +
10
+ "<p>To save your changes to the server, press s. You can refresh the page, or press r to reload.</p>" +
11
+ "<p>Make views bigger and smaller with the +/- keys. Pageup selects the parent of the current view. To delete a view, use the delete key.</p>" +
12
+ "<p>Switch between workspaces with alt-1, alt-2, etc.</p>" +
13
+ "<p>My sincere apologies for layout jankiness. There are a few gross bugs and incapabilities in the current hstack/vstack system; if things get really bad, you can always edit ws/config.json on the server. The control scheme will probably change; I appreciate your ideas and patches.</p>" +
14
+ '</div>'
15
+ );
16
+ }
17
+
18
+ view.inherit(view.View, Help);
19
+ view.Help = Help;
20
+ view.types.Help = Help;
21
+
22
+ Help.prototype.json = function() {
23
+ return {
24
+ type: 'Help',
25
+ title: this.title
26
+ };
27
+ }
28
+ })();
@@ -0,0 +1,107 @@
1
+ (function() {
2
+ var fitopts = {min: 6, max: 1000};
3
+
4
+ var TimeSeriesView = function(json) {
5
+ view.View.call(this, json);
6
+ this.query = json.query;
7
+ this.title = json.title;
8
+ this.delay = json.delay;
9
+ this.lineWidth = json.lineWidth;
10
+ this.strokeStyle = json.strokeStyle;
11
+ this.fillStyle = json.fillStyle;
12
+
13
+ this.clickFocusable = true;
14
+ this.el.addClass('timeseries');
15
+ this.el.append(
16
+ '<div class="box">' +
17
+ '<div class="title">' +
18
+ '<h2>' + this.title + '</h2>' +
19
+ '</div>' +
20
+ '<canvas class="timeseries"></canvas>' +
21
+ '</div>'
22
+ );
23
+
24
+ this.$canvas = this.el.find(".timeseries");
25
+ this.$titlecontainer = this.el.find("div.title");
26
+ this.$titlecontainer.css({"color": "#f2f2f2",
27
+ "text-shadow": "#262626 2px 2px 4px",
28
+ "font-size": "2em"})
29
+
30
+ this.$title = this.$titlecontainer.find("h2");
31
+ this.canvas = this.$canvas.get(0);
32
+
33
+ this.reflow();
34
+
35
+ this.smoothie = new SmoothieChart();
36
+ this.smoothie.streamTo(this.canvas, this.delay);
37
+
38
+ this.series = new TimeSeries();
39
+ this.smoothie.addTimeSeries(this.series, {lineWidth: this.lineWidth || 2,
40
+ strokeStyle: this.strokeStyle || "#FFF",
41
+ fillStyle: this.fillStyle});
42
+
43
+ if (this.query) {
44
+ var reflowed = false;
45
+ var me = this;
46
+ this.sub = subs.subscribe(this.query, function(e) {
47
+ var metric = format.float(e.metric);
48
+ me.series.append(new Date(e.time).getTime(), metric);
49
+ _.delay(function() {
50
+ if (me.$title) {
51
+ me.$title.text(me.title + ": " + metric);
52
+ }
53
+ }, +me.delay)
54
+
55
+ });
56
+ }
57
+ }
58
+
59
+ view.inherit(view.View, TimeSeriesView);
60
+ view.TimeSeries = TimeSeriesView;
61
+ view.types.TimeSeries = TimeSeriesView;
62
+
63
+ TimeSeriesView.prototype.json = function() {
64
+ return $.extend(view.View.prototype.json.call(this), {
65
+ type: 'TimeSeries',
66
+ title: this.title,
67
+ delay: this.delay,
68
+ query: this.query,
69
+ strokeStyle: this.strokeStyle,
70
+ lineWidth: this.lineWidth,
71
+ fillStyle: this.fillStyle
72
+ });
73
+ }
74
+
75
+ TimeSeriesView.prototype.editForm = function() {
76
+ return Mustache.render('<label for="title">Title</label>' +
77
+ '<input type="text" name="title" value="{{title}}" /><br />' +
78
+ '<label for="query">Query</label>' +
79
+ '<input type="text" name="query" value="{{query}}" /><br />' +
80
+ '<label for="strokeStyle">StrokeStyle</label>' +
81
+ '<input type="text" name="strokeStyle" value="{{strokeStyle}}" /><br />' +
82
+ '<label for="lineWidth">LineWidth</label>' +
83
+ '<input type="text" name="lineWidth" value="{{lineWidth}}" /><br />' +
84
+ '<label for="fillStyle">FillStyle</label>' +
85
+ '<input type="text" name="fillStyle" value="{{fillStyle}}" /><br />' +
86
+ '<label for="delay">Delay</label>' +
87
+ '<input type="text" name="delay" value="{{delay}}" />',
88
+ this)
89
+ }
90
+
91
+ TimeSeriesView.prototype.reflow = function() {
92
+ // Size metric
93
+ var width = this.el.width();
94
+ var height = this.el.height();
95
+ this.$canvas.attr("width", width - 10);
96
+ this.$canvas.attr("height", height - 10);
97
+
98
+ }
99
+
100
+ TimeSeriesView.prototype.delete = function() {
101
+ if (this.sub) {
102
+ subs.unsubscribe(this.sub);
103
+ }
104
+ view.View.prototype.delete.call(this);
105
+ }
106
+ })();
107
+