mindreframer-riemann-dash 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +21 -0
- data/README.markdown +52 -0
- data/Rakefile.rb +11 -0
- data/bin/riemann-dash +7 -0
- data/example/config.rb +17 -0
- data/lib/riemann/dash.rb +5 -0
- data/lib/riemann/dash/app.rb +32 -0
- data/lib/riemann/dash/config.rb +154 -0
- data/lib/riemann/dash/controller/css.rb +5 -0
- data/lib/riemann/dash/controller/index.rb +20 -0
- data/lib/riemann/dash/public/clock.js +45 -0
- data/lib/riemann/dash/public/dash.js +287 -0
- data/lib/riemann/dash/public/format.js +24 -0
- data/lib/riemann/dash/public/jquery-1.7.2.min.js +4 -0
- data/lib/riemann/dash/public/jquery-ui-1.9.0.custom.min.js +6 -0
- data/lib/riemann/dash/public/jquery.json-2.2.min.js +31 -0
- data/lib/riemann/dash/public/jquery.quickfit.js +144 -0
- data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +26 -0
- data/lib/riemann/dash/public/keys.js +46 -0
- data/lib/riemann/dash/public/mustache.js +597 -0
- data/lib/riemann/dash/public/persistence.js +30 -0
- data/lib/riemann/dash/public/profile.js +33 -0
- data/lib/riemann/dash/public/subs.js +164 -0
- data/lib/riemann/dash/public/toastr.css +174 -0
- data/lib/riemann/dash/public/toastr.js +207 -0
- data/lib/riemann/dash/public/toolbar.js +217 -0
- data/lib/riemann/dash/public/underscore-min.js +5 -0
- data/lib/riemann/dash/public/util.js +34 -0
- data/lib/riemann/dash/public/vendor/smoothie.js +374 -0
- data/lib/riemann/dash/public/view.js +704 -0
- data/lib/riemann/dash/public/views/gauge.js +76 -0
- data/lib/riemann/dash/public/views/grid.js +279 -0
- data/lib/riemann/dash/public/views/help.js +28 -0
- data/lib/riemann/dash/public/views/timeseries.js +107 -0
- data/lib/riemann/dash/public/views/title.js +35 -0
- data/lib/riemann/dash/public/x.png +0 -0
- data/lib/riemann/dash/rack/static.rb +16 -0
- data/lib/riemann/dash/version.rb +4 -0
- data/lib/riemann/dash/views/css.scss +393 -0
- data/lib/riemann/dash/views/index.erubis +203 -0
- data/lib/riemann/dash/views/layout.erubis +21 -0
- data/riemann-dash.gemspec +28 -0
- data/sh/c +1 -0
- data/sh/env.rb +2 -0
- data/sh/test +1 -0
- data/test/config_test.rb +106 -0
- data/test/fixtures/config/basic_config.rb +2 -0
- data/test/fixtures/config/ws_config.rb +1 -0
- data/test/fixtures/ws_config/dummy_config.json +1 -0
- data/test/fixtures/ws_config/pretty_printed_config.json +6 -0
- data/test/test_helper.rb +10 -0
- 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
|
+
|