/* Copyright (c) 2010 Richard Wall * See LICENSE for details. * * Wrappers and convenience fuctions for working with the javascriptRRD, jQuery, * and flot charting packages. * * javascriptRRD - http://javascriptrrd.sourceforge.net/ * jQuery - http://jquery.com/ * flot - http://code.google.com/p/flot/ */ if(typeof jrrd == 'undefined') { var jrrd = {}; } jrrd.downloadBinary = function(url) { var d = new MochiKit.Async.Deferred(); $.ajax({ url: url, dataType: 'text', cache: false, beforeSend: function(request) { try { request.overrideMimeType('text/plain; charset=x-user-defined'); } catch(e) { // IE doesn't support overrideMimeType } }, success: function(data) { try { d.callback(new BinaryFile(data)); } catch(e) { d.errback(e); } }, error: function(xhr, textStatus, errorThrown) { // Special case for IE which handles binary data slightly // differently. if(textStatus == 'parsererror') { if (typeof xhr.responseBody != 'undefined') { return this.success(xhr.responseBody); } } d.errback(new Error(xhr.status)); } }); return d; }; /** * A wrapper around an instance of javascriptrrd.RRDFile which provides a * convenient way to query the RRDFile based on time range, RRD data source (DS) * and RRD consolidation function (CF). * * @param startTime: A javascript {Date} instance representing the start of query * time range, or {null} to return earliest available data. * @param endTime: A javascript {Date} instance representing the end of query * time range, or {null} to return latest available data. * @param dsId: A {String} name of an RRD DS or an {Int} DS index number or * {null} to return the first available DS. * @param cfName: A {String} name of an RRD consolidation function * @return: A flot compatible data series object **/ jrrd.RrdQuery = function(rrd) { this.rrd = rrd; }; jrrd.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) { var startTimestamp = startTime.getTime()/1000; var lastUpdated = this.rrd.getLastUpdate(); var endTimestamp = lastUpdated; if(endTime) { endTimestamp = endTime.getTime()/1000; // If end time stamp is beyond the range of this rrd then reset it if(lastUpdated < endTimestamp) { endTimestamp = lastUpdated; } } if(dsId == null) { dsId = 0; } var ds = this.rrd.getDS(dsId); if(cfName == null) { cfName = 'AVERAGE'; } var rra, step, rraRowCount, firstUpdated; for(var i=0; i -1 && this.lastUpdate < endTimestamp )) { this._download = jrrd.downloadBinary(this.url) .addCallback( function(self, binary) { // Upon successful download convert the resulting binary // into an RRD file and pass it on to the next callback // in the chain. var rrd = new RRDFile(binary); self.lastUpdate = rrd.getLastUpdate(); return rrd; }, this); } // Set up a deferred which will call getData on the local RrdQuery object // returning a flot compatible data object to the caller. var ret = new MochiKit.Async.Deferred().addCallback( function(startTime, endTime, dsId, rrd) { return new jrrd.RrdQuery(rrd).getData(startTime, endTime, dsId); }, startTime, endTime, dsId); // Add a pair of callbacks to the current download which will callback the // result which we setup above. this._download.addBoth( function(ret, res) { if(res instanceof Error) { ret.errback(res); } else { ret.callback(res); } return res; }, ret); return ret; }; jrrd.RrdQueryDsProxy = function(rrdQuery, dsId) { this.rrdQuery = rrdQuery; this.dsId = dsId; }; jrrd.RrdQueryDsProxy.prototype.getData = function(startTime, endTime) { return this.rrdQuery.getData(startTime, endTime, this.dsId); }; jrrd.Chart = function(template, options) { this.template = template; this.options = jQuery.extend(true, {}, options); this.data = []; var self = this; $('.legend tr', this.template[0]).live('click', function(e) { self.switchDataEnabled($(this).children('.legendLabel').text()); self.draw(); }); }; jrrd.Chart.prototype.addData = function(label, db, enabled) { if(typeof enabled == 'undefined') { enabled = true; } this.data.push([label, db, enabled]); }; jrrd.Chart.prototype.switchDataEnabled = function(label) { for(var i=0; i -1 ) { labelCell.addClass('disabled'); } } ); }, this) .addErrback( function(self, failure) { self.template.text('error: ' + failure.message); }, this) .addBoth( function(self, res) { self.template.trigger('chart_loaded'); return res; }, this); }; jrrd.ChartCoordinator = function(ui) { this.ui = ui; this.charts = []; var self = this; this.ui.bind('submit', function(e) { self.update(); return false; }); this.ui.bind('reset', function(e) { self.reset(); return false; }); var rangePreviewOptions = { grid: { borderWidth: 1 }, selection: { mode: 'x' }, xaxis: { mode: "time" }, yaxis: { ticks: [] } }; var now = new Date().getTime(); var HOUR = 1000 * 60 * 60; var DAY = HOUR * 24; var WEEK = DAY * 7; var MONTH = DAY * 31; var YEAR = DAY * 365; var data = [ [now - WEEK, null], [now, null]]; this.rangePreview = $.plot(this.ui.find('.range-preview'), [data], rangePreviewOptions); this.ui.bind("plotselected", function(event, ranges) { self.setTimeRange(new Date(ranges.xaxis.from), new Date(ranges.xaxis.to)); }); }; jrrd.ChartCoordinator.prototype.update = function() { var startTime = new Date(this.ui[0].startTime.value); var endTime = new Date(this.ui[0].endTime.value); var ranges = { xaxis: { from: startTime.getTime(), to: endTime.getTime() } }; this.rangePreview.setSelection(ranges, true); for(var i=0; i