incubator-blur-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cr...@apache.org
Subject [15/15] git commit: merged in BLUR-367 update
Date Sun, 30 Nov 2014 16:46:52 GMT
merged in BLUR-367 update


Project: http://git-wip-us.apache.org/repos/asf/incubator-blur/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-blur/commit/cfcb73b5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-blur/tree/cfcb73b5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-blur/diff/cfcb73b5

Branch: refs/heads/master
Commit: cfcb73b5135fb4a09718041d90dbf822782d53e4
Parents: ef6e98f
Author: Chris Rohr <rohr.chris@gmail.com>
Authored: Sun Nov 30 11:46:06 2014 -0500
Committer: Chris Rohr <rohr.chris@gmail.com>
Committed: Sun Nov 30 11:46:06 2014 -0500

----------------------------------------------------------------------
 LICENSE                                         |    7 +
 blur-console/src/main/webapp/Gruntfile.js       |    1 +
 .../src/main/webapp/js/blurconsole.facets.js    |  185 +
 .../src/main/webapp/js/blurconsole.fake.js      |   13 +-
 .../src/main/webapp/js/blurconsole.model.js     |    7 +-
 .../src/main/webapp/js/blurconsole.schema.js    |    2 +-
 .../src/main/webapp/js/blurconsole.search.js    |   44 +-
 .../src/main/webapp/js/blurconsole.shell.js     |    1 +
 .../src/main/webapp/js/blurconsole.utils.js     |    2 +-
 .../src/main/webapp/less/blurconsole.less       |    1 +
 .../main/webapp/less/blurconsole.search.less    |    5 +
 .../src/main/webapp/less/tagmanager.less        |  195 +
 .../main/webapp/libs/tagmanager/tagmanager.js   |  514 ++
 ...console.2a648e83800e5f3413c3251cbe533ff3.css | 6613 ++++++++++++++++++
 .../src/main/webapp/public/css/blurconsole.css  |  102 +-
 ...ole.css.5fa0e71593d7ef244172ddff242e3517.map |    1 +
 ...ole.css.e2473a87f61e6af405b8d1575d504a83.map |    1 -
 .../main/webapp/public/css/blurconsole.css.map  |    2 +-
 ...console.ebb9bec3c7d5c9414dd3e0d7fb28e18c.css | 6513 -----------------
 blur-console/src/main/webapp/public/index.html  |    4 +-
 ...rconsole.8ee155e1d099186ffd4d0747db8f3413.js |   28 +
 ...rconsole.b1022eca3c3fa45dd44542c7852a16da.js |   28 +
 ...rconsole.b9b51b1e86c236d92a1a047613bff595.js |   28 -
 .../src/main/webapp/public/js/blurconsole.js    |   10 +-
 ...sole.js.61a0a795b3b948c93367966422a05415.map |    1 -
 ...sole.js.aa668c445aa4ac46ad629ed9857f2361.map |    1 +
 .../main/webapp/public/js/blurconsole.js.map    |    2 +-
 .../main/webapp/public/views/search.tpl.html    |    2 +-
 .../src/main/webapp/views/search.tpl.html       |    2 +-
 29 files changed, 7748 insertions(+), 6567 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index 897bd31..dbdda7f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -402,3 +402,10 @@ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+===========================================================================
+The following license applies to the following libraries: tagmanager
+./blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
+---------------------------------------------------------------------------
+Copyright 2012 MaxFavilli.com
+Licensed under the Mozilla Public License, Version 2.0
+

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/Gruntfile.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/Gruntfile.js b/blur-console/src/main/webapp/Gruntfile.js
index d32dd80..1c6f70c 100644
--- a/blur-console/src/main/webapp/Gruntfile.js
+++ b/blur-console/src/main/webapp/Gruntfile.js
@@ -43,6 +43,7 @@ module.exports = function (grunt) {
         'libs/flot/jquery.flot.categories.js',
         'libs/flot/jquery.flot.stack.js',
         'libs/typeahead/typeahead.jquery.js',
+        'libs/tagmanager/tagmanager.js',
         'libs/moment/moment.js',
         'js/blurconsole.js',
         'js/*\.js'

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.facets.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.facets.js b/blur-console/src/main/webapp/js/blurconsole.facets.js
new file mode 100644
index 0000000..921ff3f
--- /dev/null
+++ b/blur-console/src/main/webapp/js/blurconsole.facets.js
@@ -0,0 +1,185 @@
+/*
+
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/*jshint laxbreak: true */
+/*global blurconsole:false */
+blurconsole.facets = (function () {
+  'use strict';
+
+    //----------------------------- Configuration and State -------------------------
+  var
+    configMap = {
+      mainHtml: String()
+        + '<div class="container-fluid">'
+          + '<div class="alert alert-danger facetWarning" style="display:none">Please
make sure you select a family, column and add terms to run faceting.</div>'
+          + '<div class="row">'
+            + '<form>'
+              + '<div class="col-md-6">'
+                + '<div class="form-group">'
+                  + '<label for="facetFamily">Family</label>'
+                  + '<select class="form-control" id="facetFamily">'
+                  + '</select>'
+                + '</div>'
+                + '<div class="form-group">'
+                  + '<label for="facetColumn">Column</label>'
+                  + '<select class="form-control" id="facetColumn">'
+                  + '</select>'
+                + '</div>'
+              + '</div>'
+              + '<div class="col-md-6">'
+                + '<div class="form-group">'
+                  + '<label for="facetTerms">Terms</label>'
+                  + '<input type="text" name="facetTerms" class="form-control tm-input"
id="facetTerms" autocomplete="off"/>'
+                  + '<div class="facetTermList"></div>'
+                + '</div>'
+                + '<div class="form-group">'
+                  + '<button type="button" class="btn btn-primary" id="facetSubmit">Run</button>'
+                + '</div>'
+              + '</div>'
+            + '</form>'
+          + '</div>'
+          + '<hr/>'
+          + '<div class="row">'
+            + '<div class="col-md-12 facetResults">'
+            + '</div>'
+          + '</div>'
+        + '</div>'
+    },
+    stateMap = {},
+    jqueryMap = {};
+
+  //------------------------------ Private Methods -----------------------------------------------------
+  function _switchToSearch(evt){
+    var family = jqueryMap.familyChooser.val();
+    var column = jqueryMap.columnChooser.val();
+    var initialQuery = stateMap.query;
+    blurconsole.shell.changeAnchorPart({
+      tab: 'search',
+      _tab: {
+        query: encodeURIComponent(initialQuery + ' +(+' + family + '.' + column + ':(' +
$(evt.currentTarget).data('term') + '))'),
+        table: stateMap.table,
+        rr: 'rowrow'
+      }
+    });
+    jqueryMap.modal.modal('hide');
+  }
+
+  //------------------------------ Event Handling and DOM Methods --------------------------------------
+  function _showFacet(event, data) {
+    stateMap.table = data.table;
+    stateMap.query = data.query;
+    stateMap.modalId = stateMap.table + '_modal';
+    blurconsole.model.tables.getSchema(stateMap.table, _popupFacetView);
+  }
+
+  function _popupFacetView(schema) {
+    stateMap.schema = schema;
+    jqueryMap.contentHolder = $(configMap.mainHtml);
+    jqueryMap.contentHolder.find('#facetFamily').html(_buildFamilyOptions());
+
+    jqueryMap.modal = $(blurconsole.browserUtils.modal(stateMap.modalId, 'Faceting for '
+ stateMap.query, jqueryMap.contentHolder, null, 'large'));
+    jqueryMap.modal.modal()
+    .on('shown.bs.modal', function(){
+      jqueryMap.familyChooser = $('#facetFamily', jqueryMap.modal);
+      jqueryMap.columnChooser = $('#facetColumn', jqueryMap.modal);
+      jqueryMap.facetResults = $('.facetResults', jqueryMap.modal);
+      jqueryMap.terms = $('#facetTerms', jqueryMap.modal);
+
+      jqueryMap.terms.tagsManager({
+        tagsContainer: $('.facetTermList', jqueryMap.modal)
+      });
+
+      $('#facetSubmit', jqueryMap.modal).on('click', _runFacetCounts);
+    })
+    .on('hidden.bs.modal', function(e) {
+      $(e.currentTarget).remove();
+      jqueryMap.contentHolder.remove();
+      jqueryMap = {};
+      stateMap = {};
+    })
+    .on('change', 'select#facetFamily', function(e) {
+      var family = $(e.currentTarget).val();
+      jqueryMap.columnChooser.html(_buildColumnOptions(family));
+      return false;
+    })
+    .on('click', '.searchTrigger', _switchToSearch);
+  }
+
+  function _buildFamilyOptions() {
+    var options = '<option value="">Select a Family</option>';
+
+    $.each(stateMap.schema, function(family) {
+      options += '<option value="' + family + '">' + family + '</option>';
+    });
+    return options;
+  }
+
+  function _buildColumnOptions(family) {
+    var options = '<option value="">Select a Column</option>';
+
+    $.each(stateMap.schema[family], function(column) {
+      options += '<option value="' + column + '">' + column + '</option>';
+    });
+    return options;
+  }
+
+  function _runFacetCounts() {
+    var family = jqueryMap.familyChooser.val();
+    var column = jqueryMap.columnChooser.val();
+    var terms = jqueryMap.terms.tagsManager('tags');
+
+    if (jqueryMap.terms.val()) {
+      jqueryMap.terms.tagsManager('pushTag', jqueryMap.terms.val());
+      terms = jqueryMap.terms.tagsManager('tags');
+    }
+
+    if (family && column && terms.length > 0) {
+      $('.facetWarning', jqueryMap.modal).hide();
+      var facetQuery = '+(+' + family + '.' + column + ':(' + terms.join(' ') + '))';
+      blurconsole.model.search.runFacetCount(stateMap.query, stateMap.table, facetQuery,
_displayFacetCounts);
+    } else {
+      $('.facetWarning', jqueryMap.modal).show();
+    }
+  }
+
+  function _displayFacetCounts(counts) {
+    var markup = '<table class="table table-condensed table-hover table-bordered"><thead><tr><th>Term</th><th>Count</th><th></th></thead><tbody>';
+
+    $.each(counts, function(term, count){
+      markup += '<tr>';
+      markup += '<td>' + term + '</td>';
+      markup += '<td>' + count + '</td>';
+      markup += '<td><button type="button" class="btn btn-sm btn-default searchTrigger"
title="Run search with facet added" data-term="' + term + '"><i class="glyphicon glyphicon-search"/></button></td>';
+      markup += '</tr>';
+    });
+
+    markup += '</tbody></table>';
+
+    jqueryMap.facetResults.html(markup);
+  }
+
+    //----------------------------- Public API ----------------------------
+  function initModule() {
+    $.gevent.subscribe($(document), 'facet-show', _showFacet);
+  }
+
+  return {
+    initModule : initModule
+  };
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.fake.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.fake.js b/blur-console/src/main/webapp/js/blurconsole.fake.js
index f5c71d8..84e8a73 100644
--- a/blur-console/src/main/webapp/js/blurconsole.fake.js
+++ b/blur-console/src/main/webapp/js/blurconsole.fake.js
@@ -274,6 +274,16 @@ blurconsole.fake = (function() {
     }
   }
 
+  function runFacetCount(query, table, facetQuery, callback) {
+    console.log('sending fake facet count [' + facetQuery + '] on table [' + table + '] on
against query [' + query + ']');
+    var terms = facetQuery.match(/:\((.*)\)\)/)[1].split(' ');
+    var data = {};
+    $.each(terms, function(i, term){
+      data[term] = _randomNumber(100, true);
+    });
+    _sendCallback(callback, data);
+  }
+
   function initModule() {
     $('nav.navbar .pull-right').append('<button type="button" id="fake_freeze" class="btn
btn-default btn-sm">Freeze</button>');
     $('#fake_freeze').click(_toggleFreeze);
@@ -292,6 +302,7 @@ blurconsole.fake = (function() {
     getSchema : getSchema,
     findTerms : findTerms,
     copyTable : copyTable,
-    sendSearch : sendSearch
+    sendSearch : sendSearch,
+    runFacetCount: runFacetCount
   };
 }());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.model.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.model.js b/blur-console/src/main/webapp/js/blurconsole.model.js
index 336cf5f..abc78ed 100644
--- a/blur-console/src/main/webapp/js/blurconsole.model.js
+++ b/blur-console/src/main/webapp/js/blurconsole.model.js
@@ -481,11 +481,16 @@ blurconsole.model = (function() {
       _sendSearch();
     }
 
+    function runFacetCount( query, table, facetQuery, callback ) {
+      configMap.poller.runFacetCount(query, table, facetQuery, callback);
+    }
+
     return {
       runSearch: runSearch,
       getResults: getResults,
       loadMoreResults: loadMoreResults,
-      getTotal: getTotal
+      getTotal: getTotal,
+      runFacetCount: runFacetCount
     };
   }());
 

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.schema.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.schema.js b/blur-console/src/main/webapp/js/blurconsole.schema.js
index 2fa553f..036bc1a 100644
--- a/blur-console/src/main/webapp/js/blurconsole.schema.js
+++ b/blur-console/src/main/webapp/js/blurconsole.schema.js
@@ -21,7 +21,7 @@ under the License.
 /*global blurconsole:false */
 blurconsole.schema = (function () {
   'use strict';
-    
+
     //----------------------------- Configuration and State -------------------------
   var
     configMap = {

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.search.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.search.js b/blur-console/src/main/webapp/js/blurconsole.search.js
index db30b84..fd63b5c 100644
--- a/blur-console/src/main/webapp/js/blurconsole.search.js
+++ b/blur-console/src/main/webapp/js/blurconsole.search.js
@@ -193,13 +193,17 @@ blurconsole.search = (function () {
     });
     jqueryMap.$optionsTrigger.on('shown.bs.popover', _updateOptionPopover);
     $(document).on('change', '.popover select', _persistOptions);
-    jqueryMap.$facetTrigger.on('click', _popupFacetDialog);
+    //jqueryMap.$facetTrigger.on('click', _popupFacetDialog);
     jqueryMap.$tableField.on('change', function(evt) {
       stateMap.$currentTable = $(evt.currentTarget).val();
     });
     jqueryMap.$resultsHolder.on('click', 'a.fetchRow', _fetchRow);
     jqueryMap.$historyTrigger.on('click', _showHistory);
     $(document).on('click.history', '.rerunhistory', _setupSearchFromHistory);
+    jqueryMap.$facetTrigger.on('click', function() {
+      $.gevent.publish('facet-show', {table: stateMap.$currentTable, query: stateMap.$currentQuery});
+      return false;
+    });
   }
 
   function _unregisterPageEvents() {
@@ -216,7 +220,7 @@ blurconsole.search = (function () {
       $(document).off('click.history');
       jqueryMap.$tableField.off('change');
       jqueryMap.$historyTrigger.off('click');
-      //jqueryMap.$facetTrigger.off('click');
+      jqueryMap.$facetTrigger.off('click');
     }
   }
 
@@ -451,7 +455,7 @@ blurconsole.search = (function () {
   function _drawResults(evt, families) {
     var results = blurconsole.model.search.getResults();
     jqueryMap.$countHolder.html('<small>Found ' + blurconsole.utils.formatNumber(blurconsole.model.search.getTotal())
+ ' total results</small>');
-    //jqueryMap.$facetTrigger.show();
+    jqueryMap.$facetTrigger.show();
 
     if (families != null) {
       $.each(families, function(i, fam) {
@@ -551,7 +555,12 @@ blurconsole.search = (function () {
       if (tables.length > 0) {
         optGroupString = '<optgroup label="' + cluster + '">';
         $.each(tables, function(t, table){
-          optGroupString += '<option value="' + table.name + '"' + (table.name === stateMap.$currentTable
? ' selected' : '') + '>' + table.name + '</option>';
+          var isSelected = false;
+          if ((stateMap.$currentTable === null && t === 0) || table.name === stateMap.$currentTable)
{
+            isSelected = true;
+            stateMap.$currentTable = table.name;
+          }
+          optGroupString += '<option value="' + table.name + '"' + (isSelected ? ' selected'
: '') + '>' + table.name + '</option>';
         });
         optGroupString += '</optgroup>';
         jqueryMap.$tableField.append(optGroupString);
@@ -564,10 +573,29 @@ blurconsole.search = (function () {
     }
   }
 
-  function _popupFacetDialog() {
-    jqueryMap.facetModal = $(blurconsole.browserUtils.modal('facetDialog', 'Facets for Current
Search', 'TBD', null, 'large'));
-    jqueryMap.facetModal.modal();
-  }
+  // function _popupFacetDialog() {
+  //   var markup = '<div class="well">';
+    
+//     <form class="form-inline" role="form">
+//   <div class="form-group">
+//     <label class="sr-only" for="exampleInputEmail2">Email address</label>
+//     <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter
email">
+//   </div>
+//   <div class="form-group">
+//     <label class="sr-only" for="exampleInputPassword2">Password</label>
+//     <input type="password" class="form-control" id="exampleInputPassword2" placeholder="Password">
+//   </div>
+//   <div class="checkbox">
+//     <label>
+//       <input type="checkbox"> Remember me
+//     </label>
+//   </div>
+//   <button type="submit" class="btn btn-default">Sign in</button>
+// </form>
+    // var markup = '<div class="well"><input type="text" id="facetQueryField" placeholder="Enter
facet query" ></textarea><button class="btn btn-primary" id="facetCountTrigger">Go</button></div><hr/><div
id="facetResultHolder"></div>';
+  //   jqueryMap.facetModal = $(blurconsole.browserUtils.modal('facetDialog', 'Facets for
Current Search', markup, null, 'large'));
+  //   jqueryMap.facetModal.modal();
+  // }
 
   function _showHistory() {
     var history = _getHistoryList();

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.shell.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.shell.js b/blur-console/src/main/webapp/js/blurconsole.shell.js
index 0c063c8..42cb870 100644
--- a/blur-console/src/main/webapp/js/blurconsole.shell.js
+++ b/blur-console/src/main/webapp/js/blurconsole.shell.js
@@ -155,6 +155,7 @@ blurconsole.shell = (function () {
     _setJqueryMap();
 
     blurconsole.schema.initModule();
+    blurconsole.facets.initModule();
     blurconsole.logging.initModule();
 
     $('#dashboard_tab').show();

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/js/blurconsole.utils.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/js/blurconsole.utils.js b/blur-console/src/main/webapp/js/blurconsole.utils.js
index 3534207..1528bb9 100644
--- a/blur-console/src/main/webapp/js/blurconsole.utils.js
+++ b/blur-console/src/main/webapp/js/blurconsole.utils.js
@@ -20,7 +20,7 @@ under the License.
 /*global blurconsole:false, Intl:false */
 blurconsole.utils = (function(){
   'use strict';
-    
+
     //-------------------------- Public API ----------------------------
   function inject(collection, initial, block) {
     if (collection === null || collection.length === 0) {

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/less/blurconsole.less
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/less/blurconsole.less b/blur-console/src/main/webapp/less/blurconsole.less
index d9fa4b0..226a549 100644
--- a/blur-console/src/main/webapp/less/blurconsole.less
+++ b/blur-console/src/main/webapp/less/blurconsole.less
@@ -30,3 +30,4 @@ under the License.
 @import 'blurconsole.queries.less';
 @import 'blurconsole.search.less';
 @import 'typeahead';
+@import 'tagmanager';

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/less/blurconsole.search.less
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/less/blurconsole.search.less b/blur-console/src/main/webapp/less/blurconsole.search.less
index 806c5f6..73bfaec 100644
--- a/blur-console/src/main/webapp/less/blurconsole.search.less
+++ b/blur-console/src/main/webapp/less/blurconsole.search.less
@@ -68,4 +68,9 @@ input.spinner {
   background-image: url("../img/ajax-loader.gif");
   background-repeat: no-repeat;
   background-position: right center;
+}
+
+#facetQueryField {
+  width: 500px;
+  height: 100px;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/less/tagmanager.less
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/less/tagmanager.less b/blur-console/src/main/webapp/less/tagmanager.less
new file mode 100644
index 0000000..76aad99
--- /dev/null
+++ b/blur-console/src/main/webapp/less/tagmanager.less
@@ -0,0 +1,195 @@
+//
+// Bootstrap TagManager
+// --------------------------------------------------
+
+// Tag Variables
+// --------------------------------------------------
+
+// Colors
+// -------------------------
+
+@white:                 #ffffff;
+@black:                 #000000;
+@gray:                  #555555;
+@grayDark:              #333333;
+
+@textColor:             @grayDark;
+
+@tagText:               @gray;
+@tagBackground:         #f5f5f5;
+@tagBorder:             #bbb;
+
+@tagWarningText:        #945203;
+@tagWarningBackground:  #f2c889;
+@tagWarningBorder:      #f0a12f;
+
+@tagErrorText:          #84212e;
+@tagErrorBackground:    #e69ca6;
+@tagErrorBorder:        #d24a5d;
+
+@tagSuccessText:        #638421;
+@tagSuccessBackground:  #cde69c;
+@tagSuccessBorder:      #a5d24a;
+
+@tagInfoText:           #4594b5;
+@tagInfoBackground:     #c5eefa;
+@tagInfoBorder:         #5dc8f7;
+
+@tagInverseText:        #ccc;
+@tagInverseBackground:  @gray;
+@tagInverseBorder:      @grayDark;
+
+@tagDisabledText:       #aaa;
+@tagDisabledBackground: #e6e6e6;
+@tagDisabledBorder:     #ccc;
+
+// Sizing
+// -------------------------
+
+@tagFontSize:           13px;
+@tagFontSizeLarge:      @tagFontSize * 1.25; // ~16px
+@tagFontSizeSmall:      @tagFontSize * 0.85; // ~11px
+@tagFontSizeMini:       @tagFontSize * 0.75; // ~10px
+
+@tagPadding:            4px;
+@tagMargin:             5px;
+
+@borderRadiusSmall:     3px;
+@baseBorderRadius:      4px;
+
+@baseLineHeight:        20px;
+
+// Tag Classes
+// --------------------------------------------------
+
+// Fonts
+// --------------------------------------------------
+
+@sansFontFamily:        "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+// Base tag class
+// -------------------------
+
+.tm-tag {
+  color: @tagText;
+  background-color: @tagBackground;
+  border: @tagBorder 1px solid;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset;
+  display: inline-block;
+  border-radius: @borderRadiusSmall;
+  font-family: @sansFontFamily;
+  font-size: @tagFontSize;
+  margin: 0 @tagMargin @tagMargin 0;
+  padding: @tagPadding;
+  text-decoration: none;
+  transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+  -moz-transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+  -webkit-transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+  vertical-align: middle;
+
+  // Remove button
+  // -------------------------
+
+  .tm-tag-remove {
+    color: @black;
+    font-weight: bold;
+    margin-left: @tagPadding;
+    opacity: 0.2;
+    &:hover	{
+      color: @black;
+      text-decoration: none;
+      opacity: 0.4;
+    }
+  }
+
+  // Semantic Colors
+  // -------------------------
+
+  &.tm-tag-warning {
+    color: @tagWarningText;
+    background-color: @tagWarningBackground;
+    border-color: @tagWarningBorder;
+  }
+  &.tm-tag-error {
+    color: @tagErrorText;
+    background-color: @tagErrorBackground;
+    border-color: @tagErrorBorder;
+  }
+  &.tm-tag-success {
+    color: @tagSuccessText;
+    background-color: @tagSuccessBackground;
+    border-color: @tagSuccessBorder;
+  }
+  &.tm-tag-info {
+    color: @tagInfoText;
+    background-color: @tagInfoBackground;
+    border-color: @tagInfoBorder;
+  }
+  &.tm-tag-inverse {
+    color: @tagInverseText;
+    background-color: @tagInverseBackground;
+    border-color: @tagInverseBorder;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset;
+    .tm-tag-remove {
+      color: @white;
+    }
+  }
+
+  // Sizes
+  // -------------------------
+
+  &.tm-tag-large {
+    font-size: @tagFontSizeLarge;
+    border-radius: @baseBorderRadius;
+    padding: 11px 7px;
+  }
+  &.tm-tag-small {
+    font-size: @tagFontSizeSmall;
+    border-radius: @borderRadiusSmall;
+    padding: 2px 4px;
+  }
+  &.tm-tag-mini {
+    font-size: @tagFontSizeMini;
+    border-radius: 2px;
+    padding: 0px 2px;
+  }
+
+  // Miscellaneous Styles
+  // -------------------------
+
+  &.tm-tag-plain {
+    color: @textColor;
+    box-shadow: none;
+    background: none;
+    border: none;
+  }
+  &.tm-tag-disabled {
+    color: @tagDisabledText;
+    background-color: @tagDisabledBackground;
+    border-color: @tagDisabledBorder;
+    box-shadow: none;
+    .tm-tag-remove {
+      display: none;
+    }
+  }
+}
+
+// Forms
+// --------------------------------------------------
+
+// Input style (Recommended)
+// -------------------------
+
+input[type="text"].tm-input {
+  margin-bottom: @tagMargin;
+}
+
+// Form wrappers (Optional)
+// -------------------------
+
+.control-group.tm-group {
+  margin-bottom: (@baseLineHeight / 2) - @tagMargin;
+}
+.form-horizontal .control-group.tm-group {
+  margin-bottom: @baseLineHeight - @tagMargin;
+}

http://git-wip-us.apache.org/repos/asf/incubator-blur/blob/cfcb73b5/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
----------------------------------------------------------------------
diff --git a/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js b/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
new file mode 100644
index 0000000..03e7ce6
--- /dev/null
+++ b/blur-console/src/main/webapp/libs/tagmanager/tagmanager.js
@@ -0,0 +1,514 @@
+/* ===================================================
+ * tagmanager.js v3.0.1
+ * http://welldonethings.com/tags/manager
+ * ===================================================
+ * Copyright 2012 Max Favilli
+ *
+ * Licensed under the Mozilla Public License, Version 2.0 You may not use this work except
in compliance with the License.
+ *
+ * http://www.mozilla.org/MPL/2.0/
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+(function($) {
+
+    "use strict";
+
+    var defaults = {
+        prefilled: null,
+        CapitalizeFirstLetter: false,
+        preventSubmitOnEnter: true,     // deprecated
+        isClearInputOnEsc: true,        // deprecated
+        externalTagId: false,
+        prefillIdFieldName: 'Id',
+        prefillValueFieldName: 'Value',
+        AjaxPush: null,
+        AjaxPushAllTags: null,
+        AjaxPushParameters: null,
+        delimiters: [9, 13, 44],        // tab, enter, comma
+        backspace: [8],
+        maxTags: 0,
+        hiddenTagListName: null,        // deprecated
+        hiddenTagListId: null,          // deprecated
+        replace: true,
+        output: null,
+        deleteTagsOnBackspace: true,    // deprecated
+        tagsContainer: null,
+        tagCloseIcon: 'x',
+        tagClass: '',
+        validator: null,
+        onlyTagList: false,
+        tagList: null,
+        fillInputOnTagRemove: false
+    },
+
+    publicMethods = {
+        pushTag : function (tag, ignoreEvents, externalTagId) {
+            var $self = $(this), opts = $self.data('opts'), alreadyInList, tlisLowerCase,
max, tagId,
+            tlis = $self.data("tlis"), tlid = $self.data("tlid"), idx, newTagId, newTagRemoveId,
escaped,
+            html, $el, lastTagId, lastTagObj;
+
+            tag = privateMethods.trimTag(tag, opts.delimiterChars);
+
+            if (!tag || tag.length <= 0) { return; }
+
+            // check if restricted only to the tagList suggestions
+            if (opts.onlyTagList && undefined !== opts.tagList ){
+
+                //if the list has been updated by look pushed tag in the tagList. if not
found return
+                if (opts.tagList){
+                    var $tagList = opts.tagList;
+
+                    // change each array item to lower case
+                    $.each($tagList, function(index, item) {
+                        $tagList[index] = item.toLowerCase();
+                    });
+                    var suggestion = $.inArray(tag.toLowerCase(), $tagList);
+
+                    if ( -1 === suggestion ) {
+                        //console.log("tag:" + tag + " not in tagList, not adding it");
+                        return;
+                    } 
+                }
+
+            }
+
+            if (opts.CapitalizeFirstLetter && tag.length > 1) {
+                tag = tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase();
+            }
+
+            // call the validator (if any) and do not let the tag pass if invalid
+            if (opts.validator && !opts.validator(tag)) {
+                $self.trigger('tm:invalid', tag)
+                return;
+            }
+
+            // dont accept new tags beyond the defined maximum
+            if (opts.maxTags > 0 && tlis.length >= opts.maxTags) { return;
}
+
+            alreadyInList = false;
+            //use jQuery.map to make this work in IE8 (pure JS map is JS 1.6 but IE8 only
supports JS 1.5)
+            tlisLowerCase = jQuery.map(tlis, function(elem) {
+                return elem.toLowerCase();
+            });
+
+            idx = $.inArray(tag.toLowerCase(), tlisLowerCase);
+
+            if (-1 !== idx) {
+                // console.log("tag:" + tag + " !!already in list!!");
+                alreadyInList = true;
+            }
+
+            if (alreadyInList) {
+                $self.trigger('tm:duplicated', tag);
+                if (opts.blinkClass) {
+                    for (var i = 0; i < 6; ++i) {
+                        $("#" + $self.data("tm_rndid") + "_" + tlid[idx]).queue(function(next)
{
+                            $(this).toggleClass(opts.blinkClass);
+                            next();
+                        }).delay(100);
+                    }
+                } else {
+                    $("#" + $self.data("tm_rndid") + "_" + tlid[idx]).stop()
+                        .animate({backgroundColor: opts.blinkBGColor_1}, 100)
+                        .animate({backgroundColor: opts.blinkBGColor_2}, 100)
+                        .animate({backgroundColor: opts.blinkBGColor_1}, 100)
+                        .animate({backgroundColor: opts.blinkBGColor_2}, 100)
+                        .animate({backgroundColor: opts.blinkBGColor_1}, 100)
+                        .animate({backgroundColor: opts.blinkBGColor_2}, 100);
+                }
+            } else {
+                if (opts.externalTagId === true) {
+                    if (externalTagId === undefined) {
+                        $.error('externalTagId is not passed for tag -' + tag);
+                    }
+                    tagId = externalTagId;
+                } else {
+                    max = Math.max.apply(null, tlid);
+                    max = max === -Infinity ? 0 : max;
+
+                    tagId = ++max;
+                }
+                if (!ignoreEvents) { $self.trigger('tm:pushing', [tag, tagId]); }
+                tlis.push(tag);
+                tlid.push(tagId);
+
+                if (!ignoreEvents)
+                    if (opts.AjaxPush !== null && opts.AjaxPushAllTags == null) {
+                        if ($.inArray(tag, opts.prefilled) === -1) {
+                            $.post(opts.AjaxPush, $.extend({tag: tag}, opts.AjaxPushParameters));
+                        }
+                    }
+
+                // console.log("tagList: " + tlis);
+
+                newTagId = $self.data("tm_rndid") + '_' + tagId;
+                newTagRemoveId = $self.data("tm_rndid") + '_Remover_' + tagId;
+                escaped = $("<span/>").text(tag).html();
+
+                html = '<span class="' + privateMethods.tagClasses.call($self) + '" id="'
+ newTagId + '">';
+                html+= '<span>' + escaped + '</span>';
+                html+= '<a href="#" class="tm-tag-remove" id="' + newTagRemoveId + '"
TagIdToRemove="' + tagId + '">';
+                html+= opts.tagCloseIcon + '</a></span> ';
+                $el = $(html);
+
+                if (opts.tagsContainer !== null) {
+                    $(opts.tagsContainer).append($el);
+                } else {
+                    if (tlid.length > 1) {
+                        lastTagObj = $self.siblings("#" + $self.data("tm_rndid") + "_" +
tlid[tlid.length - 2]);
+                        lastTagObj.after($el);
+                    } else {
+                        $self.before($el);
+                    }
+                }
+
+                $el.find("#" + newTagRemoveId).on("click", $self, function(e) {
+                    e.preventDefault();
+                    var TagIdToRemove = parseInt($(this).attr("TagIdToRemove"));
+                    privateMethods.spliceTag.call($self, TagIdToRemove, e.data);
+                });
+
+                privateMethods.refreshHiddenTagList.call($self);
+
+                if (!ignoreEvents) { $self.trigger('tm:pushed', [tag, tagId]); }
+
+                privateMethods.showOrHide.call($self);
+                //if (tagManagerOptions.maxTags > 0 && tlis.length >= tagManagerOptions.maxTags)
{
+                //  obj.hide();
+                //}
+            }
+            $self.val("");
+        },
+
+        popTag : function () {
+            var $self = $(this), tagId, tagBeingRemoved,
+            tlis = $self.data("tlis"),
+            tlid = $self.data("tlid");
+
+            if (tlid.length > 0) {
+              tagId = tlid.pop();
+
+              tagBeingRemoved = tlis[tlis.length - 1];
+              $self.trigger('tm:popping', [tagBeingRemoved, tagId]);
+              tlis.pop();
+
+              // console.log("TagIdToRemove: " + tagId);
+              $("#" + $self.data("tm_rndid") + "_" + tagId).remove();
+              privateMethods.refreshHiddenTagList.call($self);
+              $self.trigger('tm:popped', [tagBeingRemoved, tagId]);
+              // console.log(tlis);
+            }
+        },
+
+        empty : function() {
+            var $self = $(this), tlis = $self.data("tlis"), tlid = $self.data("tlid"), tagId;
+
+            while (tlid.length > 0) {
+                tagId = tlid.pop();
+                tlis.pop();
+                // console.log("TagIdToRemove: " + tagId);
+                $("#" + $self.data("tm_rndid") + "_" + tagId).remove();
+                privateMethods.refreshHiddenTagList.call($self);
+                // console.log(tlis);
+            }
+            $self.trigger('tm:emptied', null);
+
+            privateMethods.showOrHide.call($self);
+            //if (tagManagerOptions.maxTags > 0 && tlis.length < tagManagerOptions.maxTags)
{
+            //  obj.show();
+            //}
+        },
+
+        tags : function() {
+            var $self = this, tlis = $self.data("tlis");
+            return tlis;
+        }
+    },
+
+    privateMethods = {
+        showOrHide : function () {
+            var $self = this, opts = $self.data('opts'), tlis = $self.data("tlis");
+
+            if (opts.maxTags > 0 && tlis.length < opts.maxTags) {
+                $self.show();
+                $self.trigger('tm:show');
+            }
+
+            if (opts.maxTags > 0 && tlis.length >= opts.maxTags) {
+                $self.hide();
+                $self.trigger('tm:hide');
+            }
+        },
+
+        tagClasses : function () {
+            var $self = $(this), opts = $self.data('opts'), tagBaseClass = opts.tagBaseClass,
+            inputBaseClass = opts.inputBaseClass, cl;
+            // 1) default class (tm-tag)
+            cl = tagBaseClass;
+            // 2) interpolate from input class: tm-input-xxx --> tm-tag-xxx
+            if ($self.attr('class')) {
+                $.each($self.attr('class').split(' '), function (index, value) {
+                    if (value.indexOf(inputBaseClass + '-') !== -1) {
+                        cl += ' ' + tagBaseClass + value.substring(inputBaseClass.length);
+                    }
+                });
+            }
+            // 3) tags from tagClass option
+            cl += (opts.tagClass ? ' ' + opts.tagClass : '');
+            return cl;
+        },
+
+        trimTag : function (tag, delimiterChars) {
+            var i;
+            tag = $.trim(tag);
+            // truncate at the first delimiter char
+            i = 0;
+            for (i; i < tag.length; i++) {
+                if ($.inArray(tag.charCodeAt(i), delimiterChars) !== -1) { break; }
+            }
+            return tag.substring(0, i);
+        },
+
+        refreshHiddenTagList : function () {
+            var $self = $(this), tlis = $self.data("tlis"), lhiddenTagList = $self.data("lhiddenTagList");
+
+            if (lhiddenTagList) {
+                $(lhiddenTagList).val(tlis.join($self.data('opts').baseDelimiter)).change();
+            }
+
+            $self.trigger('tm:refresh', tlis.join($self.data('opts').baseDelimiter));
+        },
+
+        killEvent : function (e) {
+            e.cancelBubble = true;
+            e.returnValue = false;
+            e.stopPropagation();
+            e.preventDefault();
+        },
+
+        keyInArray : function (e, ary) {
+            return $.inArray(e.which, ary) !== -1;
+        },
+
+        applyDelimiter : function (e) {
+            var $self = $(this);
+            publicMethods.pushTag.call($self,$(this).val());
+            e.preventDefault();
+        },
+
+        prefill: function (pta) {
+            var $self = $(this);
+            var opts = $self.data('opts')
+            $.each(pta, function (key, val) {
+                if (opts.externalTagId === true) {
+                    publicMethods.pushTag.call($self, val[opts.prefillValueFieldName], true,
val[opts.prefillIdFieldName]);
+                } else {
+                    publicMethods.pushTag.call($self, val, true);
+                }
+            });
+        },
+
+        pushAllTags : function (e, tag) {
+            var $self = $(this), opts = $self.data('opts'), tlis = $self.data("tlis");
+            if (opts.AjaxPushAllTags) {
+                if (e.type !== 'tm:pushed' || $.inArray(tag, opts.prefilled) === -1) {
+                    $.post(opts.AjaxPush, $.extend({ tags: tlis.join(opts.baseDelimiter)
}, opts.AjaxPushParameters));
+                }
+            }
+        },
+
+        spliceTag : function (tagId) {
+            var $self = this, tlis = $self.data("tlis"), tlid = $self.data("tlid"), idx =
$.inArray(tagId, tlid),
+            tagBeingRemoved;
+
+            // console.log("TagIdToRemove: " + tagId);
+            // console.log("position: " + idx);
+
+            if (-1 !== idx) {
+                tagBeingRemoved = tlis[idx];
+                $self.trigger('tm:splicing', [tagBeingRemoved, tagId]);
+                $("#" + $self.data("tm_rndid") + "_" + tagId).remove();
+                tlis.splice(idx, 1);
+                tlid.splice(idx, 1);
+                privateMethods.refreshHiddenTagList.call($self);
+                $self.trigger('tm:spliced', [tagBeingRemoved, tagId]);
+                // console.log(tlis);
+            }
+
+            privateMethods.showOrHide.call($self);
+            //if (tagManagerOptions.maxTags > 0 && tlis.length < tagManagerOptions.maxTags)
{
+            //  obj.show();
+            //}
+        },
+
+        init : function (options) {
+            var opts = $.extend({}, defaults, options), delimiters, keyNums;
+
+            opts.hiddenTagListName = (opts.hiddenTagListName === null)
+                ? 'hidden-' + this.attr('name')
+                : opts.hiddenTagListName;
+
+            delimiters = opts.delimeters || opts.delimiters; // 'delimeter' is deprecated
+            keyNums = [9, 13, 17, 18, 19, 37, 38, 39, 40]; // delimiter values to be handled
as key codes
+            opts.delimiterChars = [];
+            opts.delimiterKeys = [];
+
+            $.each(delimiters, function (i, v) {
+                if ($.inArray(v, keyNums) !== -1) {
+                    opts.delimiterKeys.push(v);
+                } else {
+                    opts.delimiterChars.push(v);
+                }
+            });
+
+            opts.baseDelimiter = String.fromCharCode(opts.delimiterChars[0] || 44);
+            opts.tagBaseClass = 'tm-tag';
+            opts.inputBaseClass = 'tm-input';
+
+            if (!$.isFunction(opts.validator)) { opts.validator = null; }
+
+            this.each(function() {
+                var $self = $(this), hiddenObj ='', rndid ='', albet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+                // prevent double-initialization of TagManager
+                if ($self.data('tagManager')) { return false; }
+                $self.data('tagManager', true);
+
+                for (var i = 0; i < 5; i++) {
+                  rndid += albet.charAt(Math.floor(Math.random() * albet.length));
+                }
+
+                $self.data("tm_rndid", rndid);
+
+                // store instance-specific data in the DOM object
+                $self.data('opts',opts)
+                    .data('tlis', []) //list of string tags
+                    .data('tlid', []); //list of ID of the string tags
+
+                if (opts.output === null) {
+                    hiddenObj = $('<input/>', {
+                        type: 'hidden',
+                        name: opts.hiddenTagListName
+                    });
+                    $self.after(hiddenObj);
+                    $self.data("lhiddenTagList", hiddenObj);
+                } else {
+                    $self.data("lhiddenTagList", $(opts.output));
+                }
+
+                if (opts.AjaxPushAllTags) {
+                    $self.on('tm:spliced', privateMethods.pushAllTags);
+                    $self.on('tm:popped', privateMethods.pushAllTags);
+                    $self.on('tm:pushed', privateMethods.pushAllTags);
+                }
+
+                // hide popovers on focus and keypress events
+                $self.on('focus keypress', function(e) {
+                    if ($(this).popover) { $(this).popover('hide'); }
+                });
+
+                // handle ESC (keyup used for browser compatibility)
+                if (opts.isClearInputOnEsc) {
+                    $self.on('keyup', function(e) {
+                        if (e.which === 27) {
+                            // console.log('esc detected');
+                            $(this).val('');
+                            privateMethods.killEvent(e);
+                        }
+                    });
+                }
+
+                $self.on('keypress', function(e) {
+                    // push ASCII-based delimiters
+                    if (privateMethods.keyInArray(e, opts.delimiterChars)) {
+                        privateMethods.applyDelimiter.call($self, e);
+                    }
+                });
+
+                $self.on('keydown', function(e) {
+                    // disable ENTER
+                    if (e.which === 13) {
+                        if (opts.preventSubmitOnEnter) {
+                            privateMethods.killEvent(e);
+                        }
+                    }
+
+                    // push key-based delimiters (includes <enter> by default)
+                    if (privateMethods.keyInArray(e, opts.delimiterKeys)) {
+                        privateMethods.applyDelimiter.call($self, e);
+                    }
+                });
+
+                // BACKSPACE (keydown used for browser compatibility)
+                if (opts.deleteTagsOnBackspace) {
+                    $self.on('keydown', function(e) {
+                        if (privateMethods.keyInArray(e, opts.backspace)) {
+                            // console.log("backspace detected");
+                            if ($(this).val().length <= 0) {
+                                publicMethods.popTag.call($self);
+                                privateMethods.killEvent(e);
+                            }
+                        }
+                    });
+                }
+
+                // on tag pop fill back the tag's content to the input field
+                if (opts.fillInputOnTagRemove) {
+                    $self.on('tm:popped', function(e, tag) {
+                        $(this).val(tag);
+                    });
+                }
+
+                $self.change(function(e) {
+                    if (!/webkit/.test(navigator.userAgent.toLowerCase())) {
+                        $self.focus();
+                    } // why?
+
+                    /* unimplemented mode to push tag on blur
+                     else if (tagManagerOptions.pushTagOnBlur) {
+                     console.log('change: pushTagOnBlur ' + tag);
+                     pushTag($(this).val());
+                     } */
+                    privateMethods.killEvent(e);
+                });
+
+                if (opts.prefilled !== null) {
+                    if (typeof (opts.prefilled) === "object") {
+                        privateMethods.prefill.call($self, opts.prefilled);
+                    } else if (typeof (opts.prefilled) === "string") {
+                        privateMethods.prefill.call($self, opts.prefilled.split(opts.baseDelimiter));
+                    } else if (typeof (opts.prefilled) === "function") {
+                        privateMethods.prefill.call($self, opts.prefilled());
+                    }
+                } else if (opts.output !== null) {
+                    if ($(opts.output) && $(opts.output).val()) { var existing_tags
= $(opts.output); }
+                    privateMethods.prefill.call($self,$(opts.output).val().split(opts.baseDelimiter));
+                }
+
+            });
+
+            return this;
+        }
+    };
+
+    $.fn.tagsManager = function(method) {
+        var $self = $(this);
+
+        if (!(0 in this)) { return this; }
+
+        if ( publicMethods[method] ) {
+            return publicMethods[method].apply( $self, Array.prototype.slice.call(arguments,
1) );
+        } else if ( typeof method === 'object' || ! method ) {
+            return privateMethods.init.apply( this, arguments );
+        } else {
+            $.error( 'Method ' +  method + ' does not exist.' );
+            return false;
+        }
+    };
+
+}(jQuery));


Mime
View raw message