visage-app 1.0.0 → 2.0.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.
- data/.gitignore +10 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -15
- data/Gemfile.lock +44 -42
- data/README.md +123 -49
- data/Rakefile +16 -26
- data/bin/visage-app +17 -4
- data/features/cli.feature +10 -3
- data/features/json.feature +37 -0
- data/features/step_definitions/{visage_steps.rb → cli_steps.rb} +1 -1
- data/features/step_definitions/json_steps.rb +50 -8
- data/features/step_definitions/site_steps.rb +1 -1
- data/features/support/config/default/profiles.yaml +335 -0
- data/features/{data → support}/config/with_no_profiles/.stub +0 -0
- data/features/support/config/with_no_profiles/profiles.yaml +0 -0
- data/features/support/config/with_old_profile_yaml/profiles.yaml +116 -0
- data/features/support/env.rb +2 -3
- data/lib/visage-app.rb +35 -25
- data/lib/visage-app/collectd/json.rb +115 -118
- data/lib/visage-app/collectd/rrds.rb +25 -19
- data/lib/visage-app/helpers.rb +17 -0
- data/lib/visage-app/profile.rb +18 -25
- data/lib/visage-app/public/images/caution.png +0 -0
- data/lib/visage-app/public/images/ok.png +0 -0
- data/lib/visage-app/public/images/questions.png +0 -0
- data/lib/visage-app/public/javascripts/builder.js +607 -0
- data/lib/visage-app/public/javascripts/graph.js +179 -142
- data/lib/visage-app/public/javascripts/message.js +520 -0
- data/lib/visage-app/public/javascripts/mootools-core-1.4.0-full-compat.js +6285 -0
- data/lib/visage-app/public/javascripts/mootools-more-1.4.0.1.js +6399 -0
- data/lib/visage-app/public/stylesheets/message.css +61 -0
- data/lib/visage-app/public/stylesheets/screen.css +149 -38
- data/lib/visage-app/version.rb +5 -0
- data/lib/visage-app/views/builder.haml +38 -49
- data/lib/visage-app/views/builder_form.haml +14 -0
- data/lib/visage-app/views/layout.haml +5 -2
- data/lib/visage-app/views/profile.haml +44 -25
- data/visage-app.gemspec +29 -132
- metadata +93 -150
- data/VERSION +0 -1
- data/features/builder.feature +0 -16
- data/lib/visage-app/collectd/profile.rb +0 -36
@@ -121,14 +121,12 @@ function formatValue(value, options) {
|
|
121
121
|
break
|
122
122
|
}
|
123
123
|
|
124
|
-
|
125
|
-
|
126
|
-
return rounded + unit
|
124
|
+
return label.format({decimals: precision, suffix: unit})
|
127
125
|
}
|
128
126
|
|
129
127
|
function formatDate(d) {
|
130
|
-
var datetime = new Date(d
|
131
|
-
return datetime.format("%Y-%m-%d %H:%M:%S UTC%
|
128
|
+
var datetime = new Date(d)
|
129
|
+
return datetime.format("%Y-%m-%d %H:%M:%S UTC%z")
|
132
130
|
}
|
133
131
|
|
134
132
|
function formatPluginName(name) {
|
@@ -140,13 +138,13 @@ function formatPluginName(name) {
|
|
140
138
|
|
141
139
|
|
142
140
|
/*
|
143
|
-
*
|
141
|
+
* VisageBase()
|
144
142
|
*
|
145
143
|
* Base class for fetching data and setting graph options.
|
146
144
|
* Should be used by other classes to build specialised graphing behaviour.
|
147
145
|
*
|
148
146
|
*/
|
149
|
-
var
|
147
|
+
var VisageBase = new Class({
|
150
148
|
Implements: [ Options, Events ],
|
151
149
|
options: {
|
152
150
|
secureJSON: false,
|
@@ -154,30 +152,28 @@ var visageBase = new Class({
|
|
154
152
|
live: false
|
155
153
|
},
|
156
154
|
initialize: function(element, host, plugin, options) {
|
157
|
-
this.parentElement = element
|
158
|
-
this.options.host = host
|
159
|
-
this.options.plugin = plugin
|
160
|
-
this.
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
}
|
155
|
+
this.parentElement = element;
|
156
|
+
this.options.host = host;
|
157
|
+
this.options.plugin = plugin;
|
158
|
+
this.query = window.location.search.slice(1).parseQueryString();
|
159
|
+
this.options = Object.merge(this.options, this.query);
|
160
|
+
|
161
|
+
this.setOptions(options);
|
162
|
+
|
163
|
+
this.requestData = new Object();
|
164
|
+
this.requestData.start = this.options.start;
|
165
|
+
this.requestData.finish = this.options.finish;
|
169
166
|
|
170
|
-
this.requestData = data;
|
171
167
|
this.getData(); // calls graphData
|
172
168
|
},
|
173
169
|
dataURL: function() {
|
174
|
-
var url = ['data', this.options.host, this.options.plugin]
|
170
|
+
var url = ['data', this.options.host, this.options.plugin];
|
175
171
|
// if the data exists on another host (useful for embedding)
|
176
|
-
if (
|
172
|
+
if (this.options.baseurl) {
|
177
173
|
url.unshift(this.options.baseurl.replace(/\/$/, ''))
|
178
174
|
}
|
179
175
|
// for specific plugin instances
|
180
|
-
if (
|
176
|
+
if (this.options.pluginInstance) {
|
181
177
|
url.push(this.options.pluginInstance)
|
182
178
|
}
|
183
179
|
// if no url is specified
|
@@ -202,30 +198,30 @@ var visageBase = new Class({
|
|
202
198
|
|
203
199
|
this.request.send();
|
204
200
|
},
|
205
|
-
|
201
|
+
title: function() {
|
206
202
|
if ($chk(this.options.name)) {
|
207
|
-
var
|
203
|
+
var title = this.options.name
|
208
204
|
} else {
|
209
|
-
var
|
210
|
-
|
211
|
-
|
205
|
+
var title = [ formatPluginName(this.options.plugin),
|
206
|
+
'on',
|
207
|
+
this.options.host ].join(' ')
|
212
208
|
}
|
213
|
-
return
|
209
|
+
return title
|
214
210
|
},
|
215
211
|
});
|
216
212
|
|
217
213
|
|
218
214
|
/*
|
219
|
-
*
|
215
|
+
* VisageGraph()
|
220
216
|
*
|
221
217
|
* General purpose graph for rendering data from a single plugin
|
222
218
|
* with multiple plugin instances.
|
223
219
|
*
|
224
|
-
* Builds upon
|
220
|
+
* Builds upon VisageBase().
|
225
221
|
*
|
226
222
|
*/
|
227
|
-
var
|
228
|
-
Extends:
|
223
|
+
var VisageGraph = new Class({
|
224
|
+
Extends: VisageBase,
|
229
225
|
Implements: Chain,
|
230
226
|
// assemble data to graph, then draw it
|
231
227
|
graphData: function(data) {
|
@@ -266,20 +262,21 @@ var visageGraph = new Class({
|
|
266
262
|
var plugin = this.options.plugin
|
267
263
|
var data = data ? data : this.response
|
268
264
|
|
269
|
-
$each(data[host][plugin], function(instance,
|
270
|
-
$each(instance, function(metric,
|
265
|
+
$each(data[host][plugin], function(instance, instanceName) {
|
266
|
+
$each(instance, function(metric, metricName) {
|
271
267
|
var start = metric.start,
|
272
268
|
finish = metric.finish,
|
273
269
|
interval = (finish - start) / metric.data.length;
|
274
270
|
|
275
271
|
var data = metric.data.map(function(value, index) {
|
276
|
-
var x = start + index * interval,
|
272
|
+
var x = (start + index * interval) * 1000,
|
277
273
|
y = value;
|
274
|
+
|
278
275
|
return [ x, y ];
|
279
276
|
});
|
280
277
|
|
281
278
|
var set = {
|
282
|
-
name: [ host, plugin,
|
279
|
+
name: [ host, plugin, instanceName, metricName ],
|
283
280
|
data: data,
|
284
281
|
};
|
285
282
|
|
@@ -318,7 +315,7 @@ var visageGraph = new Class({
|
|
318
315
|
},
|
319
316
|
drawChart: function() {
|
320
317
|
var series = this.series,
|
321
|
-
title = this.
|
318
|
+
title = this.title(),
|
322
319
|
element = this.parentElement,
|
323
320
|
ytitle = formatPluginName(this.options.plugin),
|
324
321
|
min,
|
@@ -331,19 +328,20 @@ var visageGraph = new Class({
|
|
331
328
|
max = meta.max;
|
332
329
|
|
333
330
|
this.chart = new Highcharts.Chart({
|
331
|
+
series: series,
|
334
332
|
chart: {
|
335
|
-
renderTo:
|
336
|
-
|
337
|
-
marginRight:
|
338
|
-
marginBottom:
|
339
|
-
zoomType:
|
340
|
-
height:
|
333
|
+
renderTo: element,
|
334
|
+
type: 'line',
|
335
|
+
marginRight: 0,
|
336
|
+
marginBottom: 60,
|
337
|
+
zoomType: 'xy',
|
338
|
+
height: 300,
|
341
339
|
events: {
|
342
340
|
load: function(e) {
|
343
341
|
setInterval(function() {
|
344
342
|
if (this.options.live) {
|
345
|
-
var data = { 'start': this.lastFinish,
|
346
|
-
'finish': this.lastFinish + 10,
|
343
|
+
var data = { 'start': this.lastFinish / 1000,
|
344
|
+
'finish': this.lastFinish / 1000 + 10,
|
347
345
|
'live': true };
|
348
346
|
this.requestData = data;
|
349
347
|
this.getData()
|
@@ -353,37 +351,48 @@ var visageGraph = new Class({
|
|
353
351
|
}
|
354
352
|
},
|
355
353
|
title: {
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
354
|
+
text: title,
|
355
|
+
style: {
|
356
|
+
'fontSize': '18px',
|
357
|
+
'fontWeight': 'bold',
|
358
|
+
'color': '#333333',
|
359
|
+
'font-family': 'Bitstream Vera Sans, Helvetica Neue, sans-serif',
|
360
|
+
}
|
362
361
|
},
|
362
|
+
/*
|
363
|
+
colors: [
|
364
|
+
'#204a87', '#4e9a06', '#cc0000', '#5c3566', '#f57900', '#e9b96e', '#ad7fa8', '#888a85', '#8ae234', '#75507b', '#c17d11', '#729fcf', '#73d216', '#ef2929', '#edd400', '#8f5902', '#555753', '#fce94f', '#2e3436', '#babdb6', '#3465a4', '#a40000', '#c4a000', '#ce5c00', '#d3d7cf', '#fcaf3e', '#eeeeec',
|
365
|
+
],
|
366
|
+
*/
|
363
367
|
xAxis: {
|
364
|
-
type: 'datetime',
|
365
|
-
labels: {
|
366
|
-
y: 20,
|
367
|
-
formatter: function() {
|
368
|
-
var d = new Date(this.value * 1000)
|
369
|
-
return d.format("%H:%M")
|
370
|
-
}
|
371
|
-
},
|
372
368
|
title: {
|
373
369
|
text: null
|
370
|
+
},
|
371
|
+
lineColor: "#aaa",
|
372
|
+
tickColor: "#aaa",
|
373
|
+
type: 'datetime',
|
374
|
+
dateTimeLabelFormats: {
|
375
|
+
second: '%H:%M:%S',
|
376
|
+
minute: '%H:%M',
|
377
|
+
hour: '%H:%M',
|
378
|
+
day: '%d/%m',
|
379
|
+
week: '%d/%m',
|
380
|
+
month: '%m/%Y',
|
381
|
+
year: '%Y'
|
374
382
|
}
|
383
|
+
|
375
384
|
},
|
376
385
|
yAxis: {
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
386
|
+
title: {
|
387
|
+
text: null
|
388
|
+
},
|
389
|
+
startOnTick: false,
|
390
|
+
minPadding: 0.065,
|
391
|
+
endOnTick: false,
|
392
|
+
gridLineColor: "#dddddd",
|
393
|
+
labels: {
|
385
394
|
formatter: function() {
|
386
|
-
var precision =
|
395
|
+
var precision = 1,
|
387
396
|
value = formatValue(this.value, {
|
388
397
|
'precision': precision,
|
389
398
|
'min': min,
|
@@ -391,50 +400,55 @@ var visageGraph = new Class({
|
|
391
400
|
});
|
392
401
|
return value
|
393
402
|
}
|
394
|
-
|
403
|
+
}
|
395
404
|
},
|
396
405
|
plotOptions: {
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
406
|
+
series: {
|
407
|
+
shadow: false,
|
408
|
+
lineWidth: 1,
|
409
|
+
marker: {
|
410
|
+
enabled: false,
|
411
|
+
states: {
|
412
|
+
hover: {
|
413
|
+
enabled: true,
|
414
|
+
radius: 4,
|
415
|
+
},
|
416
|
+
},
|
417
|
+
},
|
418
|
+
states: {
|
419
|
+
hover: {
|
420
|
+
enabled: true,
|
421
|
+
lineWidth: 1,
|
422
|
+
},
|
405
423
|
}
|
406
|
-
}
|
407
424
|
}
|
408
|
-
}
|
409
425
|
},
|
410
426
|
tooltip: {
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
427
|
+
formatter: function() {
|
428
|
+
var tip;
|
429
|
+
tip = '<strong>'
|
430
|
+
tip += formatSeriesLabel(this.series.name).trim()
|
431
|
+
tip += '</strong>' + ' -> '
|
432
|
+
tip += '<span style="font-family: monospace; font-size: 14px;">'
|
433
|
+
tip += formatValue(this.y, { 'precision': 2, 'min': min, 'max': max })
|
434
|
+
tip += '<span style="font-size: 9px; color: #777">'
|
435
|
+
tip += ' (' + this.y + ')'
|
436
|
+
tip += '</span>'
|
437
|
+
tip += '</span>'
|
438
|
+
tip += '<br/>'
|
439
|
+
tip += '<span style="font-family: monospace">'
|
440
|
+
tip += formatDate(this.x)
|
441
|
+
tip += '</span>'
|
442
|
+
|
443
|
+
return tip
|
444
|
+
}
|
429
445
|
},
|
430
446
|
legend: {
|
431
|
-
layout: '
|
432
|
-
align: '
|
447
|
+
layout: 'horizontal',
|
448
|
+
align: 'center',
|
433
449
|
verticalAlign: 'top',
|
434
|
-
|
435
|
-
y: 60,
|
450
|
+
y: 275,
|
436
451
|
borderWidth: 0,
|
437
|
-
itemWidth: 186,
|
438
452
|
labelFormatter: function() {
|
439
453
|
return formatSeriesLabel(this.name)
|
440
454
|
},
|
@@ -443,13 +457,12 @@ var visageGraph = new Class({
|
|
443
457
|
color: '#333333'
|
444
458
|
},
|
445
459
|
itemHoverStyle: {
|
446
|
-
color: '#
|
460
|
+
color: '#888'
|
447
461
|
}
|
448
462
|
|
449
463
|
},
|
450
|
-
series: series,
|
451
464
|
credits: {
|
452
|
-
|
465
|
+
enabled: false
|
453
466
|
}
|
454
467
|
});
|
455
468
|
|
@@ -462,53 +475,71 @@ var visageGraph = new Class({
|
|
462
475
|
* - form
|
463
476
|
* \
|
464
477
|
* - select
|
465
|
-
*
|
466
|
-
*
|
467
|
-
*
|
468
|
-
*
|
469
|
-
* |
|
470
|
-
* - submit
|
478
|
+
* \
|
479
|
+
* - option
|
480
|
+
* |
|
481
|
+
* - option
|
471
482
|
*/
|
472
483
|
var currentDate = new Date;
|
473
484
|
var currentUnixTime = parseInt(currentDate.getTime() / 1000);
|
474
485
|
|
475
486
|
var container = $(this.parentElement);
|
476
|
-
var form = new Element('form', {
|
487
|
+
var form = this.form = new Element('form', {
|
477
488
|
'method': 'get',
|
489
|
+
'styles': {
|
490
|
+
'text-align': 'right',
|
491
|
+
},
|
478
492
|
'events': {
|
479
|
-
'submit': function(e
|
480
|
-
|
481
|
-
e.target.getElement('select').getSelected().each(function(option) {
|
482
|
-
value = parseInt(option.value.split('=')[1])
|
483
|
-
data = { 'start': value }
|
484
|
-
});
|
485
|
-
this.requestData = data;
|
486
|
-
|
493
|
+
'submit': function(e) {
|
494
|
+
this.requestData = this.form.getElement('select').getSelected()[0].value.parseQueryString()
|
487
495
|
/* Draw everything again. */
|
488
496
|
this.getData();
|
489
497
|
}.bind(this)
|
490
498
|
}
|
491
499
|
});
|
492
500
|
|
493
|
-
|
494
|
-
var
|
495
|
-
|
496
|
-
|
501
|
+
/* Select dropdown */
|
502
|
+
var select = this.select = new Element('select', {
|
503
|
+
'class': 'date timescale',
|
504
|
+
'styles': {
|
505
|
+
'margin-bottom': '3px',
|
506
|
+
'border': '1px solid #aaa',
|
507
|
+
},
|
508
|
+
'events': {
|
509
|
+
'change': function(e) {
|
510
|
+
e.target.form.fireEvent('submit', e)
|
511
|
+
}
|
512
|
+
}
|
513
|
+
});
|
514
|
+
|
515
|
+
/* Timescales available in the dropdown */
|
516
|
+
var timescales = new Hash({ '1 hour': 1,
|
517
|
+
'2 hours': 2,
|
518
|
+
'6 hours': 6,
|
519
|
+
'12 hours': 12,
|
520
|
+
'24 hours': 24,
|
521
|
+
'3 days': 72,
|
522
|
+
'7 days': 168,
|
523
|
+
'2 weeks': 336,
|
524
|
+
'1 month': 774,
|
525
|
+
'3 month': 2322,
|
526
|
+
'6 months': 4368,
|
527
|
+
'1 year': 8760,
|
528
|
+
'2 years': 17520 });
|
529
|
+
|
497
530
|
timescales.each(function(hour, label) {
|
498
531
|
var current = this.currentTimePeriod == 'last {label}'.substitute({'label': label });
|
499
|
-
var value
|
500
|
-
var html
|
532
|
+
var value = "start={start}".substitute({'start': currentUnixTime - (hour * 3600)});
|
533
|
+
var html = 'last {label}'.substitute({'label': label });
|
501
534
|
|
502
535
|
var option = new Element('option', {
|
503
|
-
html:
|
504
|
-
value:
|
505
|
-
selected: (current ? 'selected' : '')
|
536
|
+
'html': html,
|
537
|
+
'value': value,
|
538
|
+
'selected': (current ? 'selected' : ''),
|
506
539
|
});
|
507
540
|
select.grab(option)
|
508
541
|
});
|
509
542
|
|
510
|
-
var submit = new Element('input', { 'type': 'submit', 'value': 'show' });
|
511
|
-
|
512
543
|
var liveToggler = new Element('input', {
|
513
544
|
'type': 'checkbox',
|
514
545
|
'id': this.parentElement + '-live',
|
@@ -520,7 +551,7 @@ var visageGraph = new Class({
|
|
520
551
|
}.bind(this)
|
521
552
|
},
|
522
553
|
'styles': {
|
523
|
-
'margin-
|
554
|
+
'margin-right': '4px',
|
524
555
|
'cursor': 'pointer'
|
525
556
|
}
|
526
557
|
});
|
@@ -531,7 +562,7 @@ var visageGraph = new Class({
|
|
531
562
|
'styles': {
|
532
563
|
'font-family': 'sans-serif',
|
533
564
|
'font-size': '11px',
|
534
|
-
'margin-
|
565
|
+
'margin-right': '8px',
|
535
566
|
'cursor': 'pointer'
|
536
567
|
}
|
537
568
|
});
|
@@ -540,9 +571,10 @@ var visageGraph = new Class({
|
|
540
571
|
'href': this.dataURL(),
|
541
572
|
'html': 'Export data',
|
542
573
|
'styles': {
|
543
|
-
'font-family':
|
544
|
-
'font-size':
|
545
|
-
'margin-
|
574
|
+
'font-family': 'sans-serif',
|
575
|
+
'font-size': '11px',
|
576
|
+
'margin-right': '8px',
|
577
|
+
'color': '#2F5A92',
|
546
578
|
},
|
547
579
|
'events': {
|
548
580
|
'mouseover': function(e) {
|
@@ -561,16 +593,21 @@ var visageGraph = new Class({
|
|
561
593
|
}
|
562
594
|
});
|
563
595
|
|
564
|
-
form.grab(
|
565
|
-
form.grab(submit)
|
596
|
+
form.grab(exportLink)
|
566
597
|
form.grab(liveToggler)
|
567
598
|
form.grab(liveLabel)
|
568
|
-
form.grab(
|
599
|
+
form.grab(select)
|
569
600
|
container.grab(form, 'top')
|
570
601
|
},
|
602
|
+
setTimePeriodTo: function(selected) {
|
603
|
+
var option = this.select.getElements('option').filter(function(opt) {
|
604
|
+
return opt.text == selected.text
|
605
|
+
})[0];
|
571
606
|
|
607
|
+
option.set('selected', 'selected');
|
572
608
|
|
573
|
-
|
609
|
+
this.form.fireEvent('submit')
|
610
|
+
}
|
574
611
|
});
|
575
612
|
|
576
613
|
// buildEmbedder: function() {
|
@@ -610,7 +647,7 @@ var visageGraph = new Class({
|
|
610
647
|
// baseurl = "{protocol}//{host}".substitute({'host': window.location.host, 'protocol': window.location.protocol});
|
611
648
|
// code = "<script src='{baseurl}/javascripts/visage.js' type='text/javascript'></script>".substitute({'baseurl': baseurl});
|
612
649
|
// code += "<div id='graph'></div>"
|
613
|
-
// code += "<script type='text/javascript'>window.addEvent('domready', function() { var graph = new
|
650
|
+
// code += "<script type='text/javascript'>window.addEvent('domready', function() { var graph = new VisageGraph('graph', '{host}', '{plugin}', ".substitute({'host': this.options.host, 'plugin': this.options.plugin});
|
614
651
|
// code += "{"
|
615
652
|
// code += "width: 900, height: 220, gridWidth: 800, gridHeight: 200, baseurl: '{baseurl}'".substitute({'baseurl': baseurl});
|
616
653
|
// code += "}); });</script>"
|