root/ui/web/htdocs/js/recon.js

Revision 7e749a22954faeee34e2375c26b6f7d1243562fe, 60.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 9 years ago)

in theory this works ... needs testing, refs #229

  • Property mode set to 100644
Line 
1 //global objects to use for calling plot_ifram_data from stream
2 //in addition to setting these to a div object and a initial value for the dirty bit,
3 //you will also need to have a hidden div tag to use to insert the remote javascript
4 // calls in an iframe, for example streambox for worksheets
5 var stream_object;
6 var stream_dirty;
7 var polltime = 2000; // (ms) how often we want data from the stream
8 var timewindow = 300000; // (ms) width of the stream window
9 var recon_realtime_hostname = '';
10 var streaming = false;
11
12 //set the global streaming object to the local ReconGraph object to use,
13 // and init,update the global streaming boolean to then call this from a server
14 function plot_iframe_data(data) {
15     if(data.type != 'M') return;
16     stream_object.ReconGraphAddPoint(data.time, data.id,
17                                      data.metric_name, data.value);
18     stream_dirty = true;
19 }
20
21 function log_iframe_message(message) {
22     $(".stream-log").html(message).fadeIn('slow');
23 }
24
25 //this will copy data only, ignoring other series variables
26 function copyData(d) {
27     var res = [];
28    
29     for (var i = 0; i < d.length; ++i) {
30         var s = {
31             data: []
32         };
33         if (d[i].data) {
34             for (var j = 0; j < d[i].data.length; j++) {
35                 s.data[j] = d[i].data[j].slice();
36             }
37             for (var v in d[i]) {
38                 if (!s[v])
39                     s[v] = d[i][v];
40             }
41         }
42         else {
43             for (var j = 0; j < d[i].length; j++) {
44                 s.data[j] = d[i][j].slice();
45             }
46         }
47         res.push(s);
48     }
49     return res;
50 }
51
52 function dump(arr, level) {
53     var dumped_text = "";
54     if (!level)
55         level = 0;
56    
57     //The padding given at the beginning of the line.
58     var level_padding = "";
59     for (var j = 0; j < level + 1; j++)
60         level_padding += "    ";
61    
62     if (typeof(arr) == 'object') { //Array/Hashes/Objects
63         for (var item in arr) {
64             var value = arr[item];
65            
66             if (typeof(value) == 'object') { //If it is an array,
67                 dumped_text += level_padding + "'" + item + "' ...\n";
68                 dumped_text += dump(value, level + 1);
69             }
70             else {
71                 dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
72             }
73         }
74     }
75     else { //Stings/Chars/Numbers etc.
76         dumped_text = "===>" + arr + "<===(" + typeof(arr) + ")";
77     }
78     return dumped_text;
79 }
80
81 function rpn_magic(expr) {
82     return function(value, o) {
83         return rpn_eval(value, expr, (o != null) ? o : {});
84     };
85 }
86
87 function rpn_eval(value, expr, meta) {
88     var s = [];
89     var ops = expr.split(",");
90     s.unshift(value)
91     for (var i = 0; i < ops.length; i++) {
92         var opname = ops[i];
93         if (meta && meta[opname])
94             for (var j = 0; j < meta[opname].length; j++)
95                 ops.splice(i, (j == 0) ? 1 : 0, meta[opname][j]);
96     }
97    
98     for (var i = 0; i < ops.length; i++) {
99         op = ops[i];
100        
101         switch (op) {
102             case 'ln':
103                 s.unshift(Math.log(s.shift()));
104                 break;
105             case 'round':
106                 r = Math.pow(10, s.shift());
107                 l = s.shift();
108                 s.unshift(Math.round(r * l) / r);
109                 break;
110             case 'floor':
111                 s.unshift(Math.floor(s.shift()));
112                 break;
113             case 'ceil':
114                 s.unshift(Math.ceil(s.shift()));
115                 break;
116             case 'log':
117                 r = s.shift();
118                 l = s.shift();
119                 s.unshift(Math.log(l, r));
120                 break;
121             case 'e':
122                 s.unshift(Math.exp(1));
123                 break;
124             case 'pi':
125                 s.unshift(Math.pi());
126                 break;
127             case '^':
128                 r = s.shift();
129                 l = s.shift();
130                 s.unshift(Math.pow(l, r));
131                 break;
132             case '-':
133                 r = s.shift();
134                 l = s.shift();
135                 s.unshift(l - r);
136                 break;
137             case '/':
138                 r = s.shift();
139                 l = s.shift();
140                 s.unshift(l / r);
141                 break;
142             case '~':
143                 s.shift();
144                 break;
145             case '.':
146                 s.unshift(s[s.shift()]);
147                 break;
148             case '+':
149                 s.unshift(s.shift() + s.shift());
150                 break;
151             case '*':
152                 s.unshift(s.shift() * s.shift());
153                 break;
154             case 'auto':
155                 var units = 1;
156                 if (meta && meta._max) {
157                     units = Math.pow(1000, Math.floor(Math.log(meta._max) / Math.log(1000)))
158                     if (units == 0)
159                         units = 1;
160                 }
161                 switch (units) {
162                     case 0.000000001:
163                         meta.suffix = 'n';
164                         break;
165                     case 0.000001:
166                         meta.suffix = 'u';
167                         break;
168                     case 0.001:
169                         meta.suffix = 'm';
170                         break;
171                     case 1000:
172                         meta.suffix = 'k';
173                         break;
174                     case 1000000:
175                         meta.suffix = 'M';
176                         break;
177                     case 1000000000:
178                         meta.suffix = 'G';
179                         break;
180                     case 1000000000000:
181                         meta.suffix = 'T';
182                         break;
183                     default:
184                         meta.suffix = null;
185                         break;
186                 }
187                 s.unshift(s.shift() / units);
188                 break;
189             case 'min':
190                 s.unshift(Math.min(s.shift(), s.shift()));
191                 break;
192             case 'max':
193                 s.unshift(Math.max(s.shift(), s.shift()));
194                 break;
195             default:
196                 if (op.match(/^-?\d+$/)) {
197                     s.unshift(op);
198                 }
199         }
200     }
201     var newvalue = s.shift();
202     return newvalue;
203 }
204
205 function my_rpn_eval(expr, meta) {
206     var s = [];
207     var ops = expr.split(",");
208     for (var i = 0; i < ops.length; i++) {
209         var opname = ops[i];
210         if (meta && meta[opname])
211             for (var j = 0; j < meta[opname].length; j++)
212                 ops.splice(i, (j == 0) ? 1 : 0, meta[opname][j]);
213     }
214    
215     for (var i = 0; i < ops.length; i++) {
216         op = ops[i];
217        
218         switch (op) {
219             case 'ln':
220                 s.unshift(Math.log(parseFloat(s.shift())));
221                 break;
222             case 'round':
223                 r = Math.pow(10, parseFloat(s.shift()));
224                 l = parseFloat(s.shift());
225                 s.unshift(Math.round(r * l) / r);
226                 break;
227             case 'floor':
228                 s.unshift(Math.floor(parseFloat(s.shift())));
229                 break;
230             case 'ceil':
231                 s.unshift(Math.ceil(parseFloat(s.shift())));
232                 break;
233             case 'log':
234                 r = parseFloat(s.shift());
235                 l = parseFloat(s.shift());
236                 s.unshift(Math.log(l, r));
237                 break;
238             case 'e':
239                 s.unshift(Math.exp(1));
240                 break;
241             case 'pi':
242                 s.unshift(Math.pi());
243                 break;
244             case '^':
245                 r = parseFloat(s.shift());
246                 l = parseFloat(s.shift());
247                 s.unshift(Math.pow(l, r));
248                 break;
249             case '-':
250                 r = parseFloat(s.shift());
251                 l = parseFloat(s.shift());
252                 s.unshift(l - r);
253                 break;
254             case '/':
255                 r = parseFloat(s.shift());
256                 l = parseFloat(s.shift());
257                 s.unshift(l / r);
258                 break;
259             case '+':
260                 s.unshift(parseFloat(s.shift()) + parseFloat(s.shift()));
261                 break;
262             case '*':
263                 s.unshift(parseFloat(s.shift()) * parseFloat(s.shift()));
264                 break;
265             case 'min':
266                 s.unshift(Math.min(parseFloat(s.shift()), parseFloat(s.shift())));
267                 break;
268             case 'max':
269                 s.unshift(Math.max(parseFloat(s.shift()), parseFloat(s.shift())));
270                 break;
271             default:
272                 if (op.match(/^-?\d+$/)) {
273                     s.unshift(op);
274                 }
275                 else if (op.match(/^-?\d+\.\d*$/)) {
276                     s.unshift(op);
277                 }
278         }
279     }
280     var newvalue = s.shift();
281     return newvalue;
282 }
283
284 (function($) {
285     var ReconGraph = function() {
286         var displayinfo = {
287             start: 14 * 86400,
288             end: '',
289             width: 380,
290             height: 180
291         };
292         var doptions, dplaceholder, ddata;
293        
294         function ytickformatter(ddata, axisidx) {
295             return function(val, axis) {
296                 for (var i = 0; i < ddata.length; i++) {
297                     if (ddata[i].yaxis == axisidx && ddata[i].reconnoiter_display_expression) {
298                         var meta = {
299                             _max: Math.max(Math.abs(axis.datamax), Math.abs(axis.datamin)),
300                             // for delta calc, we don't want to
301                             // lose precision
302                             floor: ['.'],
303                             ciel: ['.'],
304                             round: ['~', '.']
305                         }, pval = rpn_eval(val, ddata[i].reconnoiter_display_expression, meta);
306                         if ((val > 0 && pval < 0) || (val < 0 && pval > 0)) {
307                             // Sign inversion means we're being clever and using
308                             // the negative axis as a positive one.
309                             pval = Math.abs(pval);
310                         }
311                         if (axis.tickDecimals == Infinity) {
312                             return pval.toFixed(2) + ((meta.suffix != null) ? meta.suffix : '');
313                         }
314                         return pval.toFixed(axis.tickDecimals) + ((meta.suffix != null) ? meta.suffix : '');
315                     }
316                 }
317                 return val.toFixed(axis.tickDecimals);
318             }
319         }
320        
321         return {
322             init: function(options) {
323                 this.graphinfo = $.extend({}, displayinfo, options ||
324                 {});
325                 if (!this.graphinfo.cnt)
326                     this.graphinfo.cnt = this.graphinfo.width / 2;
327                 if (!this.attr("id"))
328                     this.attr("id", this.graphinfo.graphid);
329                 this.append($('<div/>').addClass("graphTitle").html(this.graphinfo.title || '')).append($('<div></div>').addClass("plot-area").css('width', this.width + 'px').css('height', this.height + 'px')).append($('<div></div>').addClass("plot-legend"));
330                 this.data('__recon', this);
331                 return this;
332             },
333             reset: function() {
334                 if (this.length > 1) {
335                     this.each(function(i) {
336                         $(this).ReconGraphReset();
337                     });
338                     return this;
339                 }
340                 this.graphinfo.graphid = '';
341                 if (this.flot_plot) {
342                     this.find(".graphTitle").html('');
343                     this.find("div.plot-legend").html('');
344                     this.flot_plot.setData({});
345                     this.flot_plot.setupGrid();
346                     this.flot_plot.draw();
347                 }
348                 this.data('__recon', this);
349                 return this;
350             },
351             PrepareStream: function(time_window, time_interval) {
352                 if (this.flot_plot) {
353                     doptions.time_window = time_window;
354                     doptions.time_interval = time_interval;
355                     doptions.max_time = 0;
356                     for (var i = 0; i < ddata.length; i++) {
357                         ddata[i].data = [];
358                     }
359                     this.flot_plot.setData({});
360                     this.flot_plot.setupGrid();
361                     this.flot_plot.draw();
362                 }
363                 return this;
364             },
365             AddPoint: function(xdata, uuid, metric_name, ydata) {
366            
367                 //note that lastval[0] and the xdata need to be converted from seconds to milliseconds for flot
368                 tdata = [xdata, ydata.toString()];
369                
370                 for (var i = 0; i < ddata.length; i++) {
371                     if ((ddata[i].uuid == uuid) && (ddata[i].metric_name == metric_name) && !ddata[i].hidden) {
372                    
373                         //console.log("got data from stream for ",uuid,"-",metric_name," data = ",tdata, "hidden = ", ddata[i].hidden);
374                         if ((xdata * 1000) > doptions.max_time) {
375                             doptions.max_time = xdata * 1000;
376                         }
377                         if (!doptions.min_time || ((xdata * 1000) < doptions.min_time)) {
378                             doptions.min_time = xdata * 1000;
379                         }
380                        
381                         if (ddata[i].metric_type == 'numeric') {
382                        
383                             if (ddata[i].lastval) {
384                                 slope = (tdata[1] - ddata[i].lastval[1]) / (tdata[0] - ddata[i].lastval[0]);
385                                 if (ddata[i].derive_val == 'derive') {
386                                     tdata[1] = slope;
387                                 }
388                                 else if (ddata[i].derive_val == 'counter') {
389                                     if (slope >= 0)
390                                         tdata[1] = slope;
391                                     else
392                                         tdata[1] = '';
393                                 }
394                             } //end if there was a last value available   
395                             //if this is the first live datapoint, set slope and count to null
396                             else if ((ddata[i].derive_val == 'derive') || (ddata[i].derive_val == 'counter')) {
397                                 tdata[1] = '';
398                             }
399                             if (tdata[1] != '') {
400                                 if (ddata[i].reconnoiter_source_expression) {
401                                     tdata[1] = rpn_eval(tdata[1], ddata[i].reconnoiter_source_expression, {});
402                                 }
403                             } //end if ydata was a number
404                             else { //we need to fix this so we can do numerical calcs with this val later
405                                 tdata[1] = 0;
406                             }
407                            
408                             tdata[0] *= 1000; //convert from seconds to milliseconds for flot
409                             ddata[i].data.push(tdata);
410                             if (ddata[i].lastval) {
411                                 if ((tdata[0] - ddata[i].data[0][0]) > doptions.time_window) {
412                                     ddata[i].data.shift();
413                                 }
414                             }
415                         } //end if metric was numeric
416                         //if we have a text data type
417                         else {
418                        
419                             tdata[0] *= 1000; //convert from seconds to milliseconds for flot
420                             tdata.push(tdata[1]);
421                             tdata[1] = "0";
422                            
423                             //if we had a previous value stored, only push data to plot when the value changes
424                             if (ddata[i].lastval) {
425                                 if (ddata[i].lastval[1] != tdata[2]) {
426                                     ddata[i].data.push(tdata);
427                                     if ((tdata[0] - ddata[i].data[0][0]) > doptions.time_window) {
428                                         ddata[i].data.shift();
429                                     }
430                                 }
431                                 else { //if there was no change in the value clear the metric so it doesnt display
432                                     ddata[i].data = [];
433                                 }
434                             }
435                             //otherwise we are adding a text point for the first time
436                             else {
437                                 ddata[i].data.push(tdata);
438                             }
439                            
440                         } //end if text metric type
441                         ddata[i].lastval = [xdata, ydata];
442                        
443                     } //end if the uuid and metric_name match
444                 } //end for each dataset       
445                 return this;
446             },
447             PlotPoints: function() {
448            
449                 if ((doptions.max_time >= doptions.min_time + doptions.time_window)) {
450                     doptions.xaxis.min = doptions.max_time - doptions.time_window;
451                     doptions.xaxis.max = doptions.max_time;
452                 }
453                 else {
454                     doptions.xaxis.min = doptions.min_time;
455                     doptions.xaxis.max = doptions.min_time + doptions.time_window;
456                 }
457                
458                 flotdata = copyData(ddata);
459                 this.flot_plot = $.plot(dplaceholder, flotdata, doptions);
460                 return this;
461             },
462             refresh: function(options) {
463            
464                 if (this.length > 1) {
465                     this.each(function(i) {
466                         $(this).ReconGraphRefresh(options);
467                     });
468                     return this;
469                 }
470                
471                 var o = this.data('__recon');
472                 if (o == null)
473                     return this;
474                 this.graphinfo = $.extend({}, o.graphinfo, options ||
475                 {});
476                 var url = "flot/graph/settings/" + this.graphinfo.graphid;
477                
478                 this.find(".plot-area").html('<div class="centered"><div class="loading">&nbsp;</div></div>');
479                
480                 data = {
481                     'cnt': this.graphinfo.cnt,
482                     'start': this.graphinfo.start,
483                     'end': this.graphinfo.end,
484                     'type': this.graphinfo.type
485                 };
486                
487                 $.ajaxq(this.graphinfo.graphid, {
488                     url: url,
489                     data: data,
490                     success: (function(o) {
491                         return function(r) {
492                             r = eval('(' + r + ')');
493                             o.ReconGraphPlot(r, function() {
494                                 o.ReconGraphRefresh();
495                             })
496                         }
497                     })(this)
498                 });
499                
500                 this.data('__recon', this);
501                 return this;
502             },
503             make_composite_data: function(cindex, data) {
504            
505                 expr = data[cindex].reconnoiter_source_expression;
506                
507                 //we gotta find a numeric dataset to use for going through datapoints
508                 var nindex = -1;
509                 for (i = 0; i < data.length; i++) {
510                     if (data[i].metric_type == 'numeric') {
511                         nindex = i;
512                         break;
513                     }
514                 }
515                 if (nindex == -1)
516                     return false;
517                
518                 //TODO we assume here that all numeric datasets have the same length, number of points, and time discreteness
519                 //if iths is not the case, we need to think of interpolating, like we do for stacking in flot
520                 for (var i = 0; i < data[nindex].data.length; i++) {
521                     nexpr = expr.replace(/\[(\d+)\]/g, function($1) {
522                         mat = parseInt($1.match(/\d+/));
523                         //if the expression refers to a dataset that isnt numeric, throw an error and dont plot this dataset
524                         if (data[mat] && (data[mat].metric_type == 'numeric') && data[mat].data[i][1])
525                             return data[mat].data[i][1];
526                         else
527                             return "BAD";
528                     });
529                     if (nexpr.match(/BAD/))
530                         return false;
531                     var val = my_rpn_eval(nexpr, {});
532                     data[cindex].data.push([data[nindex].data[i][0], val]);
533                 }
534                 return true;
535             },
536             plot: function(r, redraw) {
537            
538                 var title = this.ReconGraphMacro(r.title, r.data);
539                 this.find(".graphTitle").html(title);
540                 var placeholder = this.find("> div.plot-area");
541                 placeholder.bind("plotselected", (function(o) {
542                     return function(event, ranges) {
543                         var start = new Date(Math.floor(ranges.xaxis.from));
544                         var end = new Date(Math.floor(ranges.xaxis.to));
545                         o.graphinfo.start = start.toUTCString();
546                         o.graphinfo.end = end.toUTCString();
547                         if (redraw)
548                             redraw();
549                     };
550                 })(this));
551                 dplaceholder = placeholder;
552                
553                 for (var i = 0; i < r.data.length; i++) {
554                     if (r.data[i].metric_type == 'composite' && r.data[i].reconnoiter_source_expression) {
555                         if (!this.ReconGraphMakeCompositeData(i, r.data)) {
556                             modal_warning("Composite Error!", "The composite dataset '" + r.data[i].metric_name + "' refers to a non-existant or non-numeric dataset, and will not be plotted.");
557                         }
558                     }
559                    
560                     if (r.data[i].reconnoiter_display_expression)
561                         r.data[i].dataManip = rpn_magic(r.data[i].reconnoiter_display_expression);
562                 }
563                
564                 ddata = r.data;
565                
566                 if (!r.options.grid)
567                     r.options.grid = {};
568                 r.options.grid.hoverable = true;
569                 $("div.tooltip").remove();
570                 r.options.legend.container = this.find("div.plot-legend");
571                 r.options.grid.autoHighlight = false;
572                 r.options.grid.mouseActiveRadius = 4;
573                 r.options.grid.hoverXOnly = true;
574                 if (!r.options.points)
575                     r.options.points = {};
576                 r.options.points.radius = 2;
577                 if (!r.options.yaxis)
578                     r.options.yaxis = {};
579                 r.options.yaxis.tickFormatter = ytickformatter(ddata, 1);
580                 if (!r.options.y2axis)
581                     r.options.y2axis = {};
582                 r.options.y2axis.tickFormatter = ytickformatter(ddata, 2);
583                 r.options.xaxis.localtime = true;
584                
585                 r.options.stackSets = this.graphinfo.stacks;
586                
587                 doptions = r.options;
588                
589                 var plot = this.flot_plot = $.plot(placeholder, r.data, r.options);
590                 var hoverings = [];
591                 placeholder.bind("plothover", function(event, pos, items) {
592                     var opacity = 0.7;
593                     for (var h = 0; h < hoverings.length; h++)
594                         plot.unhighlight(hoverings[h].series, hoverings[h].datapoint);
595                     hoverings = [];
596                     if (items && items.length) {
597                         // Emulate opacity on white
598                         if (!$("div.tooltip")[0]) {
599                             $('<div class="tooltip"><div class="wrap"></div></div>').appendTo($('body'));
600                             $("div.tooltip .wrap").bind('mousemove', function() {
601                                 plot.getEventHolder().trigger('mousemove');
602                             });
603                         }
604                         var tt = $("div.tooltip");
605                         tt.css({
606                             width: 'auto',
607                             position: 'absolute',
608                             'z-index': 4000
609                         });
610                         var ttw = $("div.tooltip .wrap");
611                         ttw.empty();
612                         ttw.append('<div class="point-down"></div>');
613                        
614                         items.sort(function(a, b) {
615                             return a.pageY - b.pageY;
616                         });
617                         var topitem = items[0];
618                         for (var i = 0; i < items.length; i++) {
619                             if (items[i].pageY < topitem.pageY)
620                                 topitem = items[i];
621                             var rgb = (items[i].series.color.match(/\((.+)\)/))[1].split(',');
622                             rgb = rgb.map(function(a) {
623                                 return Math.round(255 - (255 - a) * 1);
624                             });
625                             var soft = 'rgba(' + rgb.join(',') + ',' + opacity + ')';
626                             var val = items[i].datapoint[1];
627                             if (items[i].series.dataManip) {
628                                 var meta = {
629                                     _max: val
630                                 };
631                                 val = items[i].series.dataManip(val, meta);
632                                 if (meta.suffix)
633                                     val = val + meta.suffix;
634                             }
635                            
636                             // I want Y of YUV... for text color selection
637                             // if Y [0,255], if high (>255 * (1-opacity)) I want black, else white
638                             var Y = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
639                             var ttp = $('<div class="tip"><div/>').html((items[i].datapoint[2] ? items[i].datapoint[2] : val) + " (" + items[i].series.label + ")").css({
640                                 color: (Y > ((1 - opacity) * 255) ? 'black' : 'white'),
641                                 backgroundColor: soft
642                             });
643                             ttp.appendTo(ttw);
644                             hoverings.push(items[i]);
645                             plot.highlight(items[i].series, items[i].datapoint);
646                         }
647                         tt.css({
648                             width: tt.width() + 10
649                         });
650                         tt.css({
651                             overflow: 'hidden',
652                             top: topitem.pageY - tt.height() - 25,
653                             left: topitem.pageX - Math.floor(tt.width() / 2) - 10,
654                             display: 'block'
655                         });
656                         return true;
657                     }
658                     $("div.tooltip").remove();
659                     return false;
660                 });
661             },
662             macro: function(str, data) {
663                 if (str == null)
664                     return str;
665                 var newstr = str.replace(/%\{[^\}]+\}/g, function(match) {
666                     var matches = match.match(/^%{([^:]+):?(.*)\}$/);
667                     for (var i = 0; i < data.length; i++) {
668                         if (data[i].dataname == matches[1]) {
669                             if (matches[2] == "" || matches[2] == "description" || matches[2] == "last_description")
670                                 return data[i].data[data[i].data.length - 1][2];
671                             else if (matches[2] == "value" || matches[2] == "last_value")
672                                 return data[i].data[data[i].data.length - 1][1];
673                             else if (matches[2] == "first_description")
674                                 return data[i].data[0][2];
675                             else if (matches[2] == "first_value")
676                                 return data[i].data[0][1];
677                             return "[unknown: " + matches[2] + "]";
678                         }
679                     }
680                     return "[unknown: " + matches[1] + "]";
681                 });
682                 return newstr;
683             }
684         };
685     }();
686     $.fn.extend({
687         ReconGraph: ReconGraph.init,
688         ReconGraphRefresh: ReconGraph.refresh,
689         ReconGraphPlot: ReconGraph.plot,
690         ReconGraphReset: ReconGraph.reset,
691         ReconGraphMacro: ReconGraph.macro,
692         ReconGraphPrepareStream: ReconGraph.PrepareStream,
693         ReconGraphAddPoint: ReconGraph.AddPoint,
694         ReconGraphPlotPoints: ReconGraph.PlotPoints,
695         ReconGraphMakeCompositeData: ReconGraph.make_composite_data
696     });
697 })(jQuery);
698
699 function perform_graph_search_add(params) {
700     perform_generic_search('json/graph/search', params, perform_graph_search_add, graphs_for_worksheet, graph_search_summary);
701 }
702
703 function perform_graph_search_edit(params) {
704     perform_generic_search('json/graph/search', params, perform_graph_search_edit, graphs_for_edit, graph_search_summary);
705 }
706
707 function perform_ws_search_edit(params) {
708     perform_generic_search('json/worksheet/search', params, perform_ws_search_edit, ws_for_edit, ws_search_summary);
709 }
710
711 function perform_datapoint_search_add(params) {
712     perform_generic_search('json/datapoint/search', params, perform_datapoint_search_add, datapoints_for_graph, datapoint_search_summary);
713 }
714
715 function ws_search_summary(r) {
716     if (r.count > 0 && r.query == '')
717         return '';
718     return r.count + ' worksheet' + (r.count == 1 ? '' : 's') + ' found for \'' + htmlentities(r.query) + '\'';
719 }
720
721 function graph_search_summary(r) {
722     if (r.count > 0 && r.query == '')
723         return '';
724     return r.count + ' graph' + (r.count == 1 ? '' : 's') + ' found for \'' + htmlentities(r.query) + '\'';
725 }
726
727 function datapoint_search_summary(r) {
728     if (r.count > 0 && r.query == '')
729         return '';
730     return 'Found ' + r.count + ' data point' + (r.count == 1 ? '' : 's') + ' found for \'' + htmlentities(r.query) + '\'';
731 }
732
733 function perform_generic_search(url, params, search_func, create_item, summary_func) {
734     $.getJSON(url, {
735         'q': params.search,
736         'o': params.offset,
737         'l': params.limit
738     }, function(r) {
739         var summary = summary_func(r);
740         if (r.error)
741             summary = 'Error: ' + htmlentities(r.error);
742         $(params.domid + " > p.search-summary").html(summary).fadeIn('fast');
743         var c = new Number(r.count);
744         var l = new Number(r.limit);
745         var o = new Number(r.offset);
746         var page = $(params.domid + " > p.paginate");
747         page.html('');
748         if (c > l) {
749             if (o > 0) {
750                 var po = Math.max(o - l, 0);
751                 $('<a/>').html((po + 1) + ' - ' + (po + l)).click(function() {
752                     search_func({
753                         'domid': params.domid,
754                         'search': params.search,
755                         'offset': po,
756                         'limit': r.limit
757                     });
758                     return false;
759                 }).appendTo(page);
760             }
761             page.append($('<span/>').html((o + 1) + '-' + (o + l)).addClass('searchselect'));
762             if (o + l < c) {
763                 var pop = o + l;
764                 $('<a/>').html((pop + 1) + '-' + (pop + l)).click(function() {
765                     search_func({
766                         'domid': params.domid,
767                         'search': params.search,
768                         'offset': pop,
769                         'limit': r.limit
770                     });
771                     return false;
772                 }).appendTo(page);
773             }
774             page.slideDown('fast');
775         }
776         else
777             page.slideUp('fast');
778         $(params.domid + " > ul.searchresults > li").remove();
779         for (var i = 0; r.results && i < r.results.length; i++) {
780             var g = r.results[i];
781             var li = $('<li/>');
782             create_item(li, g, {
783                 'domid': params.domid,
784                 'search': params.search,
785                 'offset': o,
786                 'limit': l
787             });
788             $(params.domid + " > ul.searchresults").append(li);
789         }
790     });
791 }
792
793 function graphs_for_edit(li, g, params) {
794     var edit = $('<a href="#"/>');
795     edit.html('Edit').addClass('editgraph');
796     edit.click((function(graphid) {
797         return function() {
798             set_current_graph_id(graphid);
799             return false;
800         }
801     })(g.graphid));
802     var del = $('<a href="#"/>');
803     del.html('Forget').addClass('deletegraph');
804     del.click((function(graphid, li) {
805         return function() {
806             confirm("I will forget the current graph.  Are you sure?", function() {
807                 $.getJSON('json/graph/forget/' + graphid + '/0', function(r) {
808                     if (r.refed) {
809                         var msg = "This graph is used in worksheet(s):<p>";
810                         for (var wg in r.refed) {
811                             msg += "<br>" + r.refed[wg];
812                         }
813                         msg += "<p> Forgetting it will remove it from these worksheets as well. <p>Are you sure you want to forget it?";
814                         confirm(msg, function() {
815                             $.getJSON('json/graph/forget/' + graphid + '/1', function(r) {
816                                 if (r.error) {
817                                     modal_warning("Database Error!", r.error);
818                                 }
819                                 else {
820                                     perform_graph_search_edit(params);
821                                 }
822                             });
823                         });
824                     }
825                     else {
826                         if (r.error) {
827                             modal_warning("Database Error!", r.error);
828                         }
829                         else {
830                             perform_graph_search_edit(params);
831                         }
832                     }
833                 });
834                 if (current_graph_id == graphid) {
835                     set_current_graph_id('');
836                 }
837             });
838             return false;
839         }
840     })(g.graphid, li));
841     var add = $('<a href="#"/>');
842     add.html('Stack');
843     add.click((function(graphid) {
844         return function() {
845             aggregate_graph(graphid, true);
846             return false;
847         }
848     })(g.graphid));
849    
850     var addover = $('<a href="#"/>');
851     addover.html('Overlay');
852     addover.click((function(graphid) {
853         return function() {
854             aggregate_graph(graphid, false);
855             return false;
856         }
857     })(g.graphid));
858    
859     var ul = $('<ul/>');
860     ul.append($('<li/>').html(g.last_update));
861     ul.append($('<li/>').append(edit));
862     ul.append($('<li/>').append(del));
863     ul.append($('<li/>').append(add));
864     ul.append($('<li/>').append(addover));
865     gtitle = $('<div class="graphlist-title"/>').html(g.title);
866    
867     li.append(gtitle).append(ul);
868 }
869
870 function ws_for_edit(li, ws, params) {
871     var add = $('<a href="#"/>');
872     add.html('View').addClass('addtows');
873     add.click((function(sheetid) {
874         return function() {
875             worksheet.load(sheetid);
876             return false;
877         }
878     })(ws.sheetid));
879     var ul = $('<ul/>');
880     ul.append($('<li/>').html(ws.last_update));
881     ul.append($('<li/>').append(add));
882     li.append($('<div class="worksheetlist-title"/>').html(ws.title)).append(ul);
883 }
884
885 function graphs_for_worksheet(li, g, params) {
886     var add = $('<a href="#"/>');
887     var qview = $('<a href="#"/>');
888     add.html('Add').addClass('addtows');
889     add.click((function(graphid) {
890         return function() {
891             worksheet.add_graph(graphid);
892             return false;
893         }
894     })(g.graphid));
895     qview.html('Quick View').addClass('quickviewgraph');
896     qview.click((function(graphid, gtype) {
897         return function() {
898             worksheet.zoom(graphid, gtype);
899             return false;
900         }
901     })(g.graphid, 'standard'));
902     var ul = $('<ul/>');
903     ul.append($('<li/>').html(g.last_update));
904     ul.append($('<li/>').append(add));
905     ul.append($('<li/>').append(qview));
906     li.append($('<div class="ws-add-graph-title"/>').html(g.title)).append(ul);
907 }
908
909 function datapoints_for_graph(li, ds, params) {
910     var a = $('<a href="#"/>');
911     a.html(ds.target + '`' + ds.name + '`' + ds.metric_name);
912     a.click((function(ds_c) {
913         return function() {
914             graph_add_datapoint({
915                 'id': ds_c.id,
916                 'sid': ds_c.sid,
917                 'name': ds_c.target + '`' + ds_c.metric_name,
918                 'metric_name': ds_c.metric_name,
919                 'metric_type': ds_c.metric_type,
920                 'module': ds_c.module,
921                 'target': ds_c.target,
922                 'orig_name': ds_c.name
923             });
924             return false;
925         }
926     })(ds));
927     if (ds.metric_type == 'text')
928         li.addClass('txt');
929     li.append(a);
930 }
931
932 function time_window_to_seconds(w) {
933     var p = w.match(/^(\d+)(M|h|d|w|m|y)$/);
934     if (!p)
935         return 86400 * 2;
936     switch (p[2]) {
937         case 'M':
938             return p[1] * 60;
939         case 'h':
940             return p[1] * 3600;
941         case 'd':
942             return p[1] * 86400;
943         case 'w':
944             return p[1] * 86400 * 7;
945         case 'm':
946             return p[1] * 86400 * 30;
947         case 'y':
948             return p[1] * 86400 * 365;
949     }
950     return 86400 * 2;
951 }
952
953 function get_stream_controls() {
954     play_pause = $("<span id='play_pause'>PLAY</span>");
955     stop = $("<span id='stopstream' style='display:none' >STOP</span>");
956     oslider = $("<div id='oslider' style='display:none'><div id='pollslider'></div><div id='polltime'>" + polltime + " ms</div></div>");
957     stream_controls = $("<div class='stream_controls'></div>").append(play_pause).append(stop).append(oslider);
958     return stream_controls;
959 }
960
961 //call this function when you wish to stream a graph
962 //graphid: the id, used to retrieve the graph's metric to request
963 //stream_graph: the dom element to update with the stream
964 //streambox: the hidden element to insert the iframe remote calls
965 function stream_data(graph_id, stream_graph, streambox) {
966     if (!streaming) {
967         stream_object = stream_graph;
968         stream_dirty = false;
969         stream_graph.ReconGraphPrepareStream(timewindow, polltime);
970        
971         //this should be the only place we set streaming to true
972         streaming = true;
973     }
974     //setup functionality so that every 2 seconds check if we are streaming and dirty, plot if true
975     stream_graph.everyTime(2000, function() {
976         if (!streaming) {
977             streambox.html('');
978             $(".stream-log").hide();
979             stream_graph.stopTime();
980         }
981         else {
982             if (stream_dirty) {
983                 stream_graph.ReconGraphPlotPoints();
984                 stream_dirty = false;
985             }
986         }
987     });
988    
989     $.getJSON("json/graph/info/" + graph_id, function(g) {
990    
991         sids = "";
992         var sidneed = new Object();
993        
994         //we could set a polltime for each dataset, but for now we make them the same
995         //we assume we never give datasets we dont want to graph sids, like composites
996         for (var i = 0; i < g.datapoints.length; i++) {
997             if (g.datapoints[i].id) {
998                 sidneed[g.datapoints[i].id] = polltime;
999             }
1000         }
1001         for (var id in sidneed) {
1002             sids += "/" + id + "@" + sidneed[id];
1003         }
1004         //console.log("sids request: http://" +recon_realtime_hostname+"/data"+sids);
1005         streambox.html('<iframe src="http://' + recon_realtime_hostname + '/data' + sids + '"></iframe>');
1006     });
1007 }
1008
1009 ///////////////////////////
1010 // Worksheet manipulation
1011 //////////////////////////
1012 var worksheet = (function($) {
1013     var ws_displayinfo = {
1014         start: 14 * 86400,
1015         cnt: '100',
1016         end: ''
1017     };
1018     var wsinfo = {};
1019     var locked = false;
1020     streaming = false; //precautionary...should be set to false wherever we use it when weere done
1021     var stream_graph;
1022    
1023     function ws_locked(warning) {
1024         if (locked) {
1025             modal_warning("Worksheet Locked!", warning);
1026             return locked;
1027         }
1028         else if ($(".rememberWorksheet:visible").length != 0) {
1029             modal_warning("Worksheet not saved!", "You must hit 'Remember' to continue editing.");
1030             return true;
1031         }
1032         return locked;
1033     }
1034    
1035     function make_ws_graph(g) {
1036         var o = $('<div></div>').ReconGraph(g);
1037        
1038         zb = $("<li class='zoomGraph' id='Zoom-" + g.graphid + "'><img src='images/zoom_icon.png'></li>");
1039         zb.attr("graphid", g.graphid);
1040         zb.attr("graphtype", g.type);
1041         zb.click(function() {
1042             worksheet.zoom($(this).attr("graphid"), $(this).attr("graphtype"));
1043         });
1044         mb = $("<li class='moveGraph' id='Move-" + g.graphid + "'><img src='images/drag_icon.png'></li>");
1045         rb = $("<li class='deleteWorksheetGraph' id='Remove-" + g.graphid + "'><img src='images/remove_icon.png'></li>");
1046         rb.attr("graphid", g.graphid);
1047         rb.click(function() {
1048             $.getJSON('json/worksheet/deletegraph/' + wsinfo.id + '/' + $(this).attr("graphid"), function(r) {
1049                 if (r.error) {
1050                     $("#ws-tool-error").html(r.error).fadeIn('fast');
1051                 }
1052             });
1053             $("#" + $(this).attr("graphid")).remove();
1054         });
1055        
1056         var ws_tbar = $('<div class="ws-toolbar"></div>').append("<ul/>").append(zb).append(mb).append(rb);
1057        
1058         ws_tbar.attr("style", "display:none;");
1059        
1060         o.prepend(ws_tbar);
1061         o.mouseover(function() {
1062             o.attr("style", "outline: 1px solid #DDDDDD;");
1063             ws_tbar.removeAttr("style");
1064         });
1065         o.mouseout(function() {
1066             o.removeAttr("style");
1067             ws_tbar.attr("style", "display:none;");
1068         });
1069        
1070         return o;
1071     }
1072    
1073     function render_ws_inpage(divid, id, start, end, gran) {
1074         ws_displayinfo.start = start;
1075         ws_displayinfo.end = end;
1076         if (gran)
1077             ws_displayinfo.cnt = gran;
1078        
1079         plot_board = $('#' + divid);
1080        
1081         plot_board.append('<h2 class="ws_title" id="worksheetTitlePerma">Worksheet Title</h2>\
1082                  <p/>\
1083                  <div id="ws_datetool">\
1084                  <div class="zoom">\
1085         <dl>\
1086             <dt>Zoom:</dt>\
1087             <dd><a href="#" class="first datechoice">6h</a></dd>\
1088             <dd><a href="#" class="datechoice">12h</a></dd>\
1089             <dd><a href="#" class="datechoice">1d</a></dd>\
1090             <dd><a href="#" class="datechoice">2d</a></dd>\
1091             <dd><a href="#" class="datechoice">1w</a></dd>\
1092             <dd><a href="#" class="selected datechoice">2w</a></dd>\
1093             <dd><a href="#" class="datechoice">4w</a></dd>\
1094             <dd><a href="#" class="datechoice">1y</a></dd>\
1095         </dl>\
1096            </div>\
1097            <div class="range">\
1098         <dl>\
1099             <dt>Date Range:</dt>\
1100             <dd><a href="" class="btn-slide">YYYY/MM/DD - YYYY/MM/DD</a></dd>\
1101         </dl>\
1102     </div>\
1103         <br style="clear:both; margin-bottom:0.5em;"/>\
1104     <div id="ws_widgetCalendar" class="widgetCalendar"></div>');
1105        
1106         plot_board.append('<div>\
1107       <ul id="worksheet-graphs" />\
1108       <br style="clear:left" />\
1109       </div>    \
1110           <div style="display:none">\
1111      <div id="maingraph-template">\
1112          <div class="plot-area" style="width:380px;height:180px"></div>\
1113          <div class="plot-legend">legend</div>\
1114         </div>\
1115        </div>\
1116        <div class="error"><p class="error" id="ws-tool-error"></p></div>\
1117        <div id="ws_payload">\
1118        </div>');
1119         $.getJSON("json/worksheet/info/" + id, process_worksheet_json);
1120         locked = true;
1121         lock_wforms();
1122     }
1123    
1124     function render_graph_inpage(divid, id, start, end, gran) {
1125    
1126         $.getJSON("json/graph/info/" + id, function(ginfo) {
1127        
1128             streaming = false; //precautionary
1129             stream_graph = $('#' + divid);
1130             stream_graph.ReconGraph({
1131                 graphid: ginfo.id,
1132                 type: ginfo.type,
1133                 width: (gran) ? gran : 780
1134             });
1135             stream_graph.ReconGraphRefresh({
1136                 graphid: ginfo.id,
1137                 start: start,
1138                 end: end,
1139                 stacks: ginfo.stacks
1140             });
1141            
1142             var dtool = $("<div id='mini_ws_datetool'>");
1143             dtool.append('<div class="zoom"> \
1144                 <dl> \
1145                         <dt>Zoom:</dt> \
1146                         <dd><a href="#" class="first datechoice">1d</a></dd> \
1147                         <dd><a href="#" class="datechoice">2d</a></dd> \
1148                         <dd><a href="#" class="datechoice">1w</a></dd> \
1149                         <dd><a href="#" class="datechoice">2w</a></dd> \
1150                         <dd><a href="#" class="datechoice">4w</a></dd> \
1151                         <dd><a href="#" class="datechoice">1y</a></dd> \
1152                 </dl>\
1153                  </div>\
1154                   </div>');
1155            
1156             var mheader = $("<div id='stream-header'>").append(dtool);
1157             stream_controls = get_stream_controls();
1158             mheader.append(stream_controls);
1159            
1160             stream_graph.prepend(mheader);
1161             stream_graph.append("<div id='streambox' style='display:none'></div>");
1162             stream_graph.append("<div class='stream-log' style='display:none'></div>");
1163            
1164             $("#stopstream").click(function() {
1165                 streaming = false;
1166                 $("#mini_ws_datetool").show();
1167                 $("#play_pause").html('PLAY');
1168                 $('#streambox').html('');
1169                 $(".stream-log").hide();
1170                 $("#oslider").hide();
1171                 $(".stream_controls").width("");
1172                 $(this).hide();
1173                 stream_graph.ReconGraphRefresh({
1174                     graphid: ginfo.id,
1175                     stacks: ginfo.stacks
1176                 });
1177             });
1178            
1179             $("#play_pause").click(function() {
1180                 if ($(this).html() == 'PLAY') {
1181                     $(this).html('PAUSE');
1182                     //if we are playing for the frist time
1183                     if (!streaming) {
1184                         $('#mini_ws_datetool').hide();
1185                         $(".stream-log").show().html("stream log_");
1186                         //theres probably a better way to make sure stuff fits in the stream_controls div
1187                         $(".stream_controls").width("290px");
1188                         $("#stopstream").show();
1189                         $("#oslider").show();
1190                     }
1191                     //setup/restart the plotting
1192                     stream_data(ginfo.id, stream_graph, $('#streambox'));
1193                 }
1194                 else if ($(this).html() == 'PAUSE') {
1195                     $(this).html('PLAY');
1196                     //this is where we pause for a bit
1197                     stream_graph.stopTime();
1198                 }
1199             });
1200            
1201             $("#pollslider").slider({
1202                 orientation: 'horizontal',
1203                 value: polltime,
1204                 max: 10000,
1205                 min: 1000,
1206                 step: 1000,
1207                 change: function(event, ui) {
1208                     polltime = $(this).slider('option', 'value');
1209                     $("#polltime").html(polltime + " ms");
1210                     if (streaming) {
1211                         streaming = false;
1212                         $('#streambox').html('');
1213                         $("#play_pause").html('PAUSE');
1214                         stream_data(ginfo.id, stream_graph, $('#streambox'));
1215                     }
1216                 }
1217             });
1218            
1219             $("#mini_ws_datetool .datechoice").click(function() {
1220                 $(".datechoice").removeClass("selected");
1221                 $(this).addClass("selected");
1222                 stream_graph.ReconGraphRefresh({
1223                     graphid: ginfo.id,
1224                     stacks: ginfo.stacks,
1225                     start: time_window_to_seconds($(this).html()),
1226                     end: ''
1227                 });
1228                 return false;
1229             });
1230            
1231         }); //end json call
1232     }
1233     function zoom_modal(id, gtype) {
1234    
1235         if (id)
1236             $.getJSON("json/graph/info/" + id, function(ginfo) {
1237            
1238                 streaming = false; //precautionary
1239                 stream_graph = $('<div></div>').ReconGraph({
1240                     graphid: ginfo.id,
1241                     type: ginfo.type,
1242                     width: 780
1243                 });
1244                 var smod = stream_graph.modal({
1245                     containerId: 'StreamingModalContainer',
1246                     close: 'true',
1247                     overlayCss: {
1248                         backgroundColor: '#000000',
1249                         cursor: 'wait'
1250                     },
1251                     containerCss: {
1252                         backgroundColor: '#FFFFFF',
1253                         left: '30%',
1254                         top: '10%',
1255                         border: '2px solid #000000',
1256                         padding: '5px'
1257                     },
1258                 });
1259                 stream_graph.ReconGraphRefresh({
1260                     graphid: ginfo.id,
1261                     stacks: ginfo.stacks
1262                 });
1263                
1264                 var dtool = $("<div id='mini_ws_datetool'>");
1265                 dtool.append('<div class="zoom"> \
1266                 <dl> \
1267                         <dt>Zoom:</dt> \
1268                         <dd><a href="#" class="first datechoice">1d</a></dd> \
1269                         <dd><a href="#" class="datechoice">2d</a></dd> \
1270                         <dd><a href="#" class="datechoice">1w</a></dd> \
1271                         <dd><a href="#" class="selected datechoice">2w</a></dd> \
1272                         <dd><a href="#" class="datechoice">4w</a></dd> \
1273                         <dd><a href="#" class="datechoice">1y</a></dd> \
1274                 </dl>\
1275                  </div>\
1276                   </div>');
1277                
1278                 var mheader = $("<div id='stream-modal-header'>").append(dtool);
1279                
1280                 mheader.append("<span class='zoomClose'>x</span>")
1281                 mheader.append(get_stream_controls());
1282                
1283                 stream_graph.prepend(mheader);
1284                 stream_graph.append("<div class='stream-log' style='display:none'></div>");
1285                
1286                 $(".zoomClose").click(function() {
1287                     streaming = false;
1288                     $('#streambox').html('');
1289                     smod.close();
1290                 });
1291                
1292                 $("#play_pause").click(function() {
1293                     if ($(this).html() == 'PLAY') {
1294                         $(this).html('PAUSE');
1295                         //if we are playing for the frist time
1296                         if (!streaming) {
1297                             $("#mini_ws_datetool").hide();
1298                             $(".stream-log").show().html("stream log_");
1299                             $("#stopstream").show();
1300                             $("#oslider").show();
1301                             //theres probably a better way to make sure stuff fits in the stream_controls div
1302                             $(".stream_controls").width("290px");
1303                         }
1304                         //setup/restart the plotting
1305                         stream_data(ginfo.id, stream_graph, $('#streambox'));
1306                     }
1307                     else if ($(this).html() == 'PAUSE') {
1308                         $(this).html('PLAY');
1309                         //this is where we pause for a bit
1310                         stream_graph.stopTime();
1311                     }
1312                 });
1313                
1314                 $("#stopstream").click(function() {
1315                     streaming = false;
1316                     $('#mini_ws_datetool').show();
1317                     $("#play_pause").html('PLAY');
1318                     $('#streambox').html('');
1319                     $(".stream-log").hide();
1320                     $(this).hide();
1321                     $("#oslider").hide();
1322                     $(".stream_controls").width("");
1323                     stream_graph.ReconGraphRefresh({
1324                         graphid: ginfo.id,
1325                         stacks: ginfo.stacks
1326                     });
1327                 });
1328                
1329                 $("#pollslider").slider({
1330                     orientation: 'horizontal',
1331                     value: polltime,
1332                     max: 10000,
1333                     min: 1000,
1334                     step: 1000,
1335                     change: function(event, ui) {
1336                         polltime = $(this).slider('option', 'value');
1337                         $("#polltime").html(polltime + " ms");
1338                         if (streaming) {
1339                             streaming = false;
1340                             $('#streambox').html('');
1341                             $("#play_pause").html('PAUSE');
1342                             stream_data(ginfo.id, stream_graph, $('#streambox'));
1343                         }
1344                     }
1345                 });
1346                
1347                 $("#mini_ws_datetool .datechoice").click(function() {
1348                     $(".datechoice").removeClass("selected");
1349                     $(this).addClass("selected");
1350                     stream_graph.ReconGraphRefresh({
1351                         graphid: ginfo.id,
1352                         stacks: ginfo.stacks,
1353                         start: time_window_to_seconds($(this).html()),
1354                         end: ''
1355                     });
1356                     return false;
1357                 });
1358             });
1359     } //end zoom_modal
1360     function lock_wforms() {
1361         locked = true;
1362         $("h2#worksheetTitle").unbind();
1363         $("ul#worksheet-graphs").unbind();
1364         $(".ws-toolbar-edit").attr("class", "ws-toolbar");
1365     }
1366    
1367     function unlock_wforms() {
1368         locked = false;
1369         $("h2#worksheetTitle").editable(function(value, settings) {
1370             wsinfo.title = value;
1371             update_current_worksheet();
1372             return (value);
1373         }, {});
1374        
1375         var ul = $("ul#worksheet-graphs");
1376         ul.sortable({
1377             handle: '.moveGraph',
1378             scroll: true,
1379             stop: function(e, ui) {
1380                 wsinfo.graphs = new Array();
1381                 ui.item.parent().find("> li > div").each(function(i) {
1382                     wsinfo.graphs.push($(this).attr("id"));
1383                 });
1384                 update_current_worksheet();
1385             }
1386         });
1387        
1388         $(".ws-toolbar").attr("class", "ws-toolbar-edit");
1389     }
1390    
1391     //this is a bad function, because we have to account for start/end being UTC strings or integers, and to do so outside
1392     //would require changes to the where we keep worksheet ui code
1393     function update_worksheet_permalink(id, start, end, gran) {
1394         if (start != "") {
1395             if (parseInt(start) == start) {
1396                 var cdate = new Date();
1397                 start = parseInt(cdate.getTime() - ws_displayinfo.start * 1000);
1398             }
1399             else {
1400                 start = new Date(start);
1401                 start = parseInt(start.getTime());
1402             }
1403         }
1404         if (end != "") {
1405             if (parseInt(end) == end) {
1406                 end = parseInt(end);
1407             }
1408             else {
1409                 end = new Date(end);
1410                 end = parseInt(end.getTime());
1411             }
1412         }
1413         if ($('#wpermalink a')) {
1414             $('#wpermalink a').attr('href', "drawing_board.php?otype=wsheet&id=" + id + "&start=" + start + "&end=" + end + "&gran=" + gran);
1415         }
1416     }
1417    
1418     function update_current_worksheet(f) {
1419         var str = JSON.stringify(wsinfo);
1420         $.post("json/worksheet/store", {
1421             'json': str
1422         }, function(d) {
1423             wsinfo.id = d.id;
1424             if (d.error)
1425                 $("#ws-tool-error").html(d.error).fadeIn('fast');
1426             else
1427                 $("#ws-tool-error").fadeOut('fast');
1428             if (wsinfo.id && wsinfo.title && wsinfo.saved != true && $(".rememberWorksheet:visible").length == 0) {
1429                 wsinfo.saved = false;
1430                 $(".rememberWorksheet").html('"Remember" this worksheet.').fadeIn('slow');
1431                 lock_wforms();
1432                 modal_warning("Worksheet not saved!", "You must hit 'Remember' to continue editing.");
1433                 $(".rememberWorksheet").click(function() {
1434                     wsinfo.saved = true;
1435                     update_current_worksheet(function(r) {
1436                         if (r.error)
1437                             wsinfo.saved = false;
1438                         else {
1439                             $(".rememberWorksheet").html('Remebered').fadeOut('slow');
1440                             if (wsinfo.id)
1441                                 update_worksheet_permalink(wsinfo.id, "", "", "");
1442                             unlock_wforms();
1443                         }
1444                     });
1445                 });
1446             }
1447             if (f)
1448                 f(d);
1449         }, 'json');
1450     }
1451    
1452     function process_worksheet_json(r) {
1453         wsinfo.id = r.sheetid;
1454         wsinfo.title = r.title;
1455         ws_displayinfo.title = r.title;
1456         wsinfo.graphs = new Array();
1457        
1458         var ul = $("ul#worksheet-graphs");
1459         $("h2.ws_title").html(r.title);
1460         ul.empty();
1461         for (var i = 0; i < r.graphs.length; i++) {
1462             var g = {};
1463             g.graphid = r.graphs[i];
1464             g.start = ws_displayinfo.start;
1465             g.end = ws_displayinfo.end;
1466             g.cnt = ws_displayinfo.cnt;
1467            
1468             $.getJSON("json/graph/info/" + g.graphid, function(j) {
1469                 g.type = j.type;
1470                 g.graphid = j.id;
1471                 g.stacks = j.stacks;
1472                 var o = make_ws_graph(g);
1473                 ul.append($('<li/>').append(o));
1474                 o.ReconGraphRefresh();
1475                 wsinfo.graphs.push(g.graphid);
1476             });
1477         }
1478         update_worksheet_permalink(wsinfo.id, "", "", ws_displayinfo.cnt);
1479         ul.sortable("refresh");
1480     }
1481    
1482     function add_graph_to_worksheet(graphid) {
1483         if (!ws_locked("Click 'Edit Worksheet' to unlock.")) {
1484             for (var i = 0; wsinfo.graphs && (i < wsinfo.graphs.length); i++) {
1485                 if (wsinfo.graphs[i] == graphid) {
1486                     modal_warning("", "Worksheets cannot have duplicate graphs!");
1487                     return;
1488                 }
1489             }
1490             var g = {
1491                 start: ws_displayinfo.start,
1492                 end: ws_displayinfo.end,
1493                 cnt: ws_displayinfo.cnt,
1494                 graphid: graphid
1495             };
1496            
1497             $.getJSON("json/graph/info/" + g.graphid, function(j) {
1498                 g.type = j.type;
1499                 g.graphid = j.id;
1500                 g.stacks = j.stacks;
1501                 var o = make_ws_graph(g);
1502                 var ul = $("ul#worksheet-graphs");
1503                 ul.append($('<li/>').append(o));
1504                 o.ReconGraphRefresh();
1505                 ul.sortable("refresh");
1506                 if (!wsinfo.graphs) {
1507                     wsinfo.graphs = new Array();
1508                 }
1509                 wsinfo.graphs.push(graphid);
1510                 update_current_worksheet();
1511                 unlock_wforms();
1512             });
1513         }
1514     }
1515     function display_info(start, end, cnt) {
1516         ws_displayinfo.start = start;
1517         ws_displayinfo.end = end;
1518         if (cnt)
1519             ws_displayinfo.cnt = cnt;
1520         update_worksheet_permalink(wsinfo.id, ws_displayinfo.start, ws_displayinfo.end, ws_displayinfo.cnt);
1521     }
1522     function refresh_worksheet() {
1523         var g = {
1524             start: ws_displayinfo.start,
1525             end: ws_displayinfo.end,
1526             cnt: ws_displayinfo.cnt
1527         };
1528         $("ul#worksheet-graphs > li > div").ReconGraphRefresh(g);
1529     }
1530     function load_worksheet(id) {
1531         if (id == null) {
1532             wsinfo.saved = false;
1533             locked = false;
1534             unlock_wforms();
1535             $(".editWorksheet").html('Editing!').fadeIn('slow');
1536             process_worksheet_json({
1537                 graphs: [],
1538                 title: 'Worksheet Title (click to edit)',
1539                 sheetid: ''
1540             });
1541         }
1542         else {
1543             wsinfo.saved = true;
1544             locked = true;
1545             lock_wforms();
1546             $(".editWorksheet").html('Edit Worksheet').fadeIn('slow');
1547             $.getJSON("json/worksheet/info/" + id, process_worksheet_json);
1548         }
1549     }
1550     return {
1551         load: load_worksheet,
1552         display_info: display_info,
1553         refresh: refresh_worksheet,
1554         add_graph: add_graph_to_worksheet,
1555         update: update_current_worksheet,
1556         lock: lock_wforms,
1557         unlock: unlock_wforms,
1558         zoom: zoom_modal,
1559         render_graph_inpage: render_graph_inpage,
1560         render_ws_inpage: render_ws_inpage,
1561         stream: stream_data,
1562         islocked: function() {
1563             return locked;
1564         }
1565     };
1566 })(jQuery);
Note: See TracBrowser for help on using the browser.