You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2018/10/09 09:09:17 UTC

[13/27] lucene-solr:solr7896-login-page: Testing new approach

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/index.html
----------------------------------------------------------------------
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index a98a30da..f36dc13 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -23,6 +23,7 @@ limitations under the License.
   <link rel="icon" type="image/x-icon" href="img/favicon.ico?_=${version}">
   <link rel="shortcut icon" type="image/x-icon" href="img/favicon.ico?_=${version}">
 
+  <link rel="stylesheet" type="text/css" href="css/bootstrap-3.3/bootstrap.min.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/common.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/analysis.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/cloud.css?_=${version}">
@@ -67,6 +68,7 @@ limitations under the License.
   <script src="js/angular/http-auth-interceptor.js"></script>
   <script src="js/angular/login-controllers.js"></script>
   <script src="js/angular/controllers/index.js"></script>
+  <script src="js/angular/controllers/login.js"></script>
   <script src="js/angular/controllers/logging.js"></script>
   <script src="js/angular/controllers/cloud.js"></script>
   <script src="js/angular/controllers/collections.js"></script>
@@ -142,7 +144,8 @@ limitations under the License.
         <div>
 
           <ul id="menu">
-
+            <li id="login" class="global" ng-class="{active:page=='login'}"><p><a href="#/login">Logout</a></p></li>
+            
             <li id="index" class="global" ng-class="{active:page=='index'}"><p><a href="#/">Dashboard</a></p></li>
 
             <li id="logging" class="global" ng-class="{active:page=='logging'}"><p><a href="#/~logging">Logging</a></p>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/js/angular/app.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js
index fe686fe..a0bc4ab 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -31,6 +31,10 @@ solrAdminApp.config([
         templateUrl: 'partials/index.html',
         controller: 'IndexController'
       }).
+      when('/login', {
+        templateUrl: 'partials/login.html',
+        controller: 'LoginController'
+      }).
       when('/~logging', {
         templateUrl: 'partials/logging.html',
         controller: 'LoggingController'
@@ -369,56 +373,73 @@ solrAdminApp.config([
 
   return {request: started, response: ended, responseError: failed};
 })
-.factory('authInterceptor', function($q, $rootScope, $timeout, $injector) {
-  var started = function(config) {
-    var ah = "Basic c29scjpTb2xyUm9ja3M=";  // solr / SolrRocks
-    config.headers['Authorization'] = ah;
-    console.log("Added authorization header " + ah);
-    return config || $q.when(config);
-  };
-
-  var ended = function(response) {
-    console.log("Response headers: " + JSON.stringify(response.headers, undefined, 2));
-    if (response.headers['WWW-Authenticate'] != null) {
-      console.log("Got WWW-Authenticate header: " + response.headers['WWW-Authenticate']);
-    }
-    return response || $q.when(response);
-  };
-
-  var failed = function(rejection) {
-    console.log("Failed with rejection " + JSON.stringify(rejection, undefined, 2));
-    if (rejection.status === 401) {
-      console.log("Status code is 401");
-    } else {
-      console.log("Rejection status is " + rejection.status)
-    }
-    $rootScope.$broadcast('loadingStatusInactive');
-    return $q.reject(rejection);
-  };
-
-  return {request: started, response: ended, responseError: failed};
-})
+// NOCOMMIT First iteration    
+// .factory('authInterceptor', function($q, $rootScope, $timeout, $injector) {
+//   var started = function(config) {
+//     var ah = "Basic c29scjpyb2Nrcw==";  // solr / SolrRocks
+//     config.headers['Authorization'] = ah;
+//     console.log("Added authorization header " + ah);
+//     return config || $q.when(config);
+//   };
+//
+//   var ended = function(response) {
+//     console.log("Response headers: " + JSON.stringify(response.headers, undefined, 2));
+//     if (response.headers['WWW-Authenticate'] != null) {
+//       console.log("Got WWW-Authenticate header: " + response.headers['WWW-Authenticate']);
+//     }
+//     return response || $q.when(response);
+//   };
+//
+//   var failed = function(rejection) {
+//     console.log("Failed with rejection " + JSON.stringify(rejection, undefined, 2));
+//     if (rejection.status === 401) {
+//       console.log("Status code is 401");
+//     } else {
+//       console.log("Rejection status is " + rejection.status)
+//     }
+//     $rootScope.$broadcast('loadingStatusInactive');
+//     return $q.reject(rejection);
+//   };
+//
+//   return {request: started, response: ended, responseError: failed};
+// })
 .config(function($httpProvider) {
   $httpProvider.interceptors.push("httpInterceptor");
-  $httpProvider.interceptors.push("authInterceptor");
+  // NOCOMMIT $httpProvider.interceptors.push("authInterceptor");
   // Tell the BasicAuth plugin that we are Admin UI so it can serve us a 'Authorization: xBasic xxxx' header
   // so that the browser will not interfer with the login dialogue
   $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
 })
+// NOCOMMIT: just for testing     
+.run(['$rootScope', '$location', '$cookieStore', '$http',
+  function ($rootScope, $location, $cookieStore, $http) {
+    // keep user logged in after page refresh
+    $rootScope.globals = $cookieStore.get('globals') || {};
+    if ($rootScope.globals.currentUser) {
+      $http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.globals.currentUser.authdata; // jshint ignore:line
+    }
+
+    $rootScope.$on('$locationChangeStart', function (event, next, current) {
+      // redirect to login page if not logged in
+      if ($location.path() !== '/login' && !$rootScope.globals.currentUser) {
+        $location.path('/login');
+      }
+    });
+  }])
 .directive('fileModel', function ($parse) {
-    return {
-        restrict: 'A',
-        link: function(scope, element, attrs) {
-            var model = $parse(attrs.fileModel);
-            var modelSetter = model.assign;
+  return {
+    restrict: 'A',
+    link: function(scope, element, attrs) {
+      var model = $parse(attrs.fileModel);
+      var modelSetter = model.assign;
 
-            element.bind('change', function(){
-                scope.$apply(function(){
-                    modelSetter(scope, element[0].files[0]);
-                });
-            });
-        }
-    };
+      element.bind('change', function(){
+        scope.$apply(function(){
+          modelSetter(scope, element[0].files[0]);
+        });
+      });
+    }
+  };
 });
 
 solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $location, Cores, Collections, System, Ping, Constants) {
@@ -432,7 +453,7 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
   $scope.refresh = function() {
       $scope.cores = [];
       $scope.collections = [];
-  }
+  };
 
   $scope.refresh();
   $scope.resetMenu = function(page, pageType) {
@@ -462,7 +483,7 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
           for (key in data.collections) {
             var collection = {name: data.collections[key]};
             $scope.collections.push(collection);
-            if (pageType == Constants.IS_COLLECTION_PAGE && collection.name == currentCollectionName) {
+            if (pageType === Constants.IS_COLLECTION_PAGE && collection.name === currentCollectionName) {
               $scope.currentCollection = collection;
             }
           }
@@ -486,15 +507,15 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
 
   $scope.dumpCloud = function() {
       $scope.$broadcast("cloud-dump");
-  }
+  };
 
   $scope.showCore = function(core) {
     $location.url("/" + core.name);
-  }
+  };
 
   $scope.showCollection = function(collection) {
     $location.url("/" + collection.name + "/collection-overview")
-  }
+  };
 
   $scope.$on('$routeChangeStart', function() {
       $rootScope.exceptions = {};

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/js/angular/controllers/login.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/controllers/login.js b/solr/webapp/web/js/angular/controllers/login.js
new file mode 100644
index 0000000..b0b7096
--- /dev/null
+++ b/solr/webapp/web/js/angular/controllers/login.js
@@ -0,0 +1,36 @@
+/*
+ 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('LoginController',
+    ['$scope', '$rootScope', '$location', 'AuthenticationService',
+      function ($scope, $rootScope, $location, AuthenticationService) {
+        // reset login status
+        AuthenticationService.ClearCredentials();
+
+        $scope.login = function () {
+          $scope.dataLoading = true;
+          AuthenticationService.Login($scope.username, $scope.password, function (response) {
+            if (response.success) {
+              AuthenticationService.SetCredentials($scope.username, $scope.password);
+              $location.path('/');
+            } else {
+              $scope.error = response.message;
+              $scope.dataLoading = false;
+            }
+          });
+        };
+      }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/js/angular/directives.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/directives.js b/solr/webapp/web/js/angular/directives.js
deleted file mode 100644
index ba6e701..0000000
--- a/solr/webapp/web/js/angular/directives.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-'use strict';
-
-angular.module('loginDirectives', []).
-directive('loginDialog', function($timeout) {
-   return {
-       templateUrl: '/templates/loginDialog.html',
-       restrict: 'E',
-       replace: true,
-       controller: CredentialsController,
-       link: function(scope, element, attributes, controller) {
-           var isShowing = false;
-           
-           element.on('shown.bs.modal', function(e) {
-               element.find('#userName').focus();
-           });
-
-           scope.$on('event:auth-loginRequired', function() {
-               if (isShowing) {
-                   return;
-               }
-               
-               // If we're in the process of hiding the modal, we need to wait for
-               // all CSS animations to complete before showing the modal again.
-               // Otherwise, we might end up with an invisible modal, making the whole
-               // view rather unusable. I've been unable to control the transitions
-               // between "showing", "shown", "hiding", and "hidden" tightly using
-               // JQuery notifications without collecting more and more modal backdrops
-               // in the DOM, so the dirty solution here is to simply wait a second
-               // before showing the log-in dialog.
-               isShowing = true;
-               $timeout(function() {
-                   element.modal('show');
-                   isShowing = false;
-               }, 1000);
-           });
-
-           scope.$on('event:auth-loginConfirmed', function() {
-               element.modal('hide');
-               scope.credentials.password = '';
-           });
-       }
-   } 
-}).
-directive('logoutLink', function() {
-   return {
-       templateUrl: '/templates/logoutLink.html',
-       restrict: 'E',
-       replace: true,
-       controller: LoginController
-   } 
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/js/angular/http-auth-interceptor.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/http-auth-interceptor.js b/solr/webapp/web/js/angular/http-auth-interceptor.js
deleted file mode 100644
index 2f34861..0000000
--- a/solr/webapp/web/js/angular/http-auth-interceptor.js
+++ /dev/null
@@ -1,122 +0,0 @@
-/*global angular:true, browser:true */
-
-/**
- * @license HTTP Auth Interceptor Module for AngularJS
- * (c) 2012 Witold Szczerba
- * License: MIT
- */
-(function () {
-  'use strict';
-
-  angular.module('http-auth-interceptor', ['http-auth-interceptor-buffer'])
-
-  .factory('authService', ['$rootScope','httpBuffer', function($rootScope, httpBuffer) {
-    return {
-      /**
-       * Call this function to indicate that authentication was successfull and trigger a
-       * retry of all deferred requests.
-       * @param data an optional argument to pass on to $broadcast which may be useful for
-       * example if you need to pass through details of the user that was logged in
-       */
-      loginConfirmed: function(data, configUpdater) {
-        var updater = configUpdater || function(config) {return config;};
-        $rootScope.$broadcast('event:auth-loginConfirmed', data);
-        httpBuffer.retryAll(updater);
-      },
-
-      /**
-       * Call this function to indicate that authentication should not proceed.
-       * All deferred requests will be abandoned or rejected (if reason is provided).
-       * @param data an optional argument to pass on to $broadcast.
-       * @param reason if provided, the requests are rejected; abandoned otherwise.
-       */
-      loginCancelled: function(data, reason) {
-        httpBuffer.rejectAll(reason);
-        $rootScope.$broadcast('event:auth-loginCancelled', data);
-      }
-    };
-  }])
-
-  /**
-   * $http interceptor.
-   * On 401 response (without 'ignoreAuthModule' option) stores the request
-   * and broadcasts 'event:angular-auth-loginRequired'.
-   */
-  .config(['$httpProvider', function($httpProvider) {
-    $httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) {
-      return {
-        responseError: function(rejection) {
-          if (rejection.status === 401 && !rejection.config.ignoreAuthModule) {
-            var deferred = $q.defer();
-            httpBuffer.append(rejection.config, deferred);
-            $rootScope.$broadcast('event:auth-loginRequired', rejection);
-            return deferred.promise;
-          }
-          // otherwise, default behaviour
-          return $q.reject(rejection);
-        }
-      };
-    }]);
-  }]);
-
-  /**
-   * Private module, a utility, required internally by 'http-auth-interceptor'.
-   */
-  angular.module('http-auth-interceptor-buffer', [])
-
-  .factory('httpBuffer', ['$injector', function($injector) {
-    /** Holds all the requests, so they can be re-requested in future. */
-    var buffer = [];
-
-    /** Service initialized later because of circular dependency problem. */
-    var $http;
-
-    function retryHttpRequest(config, deferred) {
-      // Make room for new 'Authenticate' header value
-      delete config.headers['Authorization'];
-
-      function successCallback(response) {
-        deferred.resolve(response);
-      }
-      function errorCallback(response) {
-        deferred.reject(response);
-      }
-      $http = $http || $injector.get('$http');
-      $http(config).then(successCallback, errorCallback);
-    }
-
-    return {
-      /**
-       * Appends HTTP request configuration object with deferred response attached to buffer.
-       */
-      append: function(config, deferred) {
-        buffer.push({
-          config: config,
-          deferred: deferred
-        });
-      },
-
-      /**
-       * Abandon or reject (if reason provided) all the buffered requests.
-       */
-      rejectAll: function(reason) {
-        if (reason) {
-          for (var i = 0; i < buffer.length; ++i) {
-            buffer[i].deferred.reject(reason);
-          }
-        }
-        buffer = [];
-      },
-
-      /**
-       * Retries all the buffered requests clears the buffer.
-       */
-      retryAll: function(updater) {
-        for (var i = 0; i < buffer.length; ++i) {
-          retryHttpRequest(updater(buffer[i].config), buffer[i].deferred);
-        }
-        buffer = [];
-      }
-    };
-  }]);
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/js/angular/login-controllers.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/login-controllers.js b/solr/webapp/web/js/angular/login-controllers.js
deleted file mode 100644
index 819ccb3..0000000
--- a/solr/webapp/web/js/angular/login-controllers.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-'use strict';
-
-function CredentialsController($scope, loginService) {
-    $scope.credentials = { userName: '', password: '' };
-    
-    $scope.submit = function() {
-        loginService.setUserNameAndPassword($scope.credentials.userName, $scope.credentials.password);
-    }
-}
-
-function LoginController($scope, $location, loginService) {
-    $scope.isLoggedIn = function() {
-        return loginService.isLoggedIn();
-    }
-    
-    $scope.logOut = function() {
-        loginService.logOut();
-        $location.path('/about');
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/js/angular/services.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js
index e3dcd3f..ffd7135 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -262,4 +262,138 @@ solrAdminServices.factory('System',
      return $resource(':core/config', {wt: 'json', core: '@core', _:Date.now()}, {
        get: {method: "GET"}
      })
-}]);
+}])
+.factory('AuthenticationService',
+    ['Base64', '$http', '$cookieStore', '$rootScope', '$timeout',
+      function (Base64, $http, $cookieStore, $rootScope, $timeout) {
+        var service = {};
+
+        service.Login = function (username, password, callback) {
+
+          /* NOCOMMIT Dummy authentication for testing, uses $timeout to simulate api call
+           ----------------------------------------------*/
+          $timeout(function () {
+            var response = { success: username === 'test' && password === 'test' };
+            if (!response.success) {
+              response.message = 'Username or password is incorrect';
+            }
+            callback(response);
+          }, 1000);
+
+
+          /* Use this for real authentication
+           ----------------------------------------------*/
+          //$http.post('/api/authenticate', { username: username, password: password })
+          //    .success(function (response) {
+          //        callback(response);
+          //    });
+
+        };
+
+        service.SetCredentials = function (username, password) {
+          var authdata = Base64.encode(username + ':' + password);
+
+          $rootScope.globals = {
+            currentUser: {
+              username: username,
+              authdata: authdata
+            }
+          };
+
+          $http.defaults.headers.common['Authorization'] = 'Basic ' + authdata; // jshint ignore:line
+          $cookieStore.put('globals', $rootScope.globals);
+        };
+
+        service.ClearCredentials = function () {
+          $rootScope.globals = {};
+          $cookieStore.remove('globals');
+          $http.defaults.headers.common.Authorization = 'Basic ';
+        };
+
+        return service;
+      }])
+.factory('Base64', function () {
+  /* jshint ignore:start */
+
+  var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+  return {
+    encode: function (input) {
+      var output = "";
+      var chr1, chr2, chr3 = "";
+      var enc1, enc2, enc3, enc4 = "";
+      var i = 0;
+
+      do {
+        chr1 = input.charCodeAt(i++);
+        chr2 = input.charCodeAt(i++);
+        chr3 = input.charCodeAt(i++);
+
+        enc1 = chr1 >> 2;
+        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+        enc4 = chr3 & 63;
+
+        if (isNaN(chr2)) {
+          enc3 = enc4 = 64;
+        } else if (isNaN(chr3)) {
+          enc4 = 64;
+        }
+
+        output = output +
+            keyStr.charAt(enc1) +
+            keyStr.charAt(enc2) +
+            keyStr.charAt(enc3) +
+            keyStr.charAt(enc4);
+        chr1 = chr2 = chr3 = "";
+        enc1 = enc2 = enc3 = enc4 = "";
+      } while (i < input.length);
+
+      return output;
+    },
+
+    decode: function (input) {
+      var output = "";
+      var chr1, chr2, chr3 = "";
+      var enc1, enc2, enc3, enc4 = "";
+      var i = 0;
+
+      // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
+      var base64test = /[^A-Za-z0-9\+\/\=]/g;
+      if (base64test.exec(input)) {
+        window.alert("There were invalid base64 characters in the input text.\n" +
+            "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
+            "Expect errors in decoding.");
+      }
+      input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+      do {
+        enc1 = keyStr.indexOf(input.charAt(i++));
+        enc2 = keyStr.indexOf(input.charAt(i++));
+        enc3 = keyStr.indexOf(input.charAt(i++));
+        enc4 = keyStr.indexOf(input.charAt(i++));
+
+        chr1 = (enc1 << 2) | (enc2 >> 4);
+        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+        chr3 = ((enc3 & 3) << 6) | enc4;
+
+        output = output + String.fromCharCode(chr1);
+
+        if (enc3 != 64) {
+          output = output + String.fromCharCode(chr2);
+        }
+        if (enc4 != 64) {
+          output = output + String.fromCharCode(chr3);
+        }
+
+        chr1 = chr2 = chr3 = "";
+        enc1 = enc2 = enc3 = enc4 = "";
+
+      } while (i < input.length);
+
+      return output;
+    }
+  };
+
+  /* jshint ignore:end */
+});

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5bb585c3/solr/webapp/web/partials/login.html
----------------------------------------------------------------------
diff --git a/solr/webapp/web/partials/login.html b/solr/webapp/web/partials/login.html
new file mode 100644
index 0000000..cae1cc8
--- /dev/null
+++ b/solr/webapp/web/partials/login.html
@@ -0,0 +1,44 @@
+<!--
+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.
+-->
+<div id="login" class="jumbotron">
+
+  <div class="col-xs-offset-2 col-xs-8">
+    <div class="alert alert-info">
+      Username: test<br />
+      Password: test
+    </div>
+    <div ng-show="error" class="alert alert-danger">{{error}}</div>
+    <form name="form" ng-submit="login()" role="form">
+        <div class="form-group">
+            <label for="username">Username</label>
+            <i class="fa fa-key"></i>
+            <input type="text" name="username" id="username" class="form-control" ng-model="username" required />
+            <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
+        </div>
+        <div class="form-group">
+            <label for="password">Password</label>
+            <i class="fa fa-lock"></i>
+            <input type="password" name="password" id="password" class="form-control" ng-model="password" required />
+            <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
+        </div>
+        <div class="form-actions">
+            <button type="submit" ng-disabled="form.$invalid || dataLoading" class="btn btn-danger">Login</button>
+            <img ng-if="dataLoading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
+        </div>
+    </form>
+  </div>
+</div>