You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by sh...@apache.org on 2017/01/02 13:44:07 UTC

svn commit: r1776930 [3/19] - in /ofbiz/trunk/specialpurpose: lucene/ lucene/src/main/java/org/apache/ofbiz/content/search/ solr/ solr/src/main/java/org/apache/ofbiz/solr/webapp/ solr/webapp/solr/ solr/webapp/solr/WEB-INF/ solr/webapp/solr/css/ solr/we...

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/cores.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/cores.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/cores.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/cores.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,478 @@
+/*
+ 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.
+*/
+
+// @todo test optimize (delete stuff, watch button appear, test button/form)
+solrAdminApp.controller('CoreAdminController',
+    function($scope, $routeParams, $location, $timeout, $route, Cores, Update, Constants){
+      $scope.resetMenu("cores", Constants.IS_ROOT_PAGE);
+      $scope.selectedCore = $routeParams.corename; // use 'corename' not 'core' to distinguish from /solr/:core/
+      $scope.refresh = function() {
+        Cores.get(function(data) {
+          var coreCount = 0;
+          var cores = data.status;
+          for (_obj in cores) coreCount++;
+          $scope.hasCores = coreCount >0;
+          if (!$scope.selectedCore && coreCount==0) {
+            $scope.showAddCore();
+            return;
+          } else if (!$scope.selectedCore) {
+            for (firstCore in cores) break;
+            $scope.selectedCore = firstCore;
+            $location.path("/~cores/" + $scope.selectedCore).replace();
+          }
+          $scope.core = cores[$scope.selectedCore];
+          $scope.corelist = [];
+          $scope.swapCorelist = [];
+          for (var core in cores) {
+             $scope.corelist.push(cores[core]);
+            if (cores[core] != $scope.core) {
+              $scope.swapCorelist.push(cores[core]);
+            }
+          }
+          if ($scope.swapCorelist.length>0) {
+            $scope.swapOther = $scope.swapCorelist[0].name;
+          }
+        });
+      };
+      $scope.showAddCore = function() {
+        $scope.hideAll();
+        $scope.showAdd = true;
+        $scope.newCore = {
+          name: "new_core",
+          dataDir: "data",
+          instanceDir: "new_core",
+          config: "solrconfig.xml",
+          schema: "schema.xml",
+          collection: "",
+          shard: ""
+        };
+      };
+
+      $scope.addCore = function() {
+        if (!$scope.newCore.name) {
+          $scope.addMessage = "Please provide a core name";
+        } else if (false) { //@todo detect whether core exists
+          $scope.AddMessage = "A core with that name already exists";
+        } else {
+          var params = {
+            name: $scope.newCore.name,
+            instanceDir: $scope.newCore.instanceDir,
+            config: $scope.newCore.config,
+            schema: $scope.newCore.schema,
+            dataDir: $scope.newCore.dataDir
+          };
+          if ($scope.isCloud) {
+            params.collection = $scope.newCore.collection;
+            params.shard = $scope.newCore.shard;
+          }
+          Cores.add(params, function(data) {
+            $location.path("/~cores/" + $scope.newCore.name);
+            $scope.cancelAddCore();
+          });
+        }
+      };
+
+      $scope.cancelAddCore = function() {
+        delete $scope.addMessage;
+        $scope.showAdd = false
+      };
+
+      $scope.unloadCore = function() {
+        var answer = confirm( 'Do you really want to unload Core "' + $scope.selectedCore + '"?' );
+        if( !answer ) return;
+        Cores.unload({core: $scope.selectedCore}, function(data) {
+          $location.path("/~cores");
+        });
+      };
+
+      $scope.showRenameCore = function() {
+        $scope.hideAll();
+        $scope.showRename = true;
+      };
+
+      $scope.renameCore = function() {
+        if (!$scope.other) {
+          $scope.renameMessage = "Please provide a new name for the " + $scope.selectedCore + " core";
+        } else if ($scope.other == $scope.selectedCore) {
+          $scope.renameMessage = "New name must be different from the current one";
+        } else {
+          Cores.rename({core:$scope.selectedCore, other: $scope.other}, function(data) {
+            $location.path("/~cores/" + $scope.other);
+            $scope.cancelRename();
+          });
+        }
+      };
+
+      $scope.cancelRenameCore = function() {
+        $scope.showRename = false;
+        delete $scope.renameMessage;
+        $scope.other = "";
+      };
+
+      $scope.showSwapCores = function() {
+        $scope.hideAll();
+        $scope.showSwap = true;
+      };
+
+      $scope.swapCores = function() {
+        if (!$scope.swapOther) {
+          $scope.swapMessage = "Please select a core to swap with";
+        } else if ($scope.swapOther == $scope.selectedCore) {
+          $scope.swapMessage = "Cannot swap with the same core";
+        } else {
+          Cores.swap({core: $scope.selectedCore, other: $scope.swapOther}, function(data) {
+            $location.path("/~cores/" + $scope.swapOther);
+            delete $scope.swapOther;
+            $scope.cancelSwapCores();
+          });
+        }
+      };
+
+      $scope.cancelSwapCores = function() {
+        delete $scope.swapMessage;
+        $scope.showSwap = false;
+      }
+
+      $scope.reloadCore = function() {
+        if ($scope.initFailures[$scope.selectedCore]) {
+          delete $scope.initFailures[$scope.selectedCore];
+          $scope.showInitFailures = Object.keys(data.initFailures).length>0;
+        }
+        Cores.reload({core: $scope.selectedCore},
+          function(data) {
+            if (data.error) {
+              $scope.reloadFailure = true;
+              $timeout(function() {
+                $scope.reloadFailure = false;
+                $route.reload();
+              }, 1000);
+            } else {
+              $scope.reloadSuccess = true;
+              $timeout(function () {
+                $scope.reloadSuccess = false;
+                $route.reload();
+              }, 1000);
+            }
+          });
+      };
+
+      $scope.hideAll = function() {
+        $scope.showRename = false;
+        $scope.showAdd = false;
+        $scope.showSwap = false;
+      };
+
+      $scope.optimizeCore = function() {
+        Update.optimize({core: $scope.selectedCore},
+          function(successData) {
+            $scope.optimizeSuccess = true;
+            $timeout(function() {$scope.optimizeSuccess=false}, 1000);
+            $scope.refresh();
+          },
+          function(failureData) {
+            $scope.optimizeFailure = true;
+            $timeout(function () {$scope.optimizeFailure=false}, 1000);
+            $scope.refresh();
+          });
+      };
+
+      $scope.refresh();
+    }
+);
+
+/**************
+  'cores_load_data',
+  function( event, params )
+  {
+    $.ajax
+    (
+      {
+        url : app.config.solr_path + app.config.core_admin_path + '?wt=json',
+        dataType : 'json',
+        success : function( response, text_status, xhr )
+        {
+          if( params.only_failures )
+          {
+            app.check_for_init_failures( response );
+            return true;
+          }
+
+
+=========== NO CORES
+        error : function()
+        {
+          sammy.trigger
+          (
+            'cores_load_template',
+            {
+              content_element : content_element,
+              callback : function()
+              {
+                var cores_element = $( '#cores', content_element );
+                var navigation_element = $( '#navigation', cores_element );
+                var data_element = $( '#data', cores_element );
+                var core_data_element = $( '#core-data', data_element );
+                var index_data_element = $( '#index-data', data_element );
+
+                // layout
+
+                var ui_block = $( '#ui-block' );
+                var actions_element = $( '.actions', cores_element );
+                var div_action = $( 'div.action', actions_element );
+
+                ui_block
+                  .css( 'opacity', 0.7 )
+                  .width( cores_element.width() + 10 )
+                  .height( cores_element.height() );
+
+                if( $( '#cloud.global' ).is( ':visible' ) )
+                {
+                  $( '.cloud', div_action )
+                    .show();
+                }
+
+                $( 'button.action', actions_element )
+                  .die( 'click' )
+                  .live
+                  (
+                    'click',
+                    function( event )
+                    {
+                      var self = $( this );
+
+                      self
+                        .toggleClass( 'open' );
+
+                      $( '.action.' + self.attr( 'id' ), actions_element )
+                        .trigger( 'open' );
+
+                      return false;
+                    }
+                  );
+
+                div_action
+                  .die( 'close' )
+                  .live
+                  (
+                    'close',
+                    function( event )
+                    {
+                      div_action.hide();
+                      ui_block.hide();
+                    }
+                  )
+                  .die( 'open' )
+                  .live
+                  (
+                    'open',
+                    function( event )
+                    {
+                      var self = $( this );
+                      var rel = $( '#' + self.data( 'rel' ) );
+
+                      self
+                        .trigger( 'close' )
+                        .show()
+                        .css( 'left', rel.position().left );
+
+                      ui_block
+                        .show();
+                    }
+                  );
+
+                $( 'form button.reset', actions_element )
+                  .die( 'click' )
+                  .live
+                  (
+                    'click',
+                    function( event )
+                    {
+                      $( this ).closest( 'div.action' )
+                        .trigger( 'close' );
+                    }
+                  );
+
+                $( 'form', div_action )
+                  .ajaxForm
+                  (
+                    {
+                      url : app.config.solr_path + app.config.core_admin_path + '?wt=json&indexInfo=false',
+                      dataType : 'json',
+                      beforeSubmit : function( array, form, options )
+                      {
+                        $( 'button[type="submit"] span', form )
+                          .addClass( 'loader' );
+                      },
+                      success : function( response, status_text, xhr, form )
+                      {
+                        delete app.cores_data;
+                        sammy.refresh();
+
+                        $( 'button.reset', form )
+                          .trigger( 'click' );
+                      },
+                      error : function( xhr, text_status, error_thrown )
+                      {
+                        var response = null;
+                        eval( 'response = ' + xhr.responseText + ';' );
+
+                        var error_elem = $( '.error', div_action.filter( ':visible' ) );
+                        error_elem.show();
+                        $( 'span', error_elem ).text( response.error.msg );
+                      },
+                      complete : function()
+                      {
+                        $( 'button span.loader', actions_element )
+                          .removeClass( 'loader' );
+                      }
+                    }
+                  );
+
+                // --
+
+                $( '#add', content_element )
+                  .trigger( 'click' );
+
+                $( '[data-rel="add"] input[type="text"]:first', content_element )
+                  .focus();
+              }
+            }
+          );
+        }
+      }
+    );
+  }
+);
+
+// #/~cores
+sammy.get
+(
+  /^#\/(~cores)\//,
+  function( context )
+  {
+    var content_element = $( '#content' );
+
+    var path_parts = this.path.match( /^(.+\/~cores\/)(.*)$/ );
+    var current_core = path_parts[2];
+
+    sammy.trigger
+    (
+      'cores_load_data',
+      {
+        error : function()
+        {
+          context.redirect( '#/' + context.params.splat[0] );
+        },
+        success : function( cores )
+        {
+          sammy.trigger
+          (
+            'cores_load_template',
+            {
+              content_element : content_element,
+              callback : function()
+              {
+                var cores_element = $( '#cores', content_element );
+                var navigation_element = $( '#navigation', cores_element );
+                var data_element = $( '#data', cores_element );
+                var core_data_element = $( '#core-data', data_element );
+                var index_data_element = $( '#index-data', data_element );
+
+                cores_element
+                  .removeClass( 'empty' );
+
+                var core_data = cores[current_core];
+                var core_basepath = $( '#' + current_core, app.menu_element ).attr( 'data-basepath' );
+
+                var core_names = [];
+                var core_selects = $( '#actions select', cores_element );
+
+                $( 'option[value="' + current_core + '"]', core_selects.filter( '.other' ) )
+                  .remove();
+
+                $( 'input[data-core="current"]', cores_element )
+                  .val( current_core );
+
+                // layout
+
+                var ui_block = $( '#ui-block' );
+                var actions_element = $( '.actions', cores_element );
+                var div_action = $( 'div.action', actions_element );
+
+                ui_block
+                  .css( 'opacity', 0.7 )
+                  .width( cores_element.width() + 10 )
+                  .height( cores_element.height() );
+
+                if( $( '#cloud.global' ).is( ':visible' ) )
+                {
+                  $( '.cloud', div_action )
+                    .show();
+                }
+
+                var form_callback = {
+
+                  rename : function( form, response )
+                  {
+                    var url = path_parts[1] + $( 'input[name="other"]', form ).val();
+                    context.redirect( url );
+                  }
+
+                };
+
+                $( 'form', div_action )
+                  .ajaxForm
+                  (
+                    {
+                      url : app.config.solr_path + app.config.core_admin_path + '?wt=json&indexInfo=false',
+                      success : function( response, status_text, xhr, form )
+                      {
+                        var action = $( 'input[name="action"]', form ).val().toLowerCase();
+
+                        delete app.cores_data;
+
+                        if( form_callback[action] )
+                        {
+                         form_callback[action]( form, response );
+                        }
+                        else
+                        {
+                          sammy.refresh();
+                        }
+
+                        $( 'button.reset', form )
+                          .trigger( 'click' );
+                      },
+                  );
+
+                $( '#actions #unload', cores_element )
+                      var ret = confirm( 'Do you really want to unload Core "' + current_core + '"?' );
+                      if( !ret )
+                        return false;
+
+                          url : app.config.solr_path + app.config.core_admin_path + '?wt=json&action=UNLOAD&core=' + current_core,
+                          success : function( response, text_status, xhr )
+                          {
+                            delete app.cores_data;
+                            context.redirect( path_parts[1].substr( 0, path_parts[1].length - 1 ) );
+                          },
+
+                optimize_button
+                          url : core_basepath + '/update?optimize=true&waitFlush=true&wt=json',
+                          success : function( response, text_status, xhr )
+
+******/

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/dataimport.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/dataimport.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/dataimport.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/dataimport.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,303 @@
+/*
+ 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.
+*/
+
+var dataimport_timeout = 2000;
+
+solrAdminApp.controller('DataImportController',
+    function($scope, $rootScope, $routeParams, $location, $timeout, $interval, $cookies, Mbeans, DataImport, Constants) {
+        $scope.resetMenu("dataimport", Constants.IS_COLLECTION_PAGE);
+
+        $scope.refresh = function () {
+            Mbeans.info({core: $routeParams.core, cat: 'QUERYHANDLER'}, function (data) {
+                var mbeans = data['solr-mbeans'][1];
+                $scope.handlers = [];
+                for (var key in mbeans) {
+                    if (mbeans[key]['class'] !== key && mbeans[key]['class'] === 'org.apache.solr.handler.dataimport.DataImportHandler') {
+                        $scope.handlers.push(key);
+                    }
+                }
+                $scope.hasHandlers = $scope.handlers.length > 0;
+
+                if (!$routeParams.handler) {
+                    $location.path("/" + $routeParams.core + "/dataimport/" + $scope.handlers[0]);
+                } else {
+                    $scope.currentHandler = $routeParams.handler;
+                }
+            });
+
+            $scope.handler = $routeParams.handler;
+            if ($scope.handler && $scope.handler[0]=="/") {
+                $scope.handler = $scope.handler.substr(1);
+            }
+            if ($scope.handler) {
+                DataImport.config({core: $routeParams.core, name: $scope.handler}, function (data) {
+                    try {
+                        $scope.config = data.config;
+                        var xml = $.parseXML(data.config);
+                        $scope.entities = [];
+                        $('document > entity', xml).each(function (i, element) {
+                            $scope.entities.push($(element).attr('name'));
+                        });
+                        $scope.refreshStatus();
+                    } catch (err) {
+                        console.log(err);
+                    }
+                });
+            }
+            $scope.lastUpdate = "unknown";
+            $scope.lastUpdateUTC = "";
+        };
+
+        $scope.toggleDebug = function () {
+            $scope.isDebugMode = !$scope.isDebugMode;
+            if ($scope.isDebugMode) {
+                // also enable Debug checkbox
+                $scope.form.showDebug = true;
+            }
+            $scope.showConfiguration = true;
+        }
+
+        $scope.toggleConfiguration = function () {
+            $scope.showConfiguration = !$scope.showConfiguration;
+        }
+
+        $scope.toggleRawStatus = function () {
+            $scope.showRawStatus = !$scope.showRawStatus;
+        }
+
+        $scope.toggleRawDebug = function () {
+            $scope.showRawDebug = !$scope.showRawDebug;
+        }
+
+        $scope.reload = function () {
+            DataImport.reload({core: $routeParams.core, name: $scope.handler}, function () {
+                $scope.reloaded = true;
+                $timeout(function () {
+                    $scope.reloaded = false;
+                }, 5000);
+                $scope.refresh();
+            });
+        }
+
+        $scope.form = {
+            command: "full-import",
+            verbose: false,
+            clean: true,
+            commit: true,
+            optimize: false,
+            showDebug: false,
+            custom: "",
+            core: $routeParams.core
+        };
+
+        $scope.submit = function () {
+            var params = {};
+            for (var key in $scope.form) {
+                if (key == "showDebug") {
+                    if ($scope.form.showDebug) {
+                        params["debug"] = true;
+                    }
+                } else {
+                    params[key] = $scope.form[key];
+                }
+            }
+            if (params.custom.length) {
+                var customParams = $scope.form.custom.split("&");
+                for (var i in customParams) {
+                    var parts = customParams[i].split("=");
+                    params[parts[0]] = parts[1];
+                }
+            }
+            delete params.custom;
+
+            if ($scope.isDebugMode) {
+                params.dataConfig = $scope.config;
+            }
+
+            params.core = $routeParams.core;
+            params.name = $scope.handler;
+
+            DataImport.post(params, function (data) {
+                $scope.rawResponse = JSON.stringify(data, null, 2);
+                $scope.refreshStatus();
+            });
+        };
+
+        $scope.abort = function () {
+            $scope.isAborting = true;
+            DataImport.abort({core: $routeParams.core, name: $scope.handler}, function () {
+                $timeout(function () {
+                    $scope.isAborting = false;
+                    $scope.refreshStatus();
+                }, 4000);
+            });
+        }
+
+        $scope.refreshStatus = function () {
+
+            console.log("Refresh Status");
+
+            $scope.isStatusLoading = true;
+            DataImport.status({core: $routeParams.core, name: $scope.handler}, function (data) {
+                if (data[0] == "<") {
+                    $scope.hasHandlers = false;
+                    return;
+                }
+
+                var now = new Date();
+                $scope.lastUpdate = now.toTimeString().split(' ').shift();
+                $scope.lastUpdateUTC = now.toUTCString();
+                var messages = data.statusMessages;
+                var messagesCount = 0;
+                for( var key in messages ) { messagesCount++; }
+
+                if (data.status == 'busy') {
+                    $scope.status = "indexing";
+
+                    $scope.timeElapsed = data.statusMessages['Time Elapsed'];
+                    $scope.elapsedSeconds = parseSeconds($scope.timeElapsed);
+
+                    var info = $scope.timeElapsed ? 'Indexing since ' + $scope.timeElapsed : 'Indexing ...';
+                    $scope.info = showInfo(messages, true, info, $scope.elapsedSeconds);
+
+                } else if (messages.RolledBack) {
+                    $scope.status = "failure";
+                    $scope.info = showInfo(messages, true);
+                } else if (messages.Aborted) {
+                    $scope.status = "aborted";
+                    $scope.info = showInfo(messages, true, 'Aborting current Import ...');
+                } else if (data.status == "idle" && messagesCount != 0) {
+                    $scope.status = "success";
+                    $scope.info = showInfo(messages, true);
+                } else {
+                    $scope.status = "idle";
+                    $scope.info = showInfo(messages, false, 'No information available (idle)');
+                }
+
+                delete data.$promise;
+                delete data.$resolved;
+
+                $scope.rawStatus = JSON.stringify(data, null, 2);
+
+                $scope.isStatusLoading = false;
+                $scope.statusUpdated = true;
+                $timeout(function () {
+                    $scope.statusUpdated = false;
+                }, dataimport_timeout / 2);
+            });
+        };
+
+        $scope.updateAutoRefresh = function () {
+            $scope.autorefresh = !$scope.autorefresh;
+            $cookies.dataimport_autorefresh = $scope.autorefresh ? true : null;
+            if ($scope.autorefresh) {
+                $scope.refreshTimeout = $interval($scope.refreshStatus, dataimport_timeout);
+                var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
+                    $interval.cancel($scope.refreshTimeout);
+                    onRouteChangeOff();
+                });
+
+            } else if ($scope.refreshTimeout) {
+                $interval.cancel($scope.refreshTimeout);
+            }
+            $scope.refreshStatus();
+        };
+
+        $scope.refresh();
+
+});
+
+var showInfo = function (messages, showFull, info_text, elapsed_seconds) {
+
+    var info = {};
+    if (info_text) {
+        info.text = info_text;
+    } else {
+        info.text = messages[''] || '';
+        // format numbers included in status nicely
+        /* @todo this pretty printing is hard to work out how to do in an Angularesque way:
+        info.text = info.text.replace(/\d{4,}/g,
+            function (match, position, string) {
+                return app.format_number(parseInt(match, 10));
+            }
+        );
+        */
+
+        var time_taken_text = messages['Time taken'];
+        info.timeTaken = parseSeconds(time_taken_text);
+    }
+    info.showDetails = false;
+
+    if (showFull) {
+        if (!elapsed_seconds) {
+            var time_taken_text = messages['Time taken'];
+            elapsed_seconds = parseSeconds(time_taken_text);
+        }
+
+        info.showDetails = true;
+
+        var document_config = {
+            'Requests': 'Total Requests made to DataSource',
+            'Fetched': 'Total Rows Fetched',
+            'Skipped': 'Total Documents Skipped',
+            'Processed': 'Total Documents Processed'
+        };
+
+        info.docs = [];
+        for (var key in document_config) {
+            var value = parseInt(messages[document_config[key]], 10);
+            var doc = {desc: document_config[key], name: key, value: value};
+            if (elapsed_seconds && key != 'Skipped') {
+                doc.speed = Math.round(value / elapsed_seconds);
+            }
+            info.docs.push(doc);
+        }
+
+        var dates_config = {
+            'Started': 'Full Dump Started',
+            'Aborted': 'Aborted',
+            'Rolledback': 'Rolledback'
+        };
+
+        info.dates = [];
+        for (var key in dates_config) {
+            var value = messages[dates_config[key]];
+            if (value) {
+                value = value.replace(" ", "T")+".000Z";
+                console.log(value);
+                var date = {desc: dates_config[key], name: key, value: value};
+                info.dates.push(date);
+            }
+        }
+    }
+    return info;
+}
+
+var parseSeconds = function(time) {
+    var seconds = 0;
+    var arr = new String(time || '').split('.');
+    var parts = arr[0].split(':').reverse();
+
+    for (var i = 0; i < parts.length; i++) {
+        seconds += ( parseInt(parts[i], 10) || 0 ) * Math.pow(60, i);
+    }
+
+    if (arr[1] && 5 <= parseInt(arr[1][0], 10)) {
+        seconds++; // treat more or equal than .5 as additional second
+    }
+    return seconds;
+}

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/documents.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/documents.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/documents.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/documents.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,139 @@
+/*
+ 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.
+ */
+//helper for formatting JSON and others
+
+var DOC_PLACEHOLDER = '<doc>\n' +
+                '<field name="id">change.me</field>' +
+                '<field name="title">change.me</field>' +
+                '</doc>';
+
+var ADD_PLACEHOLDER = '<add>\n' + DOC_PLACEHOLDER + '</add>\n';
+
+solrAdminApp.controller('DocumentsController',
+    function($scope, $rootScope, $routeParams, $location, Luke, Update, FileUpload, Constants) {
+        $scope.resetMenu("documents", Constants.IS_COLLECTION_PAGE);
+
+        $scope.refresh = function () {
+            Luke.schema({core: $routeParams.core}, function(data) {
+                //TODO: handle dynamic fields
+                delete data.schema.fields._version_;
+                $scope.fields = Object.keys(data.schema.fields);
+            });
+            $scope.document = "";
+            $scope.handler = "/update";
+            $scope.type = "json";
+            $scope.commitWithin = 1000;
+            $scope.overwrite = true;
+            $scope.boost = "1.0";
+        };
+
+        $scope.refresh();
+
+        $scope.changeDocumentType = function () {
+            $scope.placeholder = "";
+            if ($scope.type == 'json') {
+                $scope.placeholder = '{"id":"change.me","title":"change.me"}';
+            } else if ($scope.type == 'csv') {
+                $scope.placeholder = "id,title\nchange.me,change.me";
+            } else if ($scope.type == 'solr') {
+                $scope.placeholder = ADD_PLACEHOLDER;
+            } else if ($scope.type == 'xml') {
+                $scope.placeholder = DOC_PLACEHOLDER;
+            }
+        };
+
+        $scope.addWizardField = function () {
+            if ($scope.document == "") $scope.document = "{}";
+            var doc = JSON.parse($scope.document);
+            doc[$scope.fieldName] = $scope.fieldData;
+            $scope.document = JSON.stringify(doc, null, '\t');
+            $scope.fieldData = "";
+        };
+
+        $scope.submit = function () {
+            var contentType = "";
+            var postData = "";
+            var params = {};
+            var doingFileUpload = false;
+
+            if ($scope.handler[0] == '/') {
+                params.handler = $scope.handler.substring(1);
+            } else {
+                params.handler = 'update';
+                params.qt = $scope.handler;
+            }
+
+            params.commitWithin = $scope.commitWithin;
+            params.boost = $scope.boost;
+            params.overwrite = $scope.overwrite;
+            params.core = $routeParams.core;
+            params.wt = "json";
+
+            if ($scope.type == "json" || $scope.type == "wizard") {
+                postData = "[" + $scope.document + "]";
+                contentType = "json";
+            } else if ($scope.type == "csv") {
+                postData = $scope.document;
+                contentType = "csv";
+            } else if ($scope.type == "xml") {
+                postData = "<add>" + $scope.document + "</add>";
+                contentType = "xml";
+            } else if ($scope.type == "upload") {
+                doingFileUpload = true;
+                params.raw = $scope.literalParams;
+            } else if ($scope.type == "solr") {
+                postData = $scope.document;
+                if (postData[0] == "<") {
+                    contentType = "xml";
+                } else if (postData[0] == "{" || postData[0] == '[') {
+                    contentType = "json";
+                } else {
+                    alert("Cannot identify content type")
+                }
+            }
+            if (!doingFileUpload) {
+                var callback = function (success) {
+                  $scope.responseStatus = "success";
+                  delete success.$promise;
+                  delete success.$resolved;
+                  $scope.response = JSON.stringify(success, null, '  ');
+                };
+                var failure = function (failure) {
+                    $scope.responseStatus = failure;
+                };
+                if (contentType == "json") {
+                  Update.postJson(params, postData, callback, failure);
+                } else if (contentType == "xml") {
+                  Update.postXml(params, postData, callback, failure);
+                } else if (contentType == "csv") {
+                  Update.postCsv(params, postData, callback, failure);
+                }
+            } else {
+                var file = $scope.fileUpload;
+                console.log('file is ' + JSON.stringify(file));
+                var uploadUrl = "/fileUpload";
+                FileUpload.upload(params, $scope.fileUpload, function (success) {
+                    $scope.responseStatus = "success";
+                    $scope.response = JSON.stringify(success, null, '  ');
+                }, function (failure) {
+                    $scope.responseStatus = "failure";
+                    $scope.response = JSON.stringify(failure, null, '  ');
+                });
+            }
+        }
+    });
+

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/files.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/files.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/files.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/files.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,100 @@
+/*
+ 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.
+*/
+
+var contentTypeMap = { xml : 'text/xml', html : 'text/html', js : 'text/javascript', json : 'application/json', 'css' : 'text/css' };
+var languages = {js: "javascript", xml:"xml", xsl:"xml", vm: "xml", html: "xml", json: "json", css: "css"};
+
+solrAdminApp.controller('FilesController',
+    function($scope, $rootScope, $routeParams, $location, Files, Constants) {
+        $scope.resetMenu("files", Constants.IS_COLLECTION_PAGE);
+
+        $scope.file = $location.search().file;
+        $scope.content = null;
+
+        $scope.baseurl = $location.protocol()+ "://" + $location.host() + ":" + $location.port();
+
+        $scope.refresh = function () {
+
+            var process = function (path, tree) {
+                var params = {core: $routeParams.core};
+                if (path.slice(-1) == '/') {
+                    params.file = path.slice(0, -1);
+                } else if (path!='') {
+                    params.file = path;
+                }
+
+                Files.list(params, function (data) {
+                    var filenames = Object.keys(data.files);
+                    filenames.sort();
+                    for (var i in filenames) {
+                        var file = filenames[i];
+                        var filedata = data.files[file];
+                        var state = undefined;
+                        var children = undefined;
+
+                        if (filedata.directory) {
+                            file = file + "/";
+                            if ($scope.file && $scope.file.indexOf(path + file) == 0) {
+                                state = "open";
+                            } else {
+                                state = "closed";
+                            }
+                            children = [];
+                            process(path + file, children);
+                        }
+                        tree.push({
+                            data: {
+                                title: file,
+                                attr: { id: path + file}
+                            },
+                            children: children,
+                            state: state
+                        });
+                    }
+                });
+            }
+            $scope.tree = [];
+            process("", $scope.tree);
+
+            if ($scope.file && $scope.file != '' && $scope.file.split('').pop()!='/') {
+                var extension;
+                if ($scope.file == "managed-schema") {
+                  extension = contentTypeMap['xml'];
+                } else {
+                  extension = $scope.file.match( /\.(\w+)$/)[1] || '';
+                }
+                var contentType = (contentTypeMap[extension] || 'text/plain' ) + ';charset=utf-8';
+
+                Files.get({core: $routeParams.core, file: $scope.file, contentType: contentType}, function(data) {
+                    $scope.content = data.data;
+                    $scope.url = $scope.baseurl + data.config.url + "?" + $.param(data.config.params);
+                    if (contentType.indexOf("text/plain") && (data.data.indexOf("<?xml")>=0) || data.data.indexOf("<!--")>=0) {
+                        $scope.lang = "xml";
+                    } else {
+                        $scope.lang = languages[extension] || "txt";
+                    }
+                });
+            }
+        };
+
+        $scope.showTreeLink = function(data) {
+            var file = data.args[0].id;
+            $location.search({file:file});
+        };
+
+        $scope.refresh();
+    });

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/index.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/index.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/index.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/index.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,97 @@
+/*
+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.
+*/
+
+solrAdminApp.controller('IndexController', function($scope, System, Cores, Constants) {
+  $scope.resetMenu("index", Constants.IS_ROOT_PAGE);
+  $scope.reload = function() {
+    System.get(function(data) {
+      $scope.system = data;
+
+      // load average
+      var load_average = ( data.system.uptime || '' ).match( /load averages?: (\d+[.,]\d\d),? (\d+[.,]\d\d),? (\d+[.,]\d\d)/ );
+      if (load_average) {
+        for (var i=0;i<2;i++) {
+          load_average[i]=load_average[i].replace(",","."); // for European users
+        }
+        $scope.load_average = load_average.slice(1);
+      }
+
+      // physical memory
+      var memoryMax = parse_memory_value(data.system.totalPhysicalMemorySize);
+      $scope.memoryTotal = parse_memory_value(data.system.totalPhysicalMemorySize - data.system.freePhysicalMemorySize);
+      $scope.memoryPercentage = ($scope.memoryTotal / memoryMax * 100).toFixed(1)+ "%";
+      $scope.memoryMax = pretty_print_bytes(memoryMax);
+      $scope.memoryTotalDisplay = pretty_print_bytes($scope.memoryTotal);
+
+      // swap space
+      var swapMax = parse_memory_value(data.system.totalSwapSpaceSize);
+      $scope.swapTotal = parse_memory_value(data.system.totalSwapSpaceSize - data.system.freeSwapSpaceSize);
+      $scope.swapPercentage = ($scope.swapTotal / swapMax * 100).toFixed(1)+ "%";
+      $scope.swapMax = pretty_print_bytes(swapMax);
+      $scope.swapTotalDisplay = pretty_print_bytes($scope.swapTotal);
+
+      // file handles
+      $scope.fileDescriptorPercentage = (data.system.openFileDescriptorCount / data.system.maxFileDescriptorCount *100).toFixed(1) + "%";
+
+      // java memory
+      var javaMemoryMax = parse_memory_value(data.jvm.memory.raw.max || data.jvm.memory.max);
+      $scope.javaMemoryTotal = parse_memory_value(data.jvm.memory.raw.total || data.jvm.memory.total);
+      $scope.javaMemoryUsed = parse_memory_value(data.jvm.memory.raw.used || data.jvm.memory.used);
+      $scope.javaMemoryTotalPercentage = ($scope.javaMemoryTotal / javaMemoryMax *100).toFixed(1) + "%";
+      $scope.javaMemoryUsedPercentage = ($scope.javaMemoryUsed / $scope.javaMemoryTotal *100).toFixed(1) + "%";
+      $scope.javaMemoryPercentage = ($scope.javaMemoryUsed / javaMemoryMax * 100).toFixed(1) + "%";
+      $scope.javaMemoryTotalDisplay = pretty_print_bytes($scope.javaMemoryTotal);
+      $scope.javaMemoryUsedDisplay = pretty_print_bytes($scope.javaMemoryUsed);  // @todo These should really be an AngularJS Filter: {{ javaMemoryUsed | bytes }}
+      $scope.javaMemoryMax = pretty_print_bytes(javaMemoryMax);
+
+      // no info bar:
+      $scope.noInfo = !(
+        data.system.totalPhysicalMemorySize && data.system.freePhysicalMemorySize &&
+        data.system.totalSwapSpaceSize && data.system.freeSwapSpaceSize &&
+        data.system.openFileDescriptorCount && data.system.maxFileDescriptorCount);
+
+      // command line args:
+      $scope.commandLineArgs = data.jvm.jmx.commandLineArgs.sort();
+    });
+  };
+  $scope.reload();
+});
+
+var parse_memory_value = function( value ) {
+  if( value !== Number( value ) )
+  {
+    var units = 'BKMGTPEZY';
+    var match = value.match( /^(\d+([,\.]\d+)?) (\w).*$/ );
+    var value = parseFloat( match[1] ) * Math.pow( 1024, units.indexOf( match[3].toUpperCase() ) );
+  }
+
+  return value;
+};
+
+var pretty_print_bytes = function(byte_value) {
+  var unit = null;
+
+  byte_value /= 1024;
+  byte_value /= 1024;
+  unit = 'MB';
+
+  if( 1024 <= byte_value ) {
+    byte_value /= 1024;
+    unit = 'GB';
+  }
+  return byte_value.toFixed( 2 ) + ' ' + unit;
+};

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/java-properties.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/java-properties.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/java-properties.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/java-properties.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,45 @@
+/*
+ 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.
+*/
+
+solrAdminApp.controller('JavaPropertiesController',
+  function($scope, Properties, Constants){
+    $scope.resetMenu("java-props", Constants.IS_ROOT_PAGE);
+    $scope.refresh = function() {
+      Properties.get(function(data) {
+        var sysprops = data["system.properties"];
+        var sep = sysprops["path.separator"]
+        var props = [];
+        for (var key in sysprops) {
+          var value = sysprops[key];
+          var key = key.replace(/\./g, '.&#8203;');
+          if (key.indexOf(".path")!=-1 || key.indexOf(".dirs")) {
+            var values = [];
+            var parts = value.split(sep);
+            for (var i in parts) {
+              values.push({pos:i, value:parts[i]})
+            }
+            props.push({name: key, values: values});
+          } else {
+            props.push({name: key, values: [value]});
+          }
+        }
+        $scope.props = props;
+      });
+    };
+
+    $scope.refresh();
+  });

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/logging.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/logging.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/logging.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/logging.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,150 @@
+/*
+ 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.
+*/
+
+var format_time_content = function( time, timeZone ) {
+  var format_time_options = {};
+  if (timeZone && timeZone!="Local") {
+    format_time_options.timeZone = timeZone;
+  }
+  return time.toLocaleString( undefined, format_time_options );
+}
+
+solrAdminApp.controller('LoggingController',
+  function($scope, $timeout, $cookies, Logging, Constants){
+    $scope.resetMenu("logging", Constants.IS_ROOT_PAGE);
+    $scope.timezone = $cookies.logging_timezone || "Local";
+    $scope.refresh = function() {
+      Logging.events(function(data) {
+        $scope.since = new Date();
+        $scope.sinceDisplay = format_time_content($scope.since, "Local");
+        var events = data.history.docs;
+        for (var i=0; i<events.length; i++) {
+          var event = events[i];
+          var time = new Date(event.time);
+          event.local_time = format_time_content(time, "Local");
+          event.utc_time = format_time_content(time, "UTC");
+          event.loggerBase = event.logger.split( '.' ).pop();
+
+          if( !event.trace ) {
+            var lines = event.message.split( "\n" );
+            if( lines.length > 1) {
+              event.trace = event.message;
+              event.message = lines[0];
+            }
+          }
+          event.message = event.message.replace(/,/g, ',&#8203;');
+          event.showTrace = false;
+        }
+        $scope.events = events;
+        $scope.watcher = data.watcher;
+        /* @todo sticky_mode
+        // state element is in viewport
+        sticky_mode = ( state.position().top <= $( window ).scrollTop() + $( window ).height() - ( $( 'body' ).height() - state.position().top ) );
+        // initial request
+        if( 0 === since ) {
+          sticky_mode = true;
+        }
+        $scope.loggingEvents = events;
+
+        if( sticky_mode )
+        {
+          $( 'body' )
+            .animate
+            (
+                { scrollTop: state.position().top },
+                1000
+            );
+        }
+      */
+      });
+      $scope.timeout = $timeout($scope.refresh, 10000);
+      var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
+        $timeout.cancel($scope.timeout);
+        onRouteChangeOff();
+      });
+    };
+    $scope.refresh();
+
+    $scope.toggleTimezone = function() {
+      $scope.timezone = ($scope.timezone=="Local") ? "UTC":"Local";
+      $cookies.logging_timezone = $scope.timezone;
+    }
+    $scope.toggleRow = function(event) {
+      event.showTrace =! event.showTrace;
+    };
+   }
+)
+
+.controller('LoggingLevelController',
+  function($scope, Logging) {
+    $scope.resetMenu("logging-levels");
+
+    var packageOf = function(logger) {
+      var parts = logger.name.split(".");
+      return !parts.pop() ? "" : parts.join(".");
+    };
+
+    var shortNameOf = function(logger) {return logger.name.split(".").pop();}
+
+    var makeTree = function(loggers, packag) {
+      var tree = [];
+      for (var i=0; i<loggers.length; i++) {
+        var logger = loggers[i];
+        logger.packag = packageOf(logger);
+        logger.short = shortNameOf(logger);
+        if (logger.packag == packag) {
+          logger.children = makeTree(loggers, logger.name);
+          tree.push(logger);
+        }
+      }
+      return tree;
+    };
+
+    $scope.refresh = function() {
+      Logging.levels(function(data) {
+        $scope.logging = makeTree(data.loggers, "");
+        $scope.watcher = data.watcher;
+        $scope.levels = [];
+        for (level in data.levels) {
+          $scope.levels.push({name:data.levels[level], pos:level});
+        }
+      });
+    };
+
+    $scope.toggleOptions = function(logger) {
+      if (logger.showOptions) {
+        logger.showOptions = false;
+        delete $scope.currentLogger;
+      } else {
+        if ($scope.currentLogger) {
+          $scope.currentLogger.showOptions = false;
+        }
+        logger.showOptions = true;
+        $scope.currentLogger = logger;
+      }
+    };
+
+    $scope.setLevel = function(logger, newLevel) {
+      var setString = logger.name + ":" + newLevel;
+      logger.showOptions = false;
+      Logging.setLevel({set: setString}, function(data) {
+        $scope.refresh();
+      });
+    };
+
+    $scope.refresh();
+  });

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/plugins.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/plugins.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/plugins.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/plugins.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,166 @@
+/*
+ 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.
+*/
+
+solrAdminApp.controller('PluginsController',
+    function($scope, $rootScope, $routeParams, $location, Mbeans, Constants) {
+        $scope.resetMenu("plugins", Constants.IS_CORE_PAGE);
+
+        if ($routeParams.legacytype) {
+            // support legacy URLs. Angular cannot change #path without reloading controller
+            $location.path("/"+$routeParams.core+"/plugins");
+            $location.search("type", $routeParams.legacytype);
+            return;
+        }
+
+        $scope.refresh = function() {
+            Mbeans.stats({core: $routeParams.core}, function (data) {
+                var type = $location.search().type;
+                $scope.types = getPluginTypes(data, type);
+                $scope.type = getSelectedType($scope.types, type);
+
+                if ($scope.type && $routeParams.entry) {
+                    $scope.plugins = $routeParams.entry.split(",");
+                    openPlugins($scope.type, $scope.plugins);
+                } else {
+                    $scope.plugins = [];
+                }
+            });
+        };
+
+        $scope.selectPluginType = function(type) {
+            $location.search({entry:null, type: type.lower});
+            $scope.type = type;
+        };
+
+        $scope.selectPlugin = function(plugin) {
+            plugin.open = !plugin.open;
+
+            if (plugin.open) {
+                $scope.plugins.push(plugin.name);
+            } else {
+                $scope.plugins.splice($scope.plugins.indexOf(plugin.name), 1);
+            }
+
+            if ($scope.plugins.length==0) {
+                $location.search("entry", null);
+            } else {
+                $location.search("entry", $scope.plugins.join(','));
+            }
+        }
+
+        $scope.startRecording = function() {
+            $scope.isRecording = true;
+            Mbeans.reference({core: $routeParams.core}, function(data) {
+                $scope.reference = data.reference;
+                console.log($scope.reference);
+            })
+        }
+
+        $scope.stopRecording = function() {
+            $scope.isRecording = false;
+            console.log($scope.reference);
+            Mbeans.delta({core: $routeParams.core}, $scope.reference, function(data) {
+                parseDelta($scope.types, data);
+            });
+        }
+
+        $scope.refresh();
+    });
+
+var getPluginTypes = function(data, selected) {
+    var keys = [];
+    var mbeans = data["solr-mbeans"];
+    for (var i=0; i<mbeans.length; i+=2) {
+        var key = mbeans[i];
+        var lower = key.toLowerCase();
+        var plugins = getPlugins(mbeans[i+1]);
+        keys.push({name: key,
+                   selected: lower == selected,
+                   changes: 0,
+                   lower: lower,
+                   plugins: plugins
+        });
+    }
+    keys.sort(function(a,b) {return a.name > b.name});
+    return keys;
+};
+
+var getPlugins = function(data) {
+    var plugins = [];
+    for (var key in data) {
+        var pluginProperties = data[key];
+        var stats = pluginProperties.stats;
+        delete pluginProperties.stats;
+        for (var stat in stats) {
+            // add breaking space after a bracket or @ to handle wrap long lines:
+            stats[stat] = new String(stats[stat]).replace( /([\(@])/g, '$1&#8203;');
+        }
+        plugin = {name: key, changed: false, stats: stats, open:false};
+        plugin.properties = pluginProperties;
+        plugins.push(plugin);
+    }
+    plugins.sort(function(a,b) {return a.name > b.name});
+    return plugins;
+};
+
+var getSelectedType = function(types, selected) {
+    if (selected) {
+        for (var i in types) {
+            if (types[i].lower == selected) {
+                return types[i];
+            }
+        }
+    }
+};
+
+var parseDelta = function(types, data) {
+
+    var getByName = function(list, name) {
+        for (var i in list) {
+            if (list[i].name == name) return list[i];
+        }
+    }
+
+    var mbeans = data["solr-mbeans"]
+    for (var i=0; i<mbeans.length; i+=2) {
+        var typeName = mbeans[i];
+        var type = getByName(types, typeName);
+        var plugins = mbeans[i+1];
+        for (var key in plugins) {
+            var changedPlugin = plugins[key];
+            if (changedPlugin._changed_) {
+                var plugin = getByName(type.plugins, key);
+                var stats = changedPlugin.stats;
+                delete changedPlugin.stats;
+                plugin.properties = changedPlugin;
+                for (var stat in stats) {
+                    // add breaking space after a bracket or @ to handle wrap long lines:
+                    plugin.stats[stat] = new String(stats[stat]).replace( /([\(@])/g, '$1&#8203;');
+                }
+                plugin.changed = true;
+                type.changes++;
+            }
+        }
+    }
+};
+
+var openPlugins = function(type, selected) {
+    for (var i in type.plugins) {
+        var plugin = type.plugins[i];
+        plugin.open = selected.indexOf(plugin.name)>=0;
+    }
+}

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/query.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/query.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/query.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/query.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,114 @@
+/*
+ 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.
+*/
+
+solrAdminApp.controller('QueryController',
+  function($scope, $routeParams, $location, Query, Constants){
+    $scope.resetMenu("query", Constants.IS_COLLECTION_PAGE);
+
+    // @todo read URL parameters into scope
+    $scope.query = {wt: 'json', q:'*:*', indent:'on'};
+    $scope.filters = [{fq:""}];
+    $scope.dismax = {defType: "dismax"};
+    $scope.edismax = {defType: "edismax", stopwords: true, lowercaseOperators: true};
+    $scope.hl = {hl:"on"};
+    $scope.facet = {facet: "on"};
+    $scope.spatial = {};
+    $scope.spellcheck = {spellcheck:"on"};
+    $scope.qt = "/select";
+
+    $scope.doQuery = function() {
+      var params = {};
+
+      var set = function(key, value) {
+        if (params[key]) {
+          params[key].push(value);
+        } else {
+          params[key] = [value];
+        }
+      }
+      var copy = function(params, query) {
+        for (var key in query) {
+          terms = query[key];
+          if (terms.length > 0 && key[0]!="$") {
+            set(key, terms);
+          }
+        }
+      };
+
+      copy(params, $scope.query);
+
+      if ($scope.isDismax)     copy(params, $scope.dismax);
+      if ($scope.isEdismax)    copy(params, $scope.edismax);
+      if ($scope.isHighlight)  copy(params, $scope.hl);
+      if ($scope.isFacet)      copy(params, $scope.facet);
+      if ($scope.isSpatial)    copy(params, $scope.spatial);
+      if ($scope.isSpellcheck) copy(params, $scope.spellcheck);
+
+      if ($scope.rawParams) {
+        var rawParams = $scope.rawParams.split(/[&\n]/);
+        for (var i in rawParams) {
+          var param = rawParams[i];
+          var equalPos = param.indexOf("=");
+          if (equalPos > -1) {
+            set(param.substring(0, equalPos), param.substring(equalPos+1));
+          } else {
+            set(param, ""); // Use empty value for params without "="
+          }
+        }
+      }
+
+      var qt = $scope.qt ? $scope.qt : "/select";
+
+      for (var filter in $scope.filters) {
+        copy(params, $scope.filters[filter]);
+      }
+
+      params.core = $routeParams.core;
+      if (qt[0] == '/') {
+        params.handler = qt.substring(1);
+      } else { // Support legacy style handleSelect=true configs
+        params.handler = "select";
+        set("qt", qt);
+      }
+      var url = Query.url(params);
+      Query.query(params, function(data) {
+        $scope.lang = $scope.query.wt;
+        $scope.response = data;
+        $scope.url = $location.protocol() + "://" +
+                     $location.host() + ":" +
+                     $location.port() + url;
+      });
+    };
+
+    if ($location.search().q) {
+      $scope.query.q = $location.search()["q"];
+      $scope.doQuery();
+    }
+
+    $scope.removeFilter = function(index) {
+      if ($scope.filters.length === 1) {
+        $scope.filters = [{fq: ""}];
+      } else {
+        $scope.filters.splice(index, 1);
+      }
+    };
+
+    $scope.addFilter = function(index) {
+      $scope.filters.splice(index+1, 0, {fq:""});
+    };
+  }
+);

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/replication.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/replication.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/replication.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/replication.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,235 @@
+/*
+ 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.
+*/
+
+solrAdminApp.controller('ReplicationController',
+    function($scope, $rootScope, $routeParams, $interval, $timeout, Replication, Constants) {
+        $scope.resetMenu("replication", Constants.IS_CORE_PAGE);
+
+        $scope.iterationCount = 1;
+
+        $scope.refresh = function() {
+            Replication.details({core:$routeParams.core}, function(response) {
+                var timeout;
+                var interval;
+                if ($scope.interval) $interval.cancel($scope.interval);
+                $scope.isSlave = (response.details.isSlave === 'true');
+                if ($scope.isSlave) {
+                    $scope.progress = getProgressDetails(response.details.slave);
+                    $scope.iterations = getIterations(response.details.slave);
+                    $scope.versions = getSlaveVersions(response.details);
+                    $scope.settings = getSlaveSettings(response.details);
+                    if ($scope.settings.isReplicating) {
+                        timeout = $timeout($scope.refresh, 1000);
+                    } else if(!$scope.settings.isPollingDisabled && $scope.settings.pollInterval) {
+                        interval = $scope.interval = $interval(function() {
+                            $scope.settings.tick--;
+                        }, 1000, $scope.settings.tick);
+                        timeout = $timeout($scope.refresh, 1000*(1+$scope.settings.tick));
+                    }
+                } else {
+                    $scope.versions = getMasterVersions(response.details);
+                }
+                $scope.master = getMasterSettings(response.details, $scope.isSlave);
+
+                var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
+                    if (interval) $interval.cancel(interval);
+                    if (timeout) $timeout.cancel(timeout);
+                    onRouteChangeOff();
+                });
+            });
+
+        };
+
+        $scope.execute = function(command) {
+            Replication.command({core:$routeParams.core, command:command}, function(data){$scope.refresh()});
+        }
+
+        $scope.showIterations = function() { $scope.iterationCount = 100000}; // limitTo should accept undefined, but doesn't work.
+        $scope.hideIterations = function() { $scope.iterationCount = 1};
+
+        $scope.refresh();
+    });
+
+var getProgressDetails = function(progress) {
+
+    progress.timeRemaining = parseSeconds(progress.timeRemaining);
+    progress.totalPercent = parseInt(progress.totalPercent);
+    if (progress.totalPercent === 0) {
+        progress.totalPercentWidth = "1px";
+    } else {
+        progress.totalPercentWidth = progress.totalPercent + "%";
+    }
+    progress.currentFileSizePercent = parseInt(progress.currentFileSizePercent);
+
+    if (!progress.indexReplicatedAtList) {
+        progress.indexReplicatedAtList = [];
+    }
+
+    if (!progress.replicationFailedAtList) {
+        progress.replicationFailedAtList = [];
+    }
+    return progress;
+};
+
+var getIterations = function(slave) {
+
+    var iterations = [];
+
+    var find = function(list, date) {
+        return list.filter(function(e) {return e.date == date});
+    };
+
+    for (var i in slave.indexReplicatedAtList) {
+        var date = slave.indexReplicatedAtList[i];
+        var iteration = {date:date, status:"replicated", latest: false};
+        if (date == slave.indexReplicatedAt) {
+            iteration.latest = true;
+        }
+        iterations.push(iteration);
+    }
+
+    for (var i in slave.replicationFailedAtList) {
+        var failedDate = slave.replicationFailedAtList[i];
+        var matchingIterations = find(iterations, failedDate);
+        if (matchingIterations) {
+            iteration = matchingIterations[0];
+        } else {
+            iteration = {date: failedDate, latest:false};
+            iterations.push(iteration);
+        }
+        iteration.status = "failed";
+        if (failedDate == slave.replicationFailedAt) {
+            iteration.latest = true;
+        }
+    }
+    iterations.sort(function(a,b){ return a.date> b.date;}).reverse();
+    return iterations;
+};
+
+var getMasterVersions = function(data) {
+    versions = {masterSearch:{}, master:{}};
+
+    versions.masterSearch.version = data.indexVersion;
+    versions.masterSearch.generation = data.generation;
+    versions.masterSearch.size = data.indexSize;
+
+    versions.master.version = data.master.replicableVersion || '-';
+    versions.master.generation = data.master.replicableGeneration || '-';
+    versions.master.size = '-';
+
+    return versions;
+};
+
+var getSlaveVersions = function(data) {
+    versions = {masterSearch: {}, master: {}, slave: {}};
+
+    versions.slave.version = data.indexVersion;
+    versions.slave.generation = data.generation;
+    versions.slave.size = data.indexSize;
+
+    versions.master.version = data.slave.masterDetails.replicableVersion || '-';
+    versions.master.generation = data.slave.masterDetails.replicableGeneration || '-';
+    versions.master.size = '-';
+
+    versions.masterSearch.version = data.slave.masterDetails.indexVersion;
+    versions.masterSearch.generation = data.slave.masterDetails.generation;
+    versions.masterSearch.size = data.slave.masterDetails.indexSize;
+
+    versions.changedVersion = data.indexVersion !== data.slave.masterDetails.indexVersion;
+    versions.changedGeneration = data.generation !== data.slave.masterDetails.generation;
+
+    return versions;
+};
+
+var parseDateToEpoch = function(date) {
+    // ["Sat Mar 03 11:00:00 CET 2012", "Sat", "Mar", "03", "11:00:00", "CET", "2012"]
+    var parts = date.match( /^(\w+)\s+(\w+)\s+(\d+)\s+(\d+\:\d+\:\d+)\s+(\w+)\s+(\d+)$/ );
+
+    // "Sat Mar 03 2012 10:37:33"
+    var d = new Date( parts[1] + ' ' + parts[2] + ' ' + parts[3] + ' ' + parts[6] + ' ' + parts[4] );
+    return d.getTime();
+}
+
+var parseSeconds = function(time) {
+    var seconds = 0;
+    var arr = new String(time || '').split('.');
+    var parts = arr[0].split(':').reverse();
+
+    for (var i = 0; i < parts.length; i++) {
+        seconds += ( parseInt(parts[i], 10) || 0 ) * Math.pow(60, i);
+    }
+
+    if (arr[1] && 5 <= parseInt(arr[1][0], 10)) {
+        seconds++; // treat more or equal than .5 as additional second
+
+    }
+
+    return seconds;
+}
+
+var getSlaveSettings = function(data) {
+    var settings = {};
+    settings.masterUrl = data.slave.masterUrl;
+    settings.isPollingDisabled = data.slave.isPollingDisabled == 'true';
+    settings.pollInterval = data.slave.pollInterval;
+    settings.isReplicating = data.slave.isReplicating == 'true';
+    settings.nextExecutionAt = data.slave.nextExecutionAt;
+
+    if(settings.isReplicating) {
+        settings.isApprox = true;
+        settings.tick = parseSeconds(settings.pollInterval);
+    } else if (!settings.isPollingDisabled && settings.pollInterval) {
+        if( settings.nextExecutionAt ) {
+            settings.nextExecutionAtEpoch = parseDateToEpoch(settings.nextExecutionAt);
+            settings.currentTime = parseDateToEpoch(data.slave.currentDate);
+
+            if( settings.nextExecutionAtEpoch > settings.currentTime) {
+                settings.isApprox = false;
+                settings.tick = ( settings.nextExecutionAtEpoch - settings.currentTime) / 1000;
+            }
+        }
+    }
+    return settings;
+};
+
+var getMasterSettings = function(details, isSlave) {
+    var master = {};
+    var masterData = isSlave ? details.slave.masterDetails.master : details.master;
+    master.replicationEnabled = masterData.replicationEnabled == "true";
+    master.replicateAfter = masterData.replicateAfter.join(", ");
+
+    if (masterData.confFiles) {
+        master.files = [];
+        var confFiles = masterData.confFiles.split(',');
+        for (var i=0; i<confFiles.length; i++) {
+            var file = confFiles[i];
+            var short = file;
+            var title = file;
+            if (file.indexOf(":")>=0) {
+                title = file.replace(':', ' ยป ');
+                var parts = file.split(':');
+                if (isSlave) {
+                    short = parts[1];
+                } else {
+                    short = parts[0];
+                }
+            }
+            master.files.push({title:title, name:short});
+        }
+    }
+    return master;
+}