riemann-dash 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ // Provides persistent storage for dashboard configuration.
2
+ var persistence = (function() {
3
+ // Saves configuration to persistent store. Calls success() or error() when
4
+ // complete.
5
+ var save = function(config, success, error) {
6
+ jQuery.ajax('/ws/config', {
7
+ type: 'POST',
8
+ success: success,
9
+ error: error,
10
+ contentType: 'application/json',
11
+ data: JSON.stringify(config),
12
+ dataType: 'json'
13
+ });
14
+ };
15
+
16
+ // Returns configuration from persistent store.
17
+ var load = function(success, error) {
18
+ jQuery.ajax('/ws/config', {
19
+ type: 'GET',
20
+ success: success,
21
+ error: error,
22
+ dataType: 'json'
23
+ });
24
+ };
25
+
26
+ return {
27
+ save: save,
28
+ load: load
29
+ }
30
+ })();
@@ -0,0 +1,28 @@
1
+ var subs = (function() {
2
+ // Loads index with query, calling f with each received event.
3
+ var subscribe = function(query, f) {
4
+ var queryString = "query=" + encodeURI(query);
5
+ var uri = "ws://127.0.0.1:5556/index?subscribe=true&" + queryString;
6
+ var ws = new WebSocket(uri);
7
+ var $ws = $(ws);
8
+
9
+ $ws.bind('open', function() {
10
+ console.log("connected", query);
11
+ });
12
+
13
+ $ws.bind('close', function() {
14
+ console.log("closed", query);
15
+ });
16
+
17
+ $ws.bind('message', function(e) {
18
+ f(JSON.parse(e.originalEvent.data));
19
+ });
20
+
21
+ $(window).unload(function() { ws.close; ws = null });
22
+ return ws;
23
+ }
24
+
25
+ return {
26
+ "subscribe": subscribe
27
+ };
28
+ })();
@@ -0,0 +1,33 @@
1
+ (function() {
2
+ var fitopts = {min: 6, max: 400};
3
+
4
+ var Title = function(json) {
5
+ view.View.call(this, json);
6
+ this.title = json.title;
7
+ this.clickFocusable = true;
8
+ this.el.addClass("title");
9
+ this.el.text(this.title);
10
+ this.reflow();
11
+ }
12
+
13
+ view.inherit(view.View, Title);
14
+ view.Title = Title;
15
+ view.types.Title = Title;
16
+
17
+ Title.prototype.json = function() {
18
+ return $.extend(view.View.prototype.json.call(this), {
19
+ type: 'Title',
20
+ title: this.title
21
+ });
22
+ }
23
+
24
+ Title.prototype.editForm = function() {
25
+ return Mustache.render('<label for="title">Title</label>' +
26
+ '<input type="title" name="title" value="{{title}}" />',
27
+ this);
28
+ }
29
+
30
+ Title.prototype.reflow = function() {
31
+ this.el.quickfit(fitopts);
32
+ }
33
+ })();
@@ -0,0 +1,684 @@
1
+ var view = (function() {
2
+ var types = {};
3
+ var focused = null;
4
+ var focusOverlay = $('<div class="focusOverlay"></div>');
5
+ $('body').append(focusOverlay);
6
+
7
+ // Unfocus all views.
8
+ var unfocus = function() {
9
+ if (focused) {
10
+ focused.unfocus();
11
+ }
12
+ }
13
+
14
+ function createObject(parent) {
15
+ function TempClass() {}
16
+ TempClass.prototype = parent;
17
+ var child = new TempClass();
18
+ return child;
19
+ }
20
+
21
+ function inherit(sup, sub) {
22
+ var newSubPrototype = createObject(sup.prototype);
23
+ newSubPrototype.constructor = sub;
24
+ sub.prototype = newSubPrototype;
25
+ }
26
+
27
+ // Create *some* type of view from json
28
+ var reify = function(json) {
29
+ var t = types[json.type];
30
+ return(new t(json));
31
+ }
32
+
33
+ // Initialize keybindings
34
+ function setKeyBindings() {
35
+ var focusedBindings = {
36
+ // left
37
+ 37: function(ev) {
38
+ if (ev.ctrlKey === true) {
39
+ focused.split('HStack', -1);
40
+ } else {
41
+ focused.moveHorizontal(-1);
42
+ }
43
+ },
44
+
45
+ // up
46
+ 38: function(ev) {
47
+ if (ev.ctrlKey === true) {
48
+ focused.split('VStack', -1);
49
+ } else {
50
+ focused.moveVertical(-1);
51
+ }
52
+ },
53
+
54
+ // right
55
+ 39: function(ev) {
56
+ if (ev.ctrlKey === true) {
57
+ focused.split('HStack', 1);
58
+ } else {
59
+ focused.moveHorizontal(1);
60
+ }
61
+ },
62
+
63
+ // down
64
+ 40: function(ev) {
65
+ if (ev.ctrlKey === true) {
66
+ focused.split('VStack', 1);
67
+ } else {
68
+ focused.moveVertical(1);
69
+ }
70
+ },
71
+
72
+ 27: function() { focused.unfocus() }, // escape
73
+ 33: function() { if (focused.parent) { // pgup
74
+ focused.parent.focus();
75
+ } },
76
+ 46: function() { focused.delete() }, // delete
77
+ 69: function() { focused.edit() }, // e
78
+ 86: function() { focused.split('VStack', 1) }, // v
79
+ 87: function() { focused.split('HStack', 1) }, // h
80
+
81
+ 187: function() { focused.grow(); }, // +
82
+ 189: function() { focused.shrink(); } // -
83
+
84
+ };
85
+
86
+ var bindings = {};
87
+
88
+ // Set up focused bindings.
89
+ for (code in focusedBindings) {
90
+ (function(f) {
91
+ keys.bind(code, function(ev) {
92
+ if (focused) { f(ev) }
93
+ });
94
+ })(focusedBindings[code]);
95
+ }
96
+
97
+ // Set up unfocused bindings.
98
+ for (code in bindings) {
99
+ (function(f) {
100
+ keys.bind(code, function(ev) {
101
+ if (! focused) { f(ev) }
102
+ });
103
+ })(bindings[code]);
104
+ }
105
+ }
106
+ setKeyBindings()
107
+
108
+ // View ////////////////////////////////////////////////////////////////////
109
+
110
+ var View = function(json) {
111
+ this.type = json.type;
112
+ this.el = $('<div class="view">');
113
+ this.weight = json.weight || 1;
114
+ // this.el.css('background', 'rgb(' +
115
+ // Math.floor(Math.random() * 256) + ',' +
116
+ // Math.floor(Math.random() * 256) + ',' +
117
+ // Math.floor(Math.random() * 256) + ')');
118
+
119
+ var self = this;
120
+ this.clickFocusable = true;
121
+ this.el.click(function() {
122
+ if (self.clickFocusable) {
123
+ self.focus();
124
+ }
125
+ });
126
+ };
127
+ types.View = View;
128
+
129
+ View.prototype.width = function(w) {
130
+ if (w !== undefined) {
131
+ return this.el.width(w);
132
+ } else {
133
+ return this.el.width();
134
+ }
135
+ };
136
+
137
+ View.prototype.height = function(h) {
138
+ if (h !== undefined) {
139
+ return this.el.height(h);
140
+ } else {
141
+ return this.el.height();
142
+ }
143
+ };
144
+
145
+ View.prototype.top = function(t) {
146
+ if(t !== undefined) {
147
+ return this.el.css("top", t);
148
+ } else {
149
+ return this.el.css("top");
150
+ }
151
+ };
152
+
153
+ View.prototype.left = function(l) {
154
+ if (l !== undefined) {
155
+ return this.el.css("left", l);
156
+ } else {
157
+ return this.el.css("left");
158
+ }
159
+ };
160
+
161
+ View.prototype.reflow = function() {
162
+ };
163
+
164
+ View.prototype.grow = function() {
165
+ this.weight *= 2;
166
+ this.parent.reflow();
167
+ focused.refocus();
168
+ };
169
+
170
+ View.prototype.shrink = function() {
171
+ this.weight *= 0.5;
172
+ this.parent.reflow();
173
+ focused.refocus();
174
+ };
175
+
176
+ // Replace this view with a different one. Returns replacement.
177
+ View.prototype.replace = function(replacement) {
178
+ console.log("Replacing", this, "with", replacement);
179
+ console.log("Parent is", this.parent);
180
+ var p = this.parent;
181
+
182
+ if (p == null) {
183
+ throw "Sorry, can't replace top-level views.";
184
+ }
185
+
186
+ if (p.replaceChild == null) {
187
+ throw "Sorry, can't replace unless parent can replace child.";
188
+ }
189
+
190
+ p.replaceChild(this, replacement);
191
+
192
+ return replacement;
193
+ }
194
+
195
+ // Unfocus this view and delete it permanently.
196
+ View.prototype.delete = function() {
197
+ this.unfocus();
198
+ var p = this.parent;
199
+ if (p){
200
+ if (p.removeChild) {
201
+ p.removeChild(this);
202
+ }
203
+ p.reflow();
204
+ }
205
+ this.el.remove();
206
+ this.el = null;
207
+ }
208
+
209
+ // Remove us from our parent, and have them reflow.
210
+ View.prototype.removeFromParent = function() {
211
+ this.unfocus();
212
+ var oldParent = this.parent;
213
+ if (oldParent && oldParent.removeChild) {
214
+ oldParent.removeChild(this);
215
+ oldParent.reflow();
216
+ }
217
+ }
218
+
219
+ // Split in a parent stack that already exists. i is either -1 (place the
220
+ // new view before us) or +1 (place the new view after us).
221
+ View.prototype.splitParentStack = function(i) {
222
+ console.log("Split parent");
223
+ if (i === null) { i = 0 }
224
+ var index = this.parent.indexOf(this) - Math.min(i, 0);
225
+ console.log(this.parent.indexOf(this), index);
226
+ this.parent.insertChild(
227
+ index,
228
+ reify({type: 'View'})
229
+ );
230
+ this.parent.reflow();
231
+ this.refocus();
232
+ }
233
+
234
+ // Split into a stackType. i is either -1 (place the new view before us) or
235
+ // +1 (place the new view after us.
236
+ View.prototype.split = function(stackType, i) {
237
+ var parent = this.parent;
238
+ if (parent) {
239
+ if (parent.type === stackType) {
240
+ this.splitParentStack(i);
241
+ } else if (parent.replaceChild) {
242
+ // Replace self with stack
243
+ var stack = reify({
244
+ type: stackType,
245
+ weight: this.weight
246
+ });
247
+ parent.replaceChild(this, stack);
248
+
249
+ // Add self to stack
250
+ this.weight = 1;
251
+ if (i === -1) {
252
+ stack.addChild(this);
253
+ stack.addChild(new View({type: 'View'}));
254
+ } else {
255
+ stack.addChild(new View({type: 'View'}));
256
+ stack.addChild(this);
257
+ }
258
+
259
+ // Redraw
260
+ parent.reflow();
261
+ this.refocus();
262
+ } else {
263
+ console.log("Can't split: parent can't replace child.");
264
+ }
265
+ } else {
266
+ console.log("Can't split: no parent");
267
+ }
268
+ }
269
+
270
+ // Redraw the focus indicator
271
+ View.prototype.refocus = function() {
272
+ focusOverlay.width(this.el.width());
273
+ focusOverlay.height(this.el.height());
274
+ focusOverlay.css('top', this.el.offset().top);
275
+ focusOverlay.css('left', this.el.offset().left);
276
+ focusOverlay.show();
277
+ }
278
+
279
+ // Focus this view
280
+ View.prototype.focus = function() {
281
+ if (focused !== null) {
282
+ focused.unfocus();
283
+ }
284
+ this.el.addClass("focused");
285
+ this.refocus();
286
+ focused = this;
287
+ }
288
+
289
+ // Unfocus this view
290
+ View.prototype.unfocus = function() {
291
+ focusOverlay.hide();
292
+ this.el.removeClass("focused");
293
+ if (focused === this) {
294
+ focused = null;
295
+ }
296
+ }
297
+
298
+ // Returns the nearest parent hstack
299
+ View.prototype.enclosingHStack = function() {
300
+ try {
301
+ if (this.parent.isHStack) {
302
+ return {
303
+ i: this.parent.indexOf(this),
304
+ stack: this.parent
305
+ };
306
+ } else {
307
+ return this.parent.enclosingHStack();
308
+ }
309
+ } catch(e) {
310
+ return null;
311
+ }
312
+ }
313
+
314
+ // Returns the nearest parent vstack
315
+ View.prototype.enclosingVStack = function() {
316
+ try {
317
+ if (this.parent.isVStack) {
318
+ return {
319
+ i: this.parent.indexOf(this),
320
+ stack: this.parent
321
+ };
322
+ } else {
323
+ return this.parent.enclosingVStack();
324
+ }
325
+ } catch(e) {
326
+ return null;
327
+ }
328
+ }
329
+
330
+ // Move a view horizontally, by delta -1 = left, +1 = right).
331
+ View.prototype.move = function(parentFinder, delta) {
332
+ var enclosing = this[parentFinder]();
333
+
334
+ if (enclosing) {
335
+ // Get the stack we'll move in to, and our (possibly our parent's) index
336
+ // in it.
337
+ var stack = enclosing.stack;
338
+ var i = enclosing.i;
339
+ console.log("I am", this);
340
+ console.log("Enclosing is", stack, i);
341
+
342
+ var newI = i + delta;
343
+ if (newI < 0) {
344
+ console.log("Sorry, at start.");
345
+ newI = 0;
346
+ } else if (newI >= stack.children.length) {
347
+ console.log("Sorry, at end.");
348
+ newI = stack.children.length;
349
+ } else {
350
+ // What's there now?
351
+ var neighbor = stack.children[newI];
352
+ if (neighbor && neighbor.addChild) {
353
+ // We can enter our neighbor
354
+ this.removeFromParent();
355
+ neighbor.addChild(this);
356
+ neighbor.reflow();
357
+ this.focus();
358
+ return;
359
+ }
360
+ }
361
+
362
+ if (this.parent === stack &&
363
+ stack.children.length === 1) {
364
+ // An special case: we can't leave our parent and then re-enter it,
365
+ // because removeFromParent() would *destroy* our parent after we
366
+ // left. Nothing *needs* to happen, so we return immediately.
367
+ return;
368
+ }
369
+
370
+ // We're moving to a new position inside the enclosing stack.
371
+ this.removeFromParent();
372
+ stack.insertChild(newI, this);
373
+ stack.reflow();
374
+ this.focus();
375
+ } else {
376
+ console.log("Sorry, not yet");
377
+ }
378
+ }
379
+
380
+ View.prototype.moveHorizontal = function(delta) {
381
+ this.move('enclosingHStack', delta);
382
+ }
383
+
384
+ View.prototype.moveVertical = function(delta) {
385
+ this.move('enclosingVStack', delta);
386
+ }
387
+
388
+ // A jquery node inserted into the edit modal, for changing the
389
+ // properties of a view.
390
+ View.prototype.editForm = function() {
391
+ }
392
+
393
+ // Show a dialog for changing this view.
394
+ View.prototype.edit = function() {
395
+ var dialog = $('<div><h1></h1><form>' +
396
+ '<select name="type" />' +
397
+ '<div class="edit-form"></div>' +
398
+ '<button name="apply">Apply</button>' +
399
+ '</form></div>');
400
+ dialog.find('h1').text("Edit " + this.type);
401
+
402
+ // Build type selector.
403
+ var typeSelector = dialog.find('select[name=type]');
404
+ var editForm = dialog.find('.edit-form');
405
+ var type;
406
+ for (type in types) {
407
+ if (type == this.json().type) {
408
+ typeSelector.append("<option selected>" + type + "</option>");
409
+ } else {
410
+ typeSelector.append("<option>" + type + "</option>");
411
+ }
412
+ }
413
+
414
+ // The serialized representation of a view that we're editing.
415
+ // Carried between various view types; when Apply is clicked, projected
416
+ // into an actual view.
417
+ var replacementJson = this.json();
418
+
419
+ // Update the replacement structure with the current values.
420
+ var mergeCurrentValues = function() {
421
+ dialog.find('form').first().serializeArray().forEach(function(input) {
422
+ replacementJson[input.name] = input.value;
423
+ });
424
+ }
425
+
426
+ // Add the edit form itself.
427
+ editForm.append(reify(replacementJson).editForm());
428
+
429
+ // Handle type changes.
430
+ typeSelector.change(function() {
431
+ // Read fields
432
+ mergeCurrentValues();
433
+
434
+ // Replace edit form
435
+ editForm.empty();
436
+ editForm.append(reify(replacementJson).editForm());
437
+ });
438
+
439
+ // Apply button.
440
+ var me = this;
441
+ dialog.find('button[name=apply]').click(function(e) {
442
+ // Don't submit the form.
443
+ e.preventDefault();
444
+
445
+ // Read fields
446
+ mergeCurrentValues();
447
+
448
+ // Replace view.
449
+ replacement = me.replace(reify(replacementJson));
450
+ replacement.parent.reflow();
451
+ me.delete();
452
+ replacement.focus();
453
+
454
+ // Close dialog.
455
+ $.modal.close();
456
+ });
457
+
458
+ // Show dialog.
459
+ keys.disable();
460
+ dialog.modal({onClose: function() {
461
+ keys.enable();
462
+ $.modal.close();
463
+ }});
464
+ }
465
+
466
+ // Serialize this view to JSON.
467
+ View.prototype.json = function() {
468
+ return {type: 'View', weight: this.weight};
469
+ }
470
+
471
+ // Balloon /////////////////////////////////////////////////////////////////
472
+
473
+ var Balloon = function(json) {
474
+ View.call(this, json);
475
+ this.clickFocusable = false;
476
+ this.child = reify(json.child);
477
+ this.child.parent = this;
478
+ this.el.append(this.child.el);
479
+ this.parent = this.el.parent();
480
+ }
481
+ inherit(View, Balloon);
482
+ types.Balloon = Balloon;
483
+
484
+ Balloon.prototype.json = function() {
485
+ return $.extend(View.prototype.json.call(this), {
486
+ type: 'Balloon',
487
+ child: this.child.json()
488
+ });
489
+ }
490
+
491
+ Balloon.prototype.replaceChild = function(v1, v2) {
492
+ this.child.parent = null;
493
+ this.child.el.detach();
494
+
495
+ this.child = v2;
496
+ v2.parent = this;
497
+ this.el.append(this.child.el);
498
+ }
499
+
500
+ Balloon.prototype.reflow = function() {
501
+ var p = this.parent;
502
+ this.width(p.width());
503
+ this.height(p.height());
504
+ this.child.width(p.width());
505
+ this.child.height(p.height());
506
+ this.child.reflow();
507
+ }
508
+
509
+ // Fullscreen //////////////////////////////////////////////////////////////
510
+
511
+ var Fullscreen = function(json) {
512
+ Balloon.call(this, json);
513
+ this.el.detach();
514
+ this.el.css('position', 'fixed');
515
+ this.el.appendTo($('body'));
516
+ this.parent = null;
517
+ }
518
+ inherit(Balloon, Fullscreen);
519
+ types.Fullscreen = Fullscreen;
520
+
521
+ Fullscreen.prototype.json = function() {
522
+ return $.extend(
523
+ Balloon.prototype.json.call(this),
524
+ {type: 'Fullscreen'});
525
+ }
526
+
527
+ Fullscreen.prototype.reflow = function() {
528
+ var p = $(window);
529
+ this.width(p.width());
530
+ this.height(p.height());
531
+ this.child.width(p.width());
532
+ this.child.height(p.height());
533
+ this.child.reflow();
534
+ }
535
+
536
+ // Stack ///////////////////////////////////////////////////////////////////
537
+
538
+ var Stack = function(json) {
539
+ View.call(this, json);
540
+ this.clickFocusable = false;
541
+ this.children = [];
542
+ var self = this;
543
+ if (json.children !== undefined) {
544
+ json.children.map(reify).forEach(function(c) {
545
+ self.addChild(c);
546
+ });
547
+ }
548
+ };
549
+ inherit(View, Stack);
550
+
551
+ Stack.prototype.json = function() {
552
+ return $.extend(View.prototype.json.call(this), {
553
+ type: 'Stack',
554
+ children: $.map(this.children, function(x) { return x.json(); })
555
+ });
556
+ }
557
+
558
+ Stack.prototype.addChild = function(view) {
559
+ view.parent = this;
560
+ this.children.push(view);
561
+ this.el.append(view.el);
562
+ }
563
+
564
+ Stack.prototype.insertChild = function(i, view) {
565
+ view.parent = this;
566
+ this.children.splice(i, 0, view);
567
+ this.el.append(view.el);
568
+ }
569
+
570
+ // Replace v1 with v2
571
+ Stack.prototype.replaceChild = function(v1, v2) {
572
+ v1.parent = null;
573
+ v2.parent = this;
574
+ v1.el.detach();
575
+ var i = this.children.indexOf(v1);
576
+ this.children[i] = v2;
577
+ this.el.append(v2.el);
578
+ }
579
+
580
+ Stack.prototype.removeChild = function(view) {
581
+ view.parent = null;
582
+ var i = this.children.indexOf(view);
583
+ view.el.detach();
584
+ this.children.splice(i, 1);
585
+
586
+ // Delete self if empty
587
+ if (this.children.length === 0) {
588
+ this.delete();
589
+ }
590
+ }
591
+
592
+ Stack.prototype.indexOf = function(child) {
593
+ return this.children.indexOf(child);
594
+ }
595
+
596
+ Stack.prototype.delete = function() {
597
+ this.children.forEach(function(c) {
598
+ c.delete();
599
+ });
600
+ View.prototype.delete.call(this);
601
+ }
602
+
603
+ // HStack //////////////////////////////////////////////////////////////////
604
+
605
+ var HStack = function(json) {
606
+ Stack.call(this, json);
607
+ };
608
+ inherit(Stack, HStack);
609
+ types.HStack = HStack;
610
+
611
+ HStack.prototype.json = function() {
612
+ return $.extend(Stack.prototype.json.call(this), {type: 'HStack'});
613
+ }
614
+
615
+ HStack.prototype.isHStack = true;
616
+
617
+ HStack.prototype.reflow = function() {
618
+ if (this.el === null) {
619
+ // We're gone.
620
+ return;
621
+ }
622
+
623
+ var width = this.width();
624
+ var height = this.height();
625
+ var left = 0;
626
+ var weightSum = this.children.reduce(function(acc, c) {
627
+ return acc + c.weight;
628
+ }, 0);
629
+
630
+ this.children.forEach(function(c) {
631
+ c.height(height);
632
+ c.width(width * (c.weight / weightSum));
633
+ c.top(0);
634
+ c.left(left);
635
+ left = left + c.width();
636
+ c.reflow();
637
+ });
638
+ }
639
+
640
+ // VStack //////////////////////////////////////////////////////////////////
641
+
642
+ var VStack = function(json) {
643
+ Stack.call(this, json);
644
+ };
645
+ inherit(Stack, VStack);
646
+ types.VStack = VStack;
647
+
648
+ VStack.prototype.json = function() {
649
+ return $.extend(Stack.prototype.json.call(this), {type: 'VStack'});
650
+ }
651
+
652
+ VStack.prototype.isVStack = true;
653
+
654
+ VStack.prototype.reflow = function() {
655
+ if (this.el === null) {
656
+ // We're gone.
657
+ return;
658
+ }
659
+
660
+ var width = this.width();
661
+ var height = this.height();
662
+ var top = 0;
663
+ var weightSum = this.children.reduce(function(acc, c) {
664
+ return acc + c.weight;
665
+ }, 0);
666
+
667
+ this.children.forEach(function(c) {
668
+ c.width(width);
669
+ c.height(height * (c.weight / weightSum));
670
+ c.left(0);
671
+ c.top(top);
672
+ top = top + c.height();
673
+ c.reflow();
674
+ });
675
+ };
676
+
677
+ return $.extend({
678
+ types: types,
679
+ reify: reify,
680
+ inherit: inherit,
681
+ unfocus: unfocus,
682
+ focused: function() { return focused; }
683
+ }, types);
684
+ })();