Return-Path: X-Original-To: apmail-aurora-commits-archive@minotaur.apache.org Delivered-To: apmail-aurora-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 8A7A910365 for ; Tue, 31 Dec 2013 21:20:28 +0000 (UTC) Received: (qmail 37666 invoked by uid 500); 31 Dec 2013 21:20:28 -0000 Delivered-To: apmail-aurora-commits-archive@aurora.apache.org Received: (qmail 37639 invoked by uid 500); 31 Dec 2013 21:20:28 -0000 Mailing-List: contact commits-help@aurora.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aurora.incubator.apache.org Delivered-To: mailing list commits@aurora.incubator.apache.org Received: (qmail 37632 invoked by uid 99); 31 Dec 2013 21:20:28 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 31 Dec 2013 21:20:28 +0000 X-ASF-Spam-Status: No, hits=-2000.4 required=5.0 tests=ALL_TRUSTED,RP_MATCHES_RCVD X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO mail.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with SMTP; Tue, 31 Dec 2013 21:20:16 +0000 Received: (qmail 37495 invoked by uid 99); 31 Dec 2013 21:19:54 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 31 Dec 2013 21:19:54 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id BEABB90CD78; Tue, 31 Dec 2013 21:19:54 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: wfarner@apache.org To: commits@aurora.incubator.apache.org Date: Tue, 31 Dec 2013 21:20:11 -0000 Message-Id: In-Reply-To: <40c1ca128bee43618ff700d94f86fffb@git.apache.org> References: <40c1ca128bee43618ff700d94f86fffb@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [18/51] [partial] Rename twitter* and com.twitter to apache and org.apache directories to preserve all file history before the refactor. X-Virus-Checked: Checked by ClamAV on apache.org http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/bc1635df/src/main/python/apache/thermos/observer/http/assets/jquery.pailer.js ---------------------------------------------------------------------- diff --git a/src/main/python/apache/thermos/observer/http/assets/jquery.pailer.js b/src/main/python/apache/thermos/observer/http/assets/jquery.pailer.js new file mode 100644 index 0000000..5962f9e --- /dev/null +++ b/src/main/python/apache/thermos/observer/http/assets/jquery.pailer.js @@ -0,0 +1,317 @@ +// A jQuery plugin for PAging and taILing data (i.e., a +// 'PAILer'). Paging occurs when scrolling reaches the "top" and +// tailing occurs when scrolling has reached the "bottom". + +// A 'read' function must be provided for reading the data (in +// bytes). This function should expect an "options" object with the +// fields 'offset' and 'length' set for reading the data. The result +// from of the function should be a "promise" like value which has a +// 'success' and 'error' callback which each take a function. An +// object with at least two fields defined ('offset' and 'length') is +// expected on success. A third field, 'data' may be provided if +// 'length' is greater than 0. If the offset requested is greater than +// the available offset, the result should be an object with the +// 'offset' field set to the available offset (i.e., the length of the +// data) and the 'length' field set to 0. + +// The plugin prepends, appends, and updates the "html" component of +// the elements specified in the jQuery selector (e.g., doing +// $('#data').pailer(...) means that data will be updated within +// $('#data') via $('#data').prepend(...) and $('#data').append(...) +// and $('#data').html(...) calls). + +// An indicator paragraph element (i.e.,

) can be specified that +// the plugin will write text to describing any status/errors that +// have been encountered. + +// Data will automagically get truncated at some specified length, +// configurable via the 'truncate-length' option. Likewise, the amount +// of data paged in at a time can be configured via the 'page-size' +// option. + +// Example: +// HTML: +//

+// +//
+//

+//
+// Javascript: +// $('#data').pailer({ +// 'read': function(options) { +// var settings = $.extend({ +// 'offset': -1, +// 'length': -1 +// }, options); +// var url = '/url/for/data' +// + '?offset=' + settings.offset +// + '&length=' + settings.length; +// return $.getJSON(url); +// }, +// 'indicator': $('#indicator') +// }); + +(function($) { + function Pailer(read, element, indicator, page_size, truncate_length) { + var this_ = this; + + this_.read = read; + this_.element = element; + this_.indicator = indicator; + this_.initialized = false; + this_.start = -1; + this_.end = -1; + this_.paging = false; + this_.tailing = true; + + page_size || $.error('Expecting page_size to be defined'); + truncate_length || $.error('Expecting truncate_length to be defined'); + + this_.page_size = page_size; + this_.truncate_length = truncate_length; + + this_.element.css('overflow', 'auto'); + + this_.element.scroll(function () { + var scrollTop = this_.element.scrollTop(); + var height = this_.element.height(); + var scrollHeight = this_.element[0].scrollHeight; + + if (scrollTop == 0) { + this_.page(); + } else if (scrollTop + height >= scrollHeight) { + if (!this_.tailing) { + this_.tailing = true; + this_.tail(); + } + } else { + this_.tailing = false; + } + }); + } + + + Pailer.prototype.initialize = function() { + var this_ = this; + + // Set an indicator while we load the data. + this_.indicate('(LOADING)'); + + this_.read({'offset': -1}) + .success(function(data) { + this_.indicate(''); + + // Get the last page of data. + if (data.offset > this_.page_size) { + this_.start = this_.end = data.offset - this_.page_size; + } else { + this_.start = this_.end = 0; + } + + this_.initialized = true; + this_.element.html(''); + setTimeout(function() { this_.tail(); }, 0); + }) + .error(function() { + this_.indicate('(FAILED TO INITIALIZE ... RETRYING)'); + setTimeout(function() { + this_.indicate(''); + this_.initialize(); + }, 1000); + }); + } + + + Pailer.prototype.page = function() { + var this_ = this; + + if (!this_.initialized) { + return; + } + + if (this_.paging) { + return; + } + + this_.paging = true; + this_.indicate('(PAGING)'); + + if (this_.start == 0) { + this_.paging = false; + this_.indicate('(AT BEGINNING OF FILE)'); + setTimeout(function() { this_.indicate(''); }, 1000); + return; + } + + var offset = this_.start - this_.page_size; + var length = this_.page_size; + + if (offset < 0) { + offset = 0; + length = this_.start; + } + + // Buffer the data in case what gets read is less than 'length'. + var buffer = ''; + + var read = function(offset, length) { + this_.read({'offset': offset, 'length': length}) + .success(function(data) { + if (data.length < length) { + buffer += data.data; + read(offset + data.length, length - data.length); + } else if (data.length > 0) { + this_.indicate('(PAGED)'); + setTimeout(function() { this_.indicate(''); }, 1000); + + // Prepend buffer onto data. + data.offset -= buffer.length; + data.data = buffer + data.data; + data.length = data.data.length; + + // Truncate to the first newline (unless this is the beginning). + if (data.offset != 0) { + var index = data.data.indexOf('\n') + 1; + data.offset += index; + data.data = data.data.substring(index); + data.length -= index; + } + + this_.start = data.offset; + + var scrollTop = this_.element.scrollTop(); + var scrollHeight = this_.element[0].scrollHeight; + + this_.element.prepend(data.data); + + scrollTop += this_.element[0].scrollHeight - scrollHeight; + this_.element.scrollTop(scrollTop); + + this_.paging = false; + } + }) + .error(function() { + this_.indicate('(FAILED TO PAGE ... RETRYING)'); + setTimeout(function() { + this_.indicate(''); + this_.page(); + }, 1000); + }); + } + + read(offset, length); + } + + + Pailer.prototype.tail = function() { + var this_ = this; + + if (!this_.initialized) { + return; + } + + this_.read({'offset': this_.end, 'length': this_.truncate_length}) + .success(function(data) { + var scrollTop = this_.element.scrollTop(); + var height = this_.element.height(); + var scrollHeight = this_.element[0].scrollHeight; + + // Check if we are still at the bottom (since this event might + // have fired before the scroll event has been dispatched). + if (scrollTop + height < scrollHeight) { + this_.tailing = false; + return; + } + + if (data.length > 0) { + // Truncate to the first newline if this is the first time + // (and we aren't reading from the beginning of the log). + if (this_.start == this_.end && data.offset != 0) { + var index = data.data.indexOf('\n') + 1; + data.offset += index; + data.data = data.data.substring(index); + data.length -= index; + this_.start = data.offset; // Adjust the actual start too! + } + + this_.end = data.offset + data.length; + + this_.element.append(data.data); + + scrollTop += this_.element[0].scrollHeight - scrollHeight; + this_.element.scrollTop(scrollTop); + + // Also, only if we're at the bottom, truncate data so that we + // don't consume too much memory. TODO(benh): Only do + // truncations if we've been at the bottom for a while. + this_.truncate(); + } + + // Tail immediately if we got as much data as requested (since + // this probably means we've waited around a while). The + // alternative here would be to not get data in chunks, but the + // potential issue here is that we might end up requesting GB of + // log data at a time ... the right solution here might be to do + // a request to determine the new ending offset and then request + // the proper length. + if (data.length == this_.truncate_length) { + setTimeout(function() { this_.tail(); }, 0); + } else { + setTimeout(function() { this_.tail(); }, 1000); + } + }) + .error(function() { + this_.indicate('(FAILED TO TAIL ... RETRYING)'); + this_.initialized = false; + setTimeout(function() { + this_.indicate(''); + this_.initialize(); + }, 1000); + }); + } + + + Pailer.prototype.indicate = function(text) { + var this_ = this; + + if (this_.indicator) { + this_.indicator.text(text); + } + } + + + Pailer.prototype.truncate = function() { + var this_ = this; + + var length = this_.element.html().length; + if (length >= this_.truncate_length) { + var index = length - this_.truncate_length; + this_.start = this_.end - this_.truncate_length; + this_.element.html(this_.element.html().substring(index)); + } + } + + $.fn.pailer = function(options) { + var settings = $.extend({ + 'read': function() { return { + 'success': function() {}, + 'error': function(f) { f(); } + }}, + 'page_size': 8 * 4096, // 8 "pages". + 'truncate_length': 50000 + }, options); + + this.each(function() { + var pailer = $.data(this, 'pailer'); + if (!pailer) { + pailer = new Pailer(settings.read, + $(this), + settings.indicator, + settings.page_size, + settings.truncate_length); + $.data(this, 'pailer', pailer); + pailer.initialize(); + } + }); + } +})(jQuery);