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.
Files changed (42) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +1 -15
  4. data/Gemfile.lock +44 -42
  5. data/README.md +123 -49
  6. data/Rakefile +16 -26
  7. data/bin/visage-app +17 -4
  8. data/features/cli.feature +10 -3
  9. data/features/json.feature +37 -0
  10. data/features/step_definitions/{visage_steps.rb → cli_steps.rb} +1 -1
  11. data/features/step_definitions/json_steps.rb +50 -8
  12. data/features/step_definitions/site_steps.rb +1 -1
  13. data/features/support/config/default/profiles.yaml +335 -0
  14. data/features/{data → support}/config/with_no_profiles/.stub +0 -0
  15. data/features/support/config/with_no_profiles/profiles.yaml +0 -0
  16. data/features/support/config/with_old_profile_yaml/profiles.yaml +116 -0
  17. data/features/support/env.rb +2 -3
  18. data/lib/visage-app.rb +35 -25
  19. data/lib/visage-app/collectd/json.rb +115 -118
  20. data/lib/visage-app/collectd/rrds.rb +25 -19
  21. data/lib/visage-app/helpers.rb +17 -0
  22. data/lib/visage-app/profile.rb +18 -25
  23. data/lib/visage-app/public/images/caution.png +0 -0
  24. data/lib/visage-app/public/images/ok.png +0 -0
  25. data/lib/visage-app/public/images/questions.png +0 -0
  26. data/lib/visage-app/public/javascripts/builder.js +607 -0
  27. data/lib/visage-app/public/javascripts/graph.js +179 -142
  28. data/lib/visage-app/public/javascripts/message.js +520 -0
  29. data/lib/visage-app/public/javascripts/mootools-core-1.4.0-full-compat.js +6285 -0
  30. data/lib/visage-app/public/javascripts/mootools-more-1.4.0.1.js +6399 -0
  31. data/lib/visage-app/public/stylesheets/message.css +61 -0
  32. data/lib/visage-app/public/stylesheets/screen.css +149 -38
  33. data/lib/visage-app/version.rb +5 -0
  34. data/lib/visage-app/views/builder.haml +38 -49
  35. data/lib/visage-app/views/builder_form.haml +14 -0
  36. data/lib/visage-app/views/layout.haml +5 -2
  37. data/lib/visage-app/views/profile.haml +44 -25
  38. data/visage-app.gemspec +29 -132
  39. metadata +93 -150
  40. data/VERSION +0 -1
  41. data/features/builder.feature +0 -16
  42. data/lib/visage-app/collectd/profile.rb +0 -36
@@ -0,0 +1,520 @@
1
+ /*
2
+ ---
3
+ description: Message Class. A much more sophisticated way to alert your users.
4
+
5
+ license: MIT-style
6
+
7
+ authors:
8
+ - Jason Beaudoin
9
+ - ColdFire Designs
10
+
11
+ requires:
12
+ - core/1.2.4: '*'
13
+ - more/1.2.4:Chain.Wait
14
+ - more/1.2.4:Element.Position
15
+ - more/1.2.4:Element.Shortcuts
16
+ - more/1.2.4:Element.Measure
17
+
18
+ provides: [Message.say, Message.tell, Message.ask, Message.waiter, Message.tip]
19
+
20
+ ...
21
+ */
22
+
23
+ var Message = new Class({
24
+ Implements: [Options, Events],
25
+ msgChain: null,
26
+ end: false,
27
+ isDisplayed: false,
28
+ windowSize: null,
29
+ pageSize: null,
30
+ page: $(document),
31
+ box: null,
32
+ boxSize: null,
33
+ scrollPos: null,
34
+ windowSize: null,
35
+ hasVerticalBar: false,
36
+ hasHorizontalBar: false,
37
+ boxPos: $empty,
38
+ tipCheck: true,
39
+ cancel: false,
40
+ fx: null,
41
+ fxOut: null,
42
+ options: {
43
+ callingElement: null,
44
+ top: false,
45
+ left: false,
46
+ centered: false,
47
+ offset: 30, // determines the padding offset from your current window.
48
+ width: 'auto',
49
+ icon: null, // your icon is expected to be 40 x 40
50
+ iconPath: 'images/icons/',
51
+ iconSize: 40,
52
+ fontSize: 14,
53
+ title: null,
54
+ message: null,
55
+ delay: 0,
56
+ autoDismiss: true,
57
+ dismissOnEvent: false,
58
+ isUrgent: false,
59
+ callback: null, // send a function to be fired on confirmation.
60
+ passEvent: null, // passing an event will make this message appear the your cursor location.
61
+ stack: true, // stack multiple messages one OVER or UNDER the other; setting to false will stack them on TOP of one another
62
+ fxTransition: null, // set your own transition.
63
+ fxDuration: 'normal', // set the transition duration
64
+ fxUrgentTransition: Fx.Transitions.Bounce.easeOut, // set your own urgent transition
65
+ fxOutTransition: null, // set the out transition
66
+ fxOutDuration: 'normal', // se the out duration
67
+ yesLink: "Yes",
68
+ noLink: "No"
69
+ },
70
+
71
+ initialize: function(options){
72
+ this.setOptions(options);
73
+ this.box = this;
74
+ if(this.options.width == 'auto') this.options.width = '250px';
75
+
76
+ if($chk(this.options.passEvent) && $defined(this.options.callingElement)) {
77
+ this.options.dismissOnEvent = true;
78
+ this.options.callingElement.addEvent('mouseout', function(){
79
+ // Only call a dismiss action if the message is already visible. Otherwise, cancel it.
80
+ if(this.isDisplayed) this.dismiss(); else this.cancel = true;
81
+ }.bind(this));
82
+ }
83
+ },
84
+
85
+ // Your standard message.
86
+ say: function(title, message, icon, isUrgent, callback){
87
+ this.setVars(title, message, icon, isUrgent, callback);// Supporting the passing of vars.
88
+ this.box = this.createBox();
89
+ /* We must instantiate a new instance of the chain class each time the "say" method is called to overwrite the existing one,
90
+ otherwise a buggy error occurs, and bugs give me the creeps, so I don't like them. */
91
+ this.msgChain = new Chain();
92
+ this.setMsgChain();
93
+ },
94
+
95
+ // Ask the user a pondering question. This will bounce in to get their attention.
96
+ ask: function(title, message, callback, icon, isUrgent){
97
+ this.options.autoDismiss = false;
98
+ if($chk(callback)) this.options.callback = callback; // ensure that autoDismiss is set to false and callback is set.
99
+ isUrgent = $defined(isUrgent) ? isUrgent : true;
100
+ this.say(title, message, icon, isUrgent, callback);
101
+ },
102
+
103
+ // Get pushy with this tell method by making your users acknowledge your message by pressing the 'OK' link.
104
+ tell: function(title, message, icon, isUrgent){
105
+ isUrgent = $defined(isUrgent) ? isUrgent : true;
106
+ this.options.dismissOnEvent = true;
107
+ this.say(title, message, icon, isUrgent);
108
+ },
109
+
110
+ // Our waiter method will tell the user to wait. You're code will need to dismiss upon some event.
111
+ waiter: function(title, message, icon, isCentered){
112
+ if($chk(isCentered)) this.options.centered = isCentered;
113
+ this.options.autoDismiss = false;
114
+ this.options.dismissOnEvent = true;
115
+ this.options.centered = true;
116
+ this.say(title, message, icon);
117
+ },
118
+
119
+ // Our tip method will create a tip on rollover.
120
+ tip: function(title, message, icon){
121
+ this.options.autoDismiss = true;
122
+ this.options.dismissOnEvent = true;
123
+ this.say(title, message, icon);
124
+ },
125
+
126
+ setVars: function(title, message, icon, isUrgent, callback){
127
+ if($defined(title)) this.options.title = title;
128
+ if($defined(message)) this.options.message = message;
129
+ if($defined(icon)) this.options.icon = icon;
130
+ if($defined(isUrgent)) this.options.isUrgent = isUrgent;
131
+ if($defined(callback)) this.options.callback = callback;
132
+ },
133
+
134
+ // Creates the chain and sets it in motion...
135
+ setMsgChain: function(){
136
+
137
+ if(!$chk(this.fx)){
138
+ // The simple fade in and out Fx. This initializes the native chain linking option and calls the chain after each transition completes.
139
+ this.fx = new Fx.Tween(this.box, {
140
+ link: 'chain',
141
+ onComplete: function(){
142
+ if((this.options.autoDismiss && !this.options.dismissOnEvent) || (!this.isDisplayed && !$chk(this.options.callback)) ) this.msgChain.callChain();
143
+ }.bind(this),
144
+ transition: this.options.fxTransition,
145
+ duration: this.options.fxDuration
146
+ });
147
+ }
148
+
149
+ // Must set the wait time to 0 when it's urgent otherwise the message will not dismiss immediately when the user
150
+ // clicks a dismissing link.
151
+ var waitTime
152
+ if($chk(this.options.callback) || this.options.autoDismiss == false || this.options.dismissOnEvent) waitTime = 0; else waitTime = 2000 ;
153
+
154
+ // Shows the message, waits, then closes it.
155
+ this.msgChain.wait(
156
+ this.options.delay // option to delay showing the message
157
+ ).chain(
158
+ function(){
159
+ if(!this.cancel) this.showMsg(); else this.complete(); // destroys the message if it's been canceled.
160
+ this.fireEvent('onShow'); // a nifty feature that lets you know when the message is shown.
161
+ }.bind(this)
162
+ ).wait(
163
+ waitTime // the default delay before hidding the message
164
+ ).chain(
165
+ function(){
166
+ this.hideMsg();
167
+ }.bind(this)
168
+ ).callChain();
169
+ },
170
+
171
+ showMsg: function(){
172
+ this.setSizes(); // set the dimensions of the page, window, message box and scroll position.
173
+ this.setBoxPosition();
174
+
175
+ // If the vertical scroll bar is hidden, ensure that one doesn't show up during this process.
176
+ if(this.hasVerticalBar) $(document.body).setStyle('overflow', 'hidden'); // doesn't work in IE, but will not cause any ill effects.
177
+
178
+ this.box.setStyles({
179
+ 'opacity': 0,
180
+ 'top': this.boxPos.startTop,
181
+ 'left': this.boxPos.startLeft,
182
+ 'z-index': '1'
183
+ }).fade('in');
184
+
185
+ if(!this.options.isUrgent){
186
+ this.fx.start('top', this.boxPos.endTop);
187
+
188
+ // Transition using the Bounce Fx if it's urgent.
189
+ } else {
190
+
191
+ var urgentFx = new Fx.Tween(this.box, {
192
+ duration: 'long',
193
+ transition: this.options.fxUrgentTransition
194
+ });
195
+
196
+ urgentFx.start('top', this.boxPos.endTop);
197
+
198
+ }
199
+
200
+ this.isDisplayed = true; // A utility for the procedure. Stores a variable that the message is currently being displayed.
201
+ },
202
+
203
+ dismiss: function(){
204
+ this.msgChain.callChain();
205
+ },
206
+
207
+ // Determines where the message will be displayed.
208
+ setBoxPosition: function(){
209
+ this.boxPos = new Hash(); // Class positioning container.
210
+
211
+ /* Support for the top and left positioning. These variables overide other positioning settings
212
+ like centering on urgency, and event/cursor positioning. */
213
+ var usePosition = (this.options.top && this.options.left),
214
+ startTopPos,
215
+ startLeftPos,
216
+ endLeftPos,
217
+ endTopPos,
218
+ stackUp = 0,
219
+ stackDown = 0,
220
+ stackPad = 3.5,
221
+ messages,
222
+ messagesLength = 1,
223
+ heights,
224
+ mcClass = null,
225
+ tops;
226
+
227
+ if(this.options.isUrgent){ mcClass = '[class*=mcUrgent]';}
228
+ else if(this.options.top){ mcClass = '[class*=mcTop]';}
229
+ else if($defined(this.options.callingElement)){ mcClass = '[class*=mcElement]'}
230
+ else { mcClass = '[class*=mcDefault]'; }
231
+
232
+ if(this.options.stack){
233
+ messages = $$('[class*=messageClass]' + mcClass + '');
234
+ messagesInfo = messages.getCoordinates(); // I wish there was a better way to get the heights and top positions of all message elements.
235
+
236
+ var heights = new Array();
237
+ var tops = new Array();
238
+
239
+ messagesInfo.each(function(m){
240
+ heights.push(m.height);
241
+ if(m.top > 0) tops.push(m.top);
242
+ });
243
+
244
+ stackUp = this.scrollPos.y + this.windowSize.y - (heights.sum() + stackPad * messages.length);
245
+ if(stackUp >= tops.min()) stackUp = tops.min() - this.boxSize.y - stackPad; // Radical, end-user behavior support.
246
+
247
+ stackDown = heights.sum() - this.boxSize.y + (stackPad * messages.length);
248
+ if(tops.length > 0){
249
+ if(stackDown <= tops[tops.length-1] + heights[heights.length-2] + stackPad) stackDown = tops[tops.length-1] + heights[heights.length-2] + stackPad;
250
+ }
251
+ } else {
252
+ stackUp = this.scrollPos.y + this.windowSize.y - this.boxSize.y - this.options.offset;
253
+ stackDown = this.options.offset;
254
+ }
255
+
256
+ // Set the positioning. Default position is the bottom-right corner of the window (when top and left equal false).
257
+ this.options.top ? startTopPos = (this.boxSize.y * -1) : startTopPos = this.scrollPos.y + this.windowSize.y;
258
+ this.options.left ? startLeftPos = this.options.offset : startLeftPos = this.windowSize.x - this.boxSize.x - this.options.offset;
259
+ this.options.top ? endTopPos = stackDown : endTopPos = (stackUp) ;
260
+
261
+
262
+ // If there was an event that was passed, show the message at the cursor coordinates...
263
+ if(($chk(this.options.passEvent) && !this.options.isUrgent) && !usePosition){
264
+ /* Ensure that the message doesn't fall outside of the viewable area.
265
+ As the positioning of the message is determined by the cursor position,
266
+ the message box might be too large and it will fall too far to the right.
267
+ This would not be good! If that happens, we put the message box
268
+ to the left of the cursor.*/
269
+ var offsetCursor;
270
+ (this.options.passEvent.page.x + this.boxSize.x > this.windowSize.x)? offsetCursor = (this.boxSize.x * -1) - 5 : offsetCursor = 5;
271
+
272
+ this.boxPos.extend({
273
+ startTop : this.options.passEvent.page.y - this.options.offset,
274
+ startLeft : this.options.passEvent.page.x + offsetCursor,
275
+ endTop : this.options.passEvent.page.y + stackDown - (stackPad * 3)
276
+ });
277
+
278
+ /* If the message is urgent or centered, displays the message in the center of the page,
279
+ getting the users attention like a punch in the face! Like... POW! */
280
+ } else if((this.options.isUrgent && !usePosition) || this.options.centered) {
281
+ this.box.position();
282
+ this.boxPosition = this.box.getCoordinates();
283
+
284
+ if(this.options.stack && messages.length > 1){
285
+ stackDown = tops[tops.length-1] + heights[heights.length-2] + stackPad;
286
+ } else {
287
+ stackDown = this.boxPosition.top;
288
+ }
289
+
290
+ this.boxPos.extend({
291
+ startTop : this.boxPosition.top - 100,
292
+ startLeft : this.boxPosition.left,
293
+ endTop : stackDown
294
+ });
295
+
296
+ // Positions passed here...
297
+ } else {
298
+ this.boxPos.extend({
299
+ startTop : startTopPos,
300
+ startLeft : startLeftPos,
301
+ endTop : endTopPos
302
+ });
303
+ }
304
+ },
305
+
306
+ // Initialize variables that are used throughout the class
307
+ setSizes: function(){
308
+ this.boxSize = this.box.getSize(); // Size of the message itself
309
+ this.boxPosition = this.box.getCoordinates(); // Message position
310
+ this.windowSize = this.page.getSize(); // Size of the visible window
311
+ this.scrollPos = this.page.getScroll(); // The scroll position... will only have a value if the page is larger than the window.
312
+ this.pageSize = this.page.getScrollSize(); // Size of the entire page.
313
+ if(this.windowSize.y >= this.pageSize.y) this.hasVerticalBar = true || false
314
+ if(this.windowSize.x >= this.pageSize.x) this.hasHorizontalBar = true || false
315
+ },
316
+
317
+ // Creates the message elements.
318
+ createBox: function(){
319
+ var top = "",
320
+ left = "",
321
+ normal = "",
322
+ urgent = "",
323
+ mcElement = "",
324
+ newbox,
325
+ imageSize,
326
+ newContent,
327
+ newTitle,
328
+ imagesWidth,
329
+ newClear,
330
+ p,
331
+ isComment,
332
+ newMessage;
333
+
334
+ if(this.options.top){ top = " mcTop"; }
335
+ else if(this.options.isUrgent){ urgent = " mcUrgent"; }
336
+ else if($defined(this.options.callingElement)){ mcElement = " mcElement"; }
337
+ else{ normal = ' mcDefault'; }
338
+
339
+ newBox = new Element('div', {'class': 'msgBox messageClass' + top + normal + urgent + mcElement, 'styles': {'max-width':this.options.width, 'width':this.options.width}});
340
+ imageSize = 0;
341
+ if($chk(this.options.icon)) {
342
+ var newIcon = new Element('div', {'class': 'msgBoxIcon'});
343
+ var newImage = new Element('img', {
344
+ 'class': 'msgBoxImage',
345
+ 'src': this.options.iconPath + this.options.icon,
346
+ 'styles':{
347
+ 'width': this.options.iconSize,
348
+ 'height': this.options.iconSize
349
+ }
350
+ });
351
+ }
352
+
353
+ // If the title or the message vars are not set, get the content from the "rel" property of the expected passed calling element.
354
+ if(!$chk(this.options.title) || !$chk(this.options.message)) this.getContent();
355
+
356
+ newContent = new Element('div', {
357
+ 'class': 'msgBoxContent'
358
+ }).setStyle('font-size', this.options.fontSize);
359
+
360
+ newTitle = new Element('div', {
361
+ 'class': 'msgBoxTitle',
362
+ 'html': this.options.title
363
+ }).setStyle('font-size', this.options.fontSize + 4);
364
+
365
+ imageWidth = this.getCSSTotalWidth('msgBoxIcon'); // Getting the size of the icon image (width + padding);
366
+
367
+ newClear = new Element('div', {'class': 'clear'});
368
+ p = new Element('div',{
369
+ 'html': this.options.message + '<br />',
370
+ 'styles': {
371
+ 'margin': '0px' ,
372
+ 'width': this.options.width.toInt() - imageWidth // ensures that the title and content fits nicely in the message box.
373
+ }
374
+ });
375
+
376
+ // Detect if the message contains a form
377
+ isComment = this.options.message.indexOf('textarea') > -1;
378
+
379
+ // Urgent messages with an callback parametre requires a yes and a no link to dismiss the message
380
+ if($chk(this.options.callback) && !isComment) {
381
+
382
+ var yes = this.createLink(this.options.yesLink, true);
383
+ var no = this.createLink(this.options.noLink, false);
384
+
385
+ yes.inject(p);
386
+ p.appendText(' | ');
387
+ no.inject(p);
388
+
389
+ } else if(isComment){
390
+
391
+ var sendLink = this.createLink('Send', true);
392
+ var cancelLink = this.createLink('Cancel', false);
393
+
394
+ sendLink.inject(p);
395
+ p.appendText(' | ');
396
+ cancelLink.inject(p);
397
+
398
+ // Urgent messages that are for information only have an "ok" link to dismiss the message.
399
+ } else if(this.options.isUrgent || (!this.options.autoDismiss && !this.options.dismissOnEvent)){
400
+
401
+ var ok = this.createLink('Ok', false);
402
+ ok.inject(p);
403
+
404
+ }
405
+
406
+ newMessage = new Element('div', {
407
+ 'class': 'msgBoxMessage'
408
+ });
409
+
410
+ // Putting the message box together.
411
+ p.inject(newMessage);
412
+ if($chk(this.options.icon)) {
413
+ newIcon.inject(newBox);
414
+ newImage.inject(newIcon);
415
+ }
416
+ newContent.inject(newBox);
417
+ newTitle.inject(newContent);
418
+ newClear.inject(newContent);
419
+ newMessage.inject(newContent);
420
+ newBox.inject(this.page.body);
421
+
422
+ this.box = newBox;
423
+ return newBox;
424
+ },
425
+
426
+ // Creates a user response link in the message that dismisses the window (i.e.: Ok, yes, no, etc.).
427
+ createLink: function(html, callMe){
428
+ var ourLink = new Element('a', {
429
+ 'href': 'javascript:',
430
+ 'class': 'msgBoxLink',
431
+ 'html': html,
432
+ 'id': html.replace(" ", "_") + 'Link',
433
+ 'events':{
434
+ 'click': function(){
435
+ this.msgChain.callChain();
436
+ if(callMe) this.executeCallback(); // Optional callback can be executed here.
437
+ }.bind(this)
438
+ }
439
+ });
440
+ return ourLink;
441
+ },
442
+
443
+ // UTILITIES BLOCK: utilities that are used by this class.
444
+
445
+ // Gets the total size (width + padding) of a CSS class. Creates an element; injects it into the DOM; messures the element and destroys it.
446
+ // Inserting an element into the DOM is the only way to messure it.
447
+ getCSSTotalWidth: function(myClass){
448
+ var dummy = new Element('div', {'id': 'dummy', 'class': myClass});
449
+ dummy.inject($(document.body));
450
+ var size = dummy.getComputedSize();
451
+ dummy.destroy();
452
+ return size.totalWidth;
453
+ },
454
+
455
+ executeCallback: function(){
456
+ // Determine if the callback is an object, function or a string to evaluate. It is expected that the object will have a click event.
457
+ if($type(this.options.callback) == 'element') this.options.callback.fireEvent('click');
458
+ else if ($type(this.options.callback)=='function') this.options.callback.run();
459
+ else eval(this.options.callback);
460
+ },
461
+
462
+ // Tip error catching... cuz it's easy to screw this up. Nice to be told that it's messed up.
463
+ getContent: function(){
464
+ // Expecting a calling element.
465
+ var title;
466
+ var msg;
467
+ if($defined(this.options.callingElement)){
468
+ var rel = this.options.callingElement.getProperty('rel');
469
+ var arr;
470
+ if(!$chk(rel)){
471
+ arr = this.setError("Expected data in the 'rel' property of this calling element was not defined.")
472
+ title = arr[0];
473
+ msg = arr[1];
474
+ this.options.autoDismiss = false;
475
+ } else {
476
+ arr = rel.split('::');
477
+ title = arr[0];
478
+ msg = arr[1];
479
+ }
480
+ }
481
+ this.options.title = title;
482
+ this.options.message = msg;
483
+ },
484
+
485
+ setError: function(msg){
486
+ var arr = new Array();
487
+ arr.push("<span style='color:#FF0000'>Error!<\/span>");
488
+ arr.push(msg);
489
+ return arr;
490
+ },
491
+
492
+ complete: function(){
493
+ this.box.destroy(); // A James-Bond-style, self destruct feature when it's all done.
494
+ this.end = true; // Message status support (just in case you need it).
495
+ this.isDisplayed = false;
496
+ this.fireEvent('onComplete'); // If you've set an onComplete event during instantiation of the class, it will fire here.
497
+ $(document.body).setStyle('overflow', 'auto');
498
+ },
499
+
500
+ hideMsg: function(){
501
+ // Must set the overflow to hidden again here in case there is more than one message that is being shown!
502
+ if(this.hasVerticalBar) $(document.body).setStyle('overflow', 'hidden');
503
+ var position = this.box.getCoordinates(); // Get the current position (will be different than the coordinates at the start of the procedure).
504
+ this.box.fade('out');
505
+
506
+ this.fxOut = new Fx.Tween(this.box, {
507
+ transition: this.options.fxOutTransition,
508
+ duration: this.options.fxOutDuration
509
+ });
510
+
511
+ this.fxOut.addEvent('complete', function(){
512
+ this.complete(); // runs the onComplete event once the fx transition is fully complete.
513
+ }.bind(this));
514
+
515
+ var topPos;
516
+ this.options.top ? topPos = this.boxSize.y * -1 : topPos = position.top + this.boxSize.y;
517
+
518
+ this.fxOut.start('top', topPos);
519
+ }
520
+ });