You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by wf...@apache.org on 2013/12/31 22:20:11 UTC
[18/51] [partial] Rename twitter* and com.twitter to apache and
org.apache directories to preserve all file history before the refactor.
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., <p>) 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:
+// <div id="data" style="white-space:pre-wrap;"></div>
+//
+// <div style="position: absolute; left: 5px; top: 0px;">
+// <p id="indicator"></p>
+// </div>
+// 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);