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 2019/08/30 12:18:06 UTC

[lucene-solr] branch master updated: SOLR-13122: Ability to query aliases in Solr Admin UI

This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/master by this push:
     new 52be32d  SOLR-13122: Ability to query aliases in Solr Admin UI
52be32d is described below

commit 52be32d4addbead8536dbde84ed8c80af4993b8b
Author: Jan Høydahl <ja...@apache.org>
AuthorDate: Fri Aug 30 14:16:58 2019 +0200

    SOLR-13122: Ability to query aliases in Solr Admin UI
---
 solr/CHANGES.txt                                   |   3 +-
 solr/webapp/web/css/angular/collections.css        |  38 ++--
 solr/webapp/web/css/angular/overview.css           |  42 ++++
 solr/webapp/web/index.html                         |  17 +-
 solr/webapp/web/js/angular/app.js                  |  59 ++++--
 .../web/js/angular/controllers/alias-overview.js   |  27 +++
 .../web/js/angular/controllers/collections.js      |  43 ++--
 solr/webapp/web/js/angular/services.js             |   6 +
 solr/webapp/web/partials/alias_overview.html       |  46 +++++
 solr/webapp/web/partials/collection_overview.html  |   2 +-
 solr/webapp/web/partials/collections.html          | 223 ++++++++++++---------
 11 files changed, 360 insertions(+), 146 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index dad7a1c..b175de2 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -126,6 +126,7 @@ New Features
 
 * SOLR-13710: Persist package jars locally & expose them over http at /api/node/blob  (noble)
 
+* SOLR-13122: Ability to query aliases in Solr Admin UI (janhoy)
 
 Improvements
 ----------------------
@@ -147,7 +148,7 @@ Improvements
 * SOLR-13542: Code cleanup - Avoid using stream filter count where possible (Koen De Groote via Tomás Fernández Löbbe)
 
 * SOLR-13720: BlockJoinParentQParser.getCachedFilter()made public for accessing from QParser plugins
-  (Stanislav Livotov via Mikhail Khludnev) 
+  (Stanislav Livotov via Mikhail Khludnev)
 
 Bug Fixes
 ----------------------
diff --git a/solr/webapp/web/css/angular/collections.css b/solr/webapp/web/css/angular/collections.css
index e8d1207..a4eccbe 100644
--- a/solr/webapp/web/css/angular/collections.css
+++ b/solr/webapp/web/css/angular/collections.css
@@ -40,7 +40,6 @@ limitations under the License.
 
 #content #collections #navigation
 {
-  padding-top: 50px;
   width: 12%;
 }
 
@@ -49,6 +48,17 @@ limitations under the License.
   padding-left: 5px;
 }
 
+#content #collections #navigation ul
+{
+  padding-top: 20px;
+}
+
+#content #collections #navigation hr
+{
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
 #content #collections #frame .actions
 {
   margin-bottom: 20px;
@@ -129,23 +139,24 @@ limitations under the License.
   background-image: url( ../../img/ico/cross.png );
 }
 
-#content #collections .actions #add
+#content #collections #navigation #add span
 {
-  left: 0;
-  position: absolute;
+  background-image: url( ../../img/ico/plus-button.png );
 }
 
-#content #collections .actions #add span
+#content #collections #navigation #create-alias span
 {
-  background-image: url( ../../img/ico/plus-button.png );
+  background-image: url( ../../img/ico/arrow-switch.png );
 }
 
-#content #collections .actions #delete
+
+#content #collections .actions #delete-collection,
+#content #collections .actions #delete-alias
 {
   margin-right: 20px;
 }
 
-#content #collections .actions #delete span
+#content #collections .actions #delete-collection span
 {
   background-image: url( ../../img/ico/cross.png );
 }
@@ -160,11 +171,6 @@ limitations under the License.
   background-image: url( ../../img/ico/ui-text-field-select.png );
 }
 
-#content #collections .actions #create-alias span
-{
-  background-image: url( ../../img/ico/arrow-switch.png );
-}
-
 #content #collections .actions #delete-alias span
 {
   background-image: url( ../../img/ico/cross-button.png );
@@ -215,6 +221,7 @@ limitations under the License.
   background-position: 0% 50%;
 }
 
+#content #collections #data #alias-data h2 { background-image: url( ../../img/ico/box.png ); }
 #content #collections #data #collection-data h2 { background-image: url( ../../img/ico/box.png ); }
 #content #collections #data #shard-data h2 { background-image: url( ../../img/ico/sitemap.png ); }
 #content #collections #data #shard-data .replica h2 { background-image: url( ../../img/ico/node-slave.png ); }
@@ -355,6 +362,11 @@ limitations under the License.
     width: 100% !important;
 }
 
+#content #collections #alias-data {
+  float: left;
+  width: 35%;
+}
+
 #content #collections #collection-data {
   float: left;
   width: 35%;
diff --git a/solr/webapp/web/css/angular/overview.css b/solr/webapp/web/css/angular/overview.css
new file mode 100644
index 0000000..4934f72
--- /dev/null
+++ b/solr/webapp/web/css/angular/overview.css
@@ -0,0 +1,42 @@
+/*
+
+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.
+
+*/
+
+#content #dashboard .collprops
+{
+  float: left;
+}
+
+#content #dashboard .collprops dt,
+#content #dashboard .collprops dd
+{
+  display: block;
+  float: left;
+}
+
+#content #dashboard .collprops dt
+{
+  clear: left;
+  margin-right: 2%;
+  width: 48%;
+}
+
+#content #dashboard .collprops dd
+{
+  width: 50%;
+}
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index 58a76c8..7fe1381 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -46,6 +46,7 @@ limitations under the License.
   <link rel="stylesheet" type="text/css" href="css/angular/segments.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/threads.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/chosen.css?_=${version}">
+  <link rel="stylesheet" type="text/css" href="css/angular/overview.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/jquery-ui.min.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/jquery-ui.structure.min.css?_=${version}">
 
@@ -73,6 +74,7 @@ limitations under the License.
   <script src="js/angular/controllers/cores.js"></script>
   <script src="js/angular/controllers/threads.js"></script>
   <script src="js/angular/controllers/java-properties.js"></script>
+  <script src="js/angular/controllers/alias-overview.js"></script>
   <script src="js/angular/controllers/core-overview.js"></script>
   <script src="js/angular/controllers/collection-overview.js"></script>
   <script src="js/angular/controllers/analysis.js"></script>
@@ -180,7 +182,7 @@ limitations under the License.
                         ng-model="currentCollection"
                         chosen
                         ng-change="showCollection(currentCollection)"
-                        ng-options="collection.name for collection in collections"></select>
+                        ng-options="collection.name for collection in aliases_and_collections"></select>
               </div>
               <p id="has-no-collections" ng-show="collections.length==0"><a href="#/~collections">
                 No collections available
@@ -189,14 +191,15 @@ limitations under the License.
             </div>
             <div id="collection-menu" class="sub-menu" ng-show="currentCollection">
               <ul>
-                <li class="overview" ng-class="{active:page=='collection-overview'}"><a href="#/{{currentCollection.name}}/collection-overview"><span>Overview</span></a></li>
-                <li class="analysis" ng-class="{active:page=='analysis'}"><a href="#/{{currentCollection.name}}/analysis"><span>Analysis</span></a></li>
-                <li class="dataimport" ng-class="{active:page=='dataimport'}"><a href="#/{{currentCollection.name}}/dataimport"><span>Dataimport</span></a></li>
-                <li class="documents" ng-class="{active:page=='documents'}"><a href="#/{{currentCollection.name}}/documents"><span>Documents</span></a></li>
-                <li class="files" ng-class="{active:page=='files'}"><a href="#/{{currentCollection.name}}/files"><span>Files</span></a></li>
+                <li class="overview" ng-show="currentCollection.type === 'collection'" ng-class="{active:page=='collection-overview'}"><a href="#/{{currentCollection.name}}/collection-overview"><span>Overview</span></a></li>
+                <li class="overview" ng-show="currentCollection.type === 'alias'" ng-class="{active:page=='alias-overview'}"><a href="#/{{currentCollection.name}}/alias-overview"><span>Overview</span></a></li>
+                <li class="analysis" ng-show="!isMultiDestAlias(currentCollection)" ng-class="{active:page=='analysis'}"><a href="#/{{currentCollection.name}}/analysis"><span>Analysis</span></a></li>
+                <li class="dataimport" ng-show="!isMultiDestAlias(currentCollection)" ng-class="{active:page=='dataimport'}"><a href="#/{{currentCollection.name}}/dataimport"><span>Dataimport</span></a></li>
+                <li class="documents" ng-show="!isMultiDestAlias(currentCollection)" ng-class="{active:page=='documents'}"><a href="#/{{currentCollection.name}}/documents"><span>Documents</span></a></li>
+                <li class="files" ng-show="!isMultiDestAlias(currentCollection)" ng-class="{active:page=='files'}"><a href="#/{{currentCollection.name}}/files"><span>Files</span></a></li>
                 <li class="query" ng-class="{active:page=='query'}"><a href="#/{{currentCollection.name}}/query"><span>Query</span></a></li>
                 <li class="stream" ng-class="{active:page=='stream'}"><a href="#/{{currentCollection.name}}/stream"><span>Stream</span></a></li>
-                <li class="schema" ng-class="{active:page=='schema'}"><a href="#/{{currentCollection.name}}/schema"><span>Schema</span></a></li>
+                <li class="schema" ng-show="!isMultiDestAlias(currentCollection)" ng-class="{active:page=='schema'}"><a href="#/{{currentCollection.name}}/schema"><span>Schema</span></a></li>
               </ul>
             </div>
             <div id="core-selector">
diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js
index 6ba0d4c..784ea6e 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -88,6 +88,10 @@ solrAdminApp.config([
         templateUrl: 'partials/core_overview.html',
         controller: 'CoreOverviewController'
       }).
+      when('/:core/alias-overview', {
+        templateUrl: 'partials/alias_overview.html',
+        controller: 'AliasOverviewController'
+      }).
       when('/:core/collection-overview', {
         templateUrl: 'partials/collection_overview.html',
         controller: 'CollectionOverviewController'
@@ -444,6 +448,7 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
   $scope.refresh = function() {
       $scope.cores = [];
       $scope.collections = [];
+      $scope.aliases = [];
   }
 
   $scope.refresh();
@@ -466,19 +471,39 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
     System.get(function(data) {
       $scope.isCloudEnabled = data.mode.match( /solrcloud/i );
 
+      var currentCollectionName = $route.current.params.core;
+      delete $scope.currentCollection;
       if ($scope.isCloudEnabled) {
-        Collections.list(function (data) {
-          $scope.collections = [];
-          var currentCollectionName = $route.current.params.core;
-          delete $scope.currentCollection;
-          for (key in data.collections) {
-            var collection = {name: data.collections[key]};
-            $scope.collections.push(collection);
-            if (pageType == Constants.IS_COLLECTION_PAGE && collection.name == currentCollectionName) {
-              $scope.currentCollection = collection;
+        Collections.list(function (cdata) {
+          Collections.listaliases(function (adata) {
+            $scope.aliases = [];
+            for (var key in adata.aliases) {
+              props = {};
+              if (key in adata.properties) {
+                props = adata.properties[key];
+              }
+              var alias = {name: key, collections: adata.aliases[key], type: 'alias', properties: props};
+              $scope.aliases.push(alias);
+              if (pageType == Constants.IS_COLLECTION_PAGE && alias.name == currentCollectionName) {
+                $scope.currentCollection = alias;
+              }
+            }
+            $scope.collections = [];
+            for (key in cdata.collections) {
+              var collection = {name: cdata.collections[key], type: 'collection'};
+              $scope.collections.push(collection);
+              if (pageType == Constants.IS_COLLECTION_PAGE && collection.name == currentCollectionName) {
+                $scope.currentCollection = collection;
+              }
             }
-          }
-        })
+
+            $scope.aliases_and_collections = $scope.aliases;
+            if ($scope.aliases.length > 0) {
+              $scope.aliases_and_collections = $scope.aliases_and_collections.concat({name:'-----'});
+            }
+            $scope.aliases_and_collections = $scope.aliases_and_collections.concat($scope.collections);
+          });
+        });
       }
 
       $scope.showEnvironment = data.environment !== undefined;
@@ -502,6 +527,10 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
     $scope.http401 = sessionStorage.getItem("http401");
   };
 
+  $scope.isMultiDestAlias = function(selectedColl) {
+    return selectedColl && selectedColl.type === 'alias' && selectedColl.collections.includes(',');
+  };
+
   $scope.ping = function() {
     Ping.ping({core: $scope.currentCore.name}, function(data) {
       $scope.showPing = true;
@@ -519,8 +548,12 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
   }
 
   $scope.showCollection = function(collection) {
-    $location.url("/" + collection.name + "/collection-overview")
-  }
+    if (collection.type === 'collection') {
+      $location.url("/" + collection.name + "/collection-overview")
+    } else if (collection.type === 'alias') {
+      $location.url("/" + collection.name + "/alias-overview")
+    }
+  };
 
   $scope.$on('$routeChangeStart', function() {
       $rootScope.exceptions = {};
diff --git a/solr/webapp/web/js/angular/controllers/alias-overview.js b/solr/webapp/web/js/angular/controllers/alias-overview.js
new file mode 100644
index 0000000..6fbca38
--- /dev/null
+++ b/solr/webapp/web/js/angular/controllers/alias-overview.js
@@ -0,0 +1,27 @@
+/*
+ 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('AliasOverviewController',
+function($scope, $routeParams, Collections, Constants) {
+  $scope.resetMenu("collection-overview", Constants.IS_COLLECTION_PAGE);
+
+  $scope.refresh = function() {
+    $scope.selectedCollection = $scope.currentCollection;
+  };
+
+  $scope.refresh();
+});
diff --git a/solr/webapp/web/js/angular/controllers/collections.js b/solr/webapp/web/js/angular/controllers/collections.js
index 111d7ea..978fc71 100644
--- a/solr/webapp/web/js/angular/controllers/collections.js
+++ b/solr/webapp/web/js/angular/controllers/collections.js
@@ -28,6 +28,7 @@ solrAdminApp.controller('CollectionsController',
               for (var name in data.cluster.collections) {
                   var collection = data.cluster.collections[name];
                   collection.name = name;
+                  collection.type = 'collection';
                   var shards = collection.shards;
                   collection.shards = [];
                   for (var shardName in shards) {
@@ -50,10 +51,28 @@ solrAdminApp.controller('CollectionsController',
                       $scope.collection = collection;
                   }
               }
-              if ($routeParams.collection && !$scope.collection) {
-                  alert("No collection called " + $routeParams.collection)
-                  $location.path("/~collections");
-              }
+              // Fetch aliases using LISTALIASES to get properties
+              Collections.listaliases(function (adata) {
+                  // TODO: Population of aliases array duplicated in app.js
+                  $scope.aliases = [];
+                  for (var key in adata.aliases) {
+                      props = {};
+                      if (key in adata.properties) {
+                          props = adata.properties[key];
+                      }
+                      var alias = {name: key, collections: adata.aliases[key], type: 'alias', properties: props};
+                      $scope.aliases.push(alias);
+                      if ($routeParams.collection == 'alias_' + key) {
+                          $scope.collection = alias;
+                      }
+                  }
+                  // Decide what is selected in list
+                  if ($routeParams.collection && !$scope.collection) {
+                      alert("No collection or alias called " + $routeParams.collection);
+                      $location.path("/~collections");
+                  }
+              });
+
               $scope.liveNodes = data.cluster.liveNodes;
           });
           Zookeeper.configs(function(data) {
@@ -96,14 +115,6 @@ solrAdminApp.controller('CollectionsController',
       $scope.toggleDeleteAlias = function() {
         $scope.hideAll();
         $scope.showDeleteAlias = true;
-        Zookeeper.aliases({}, function(data){
-          if (Object.keys(data.aliases).length == 0) {
-            delete $scope.aliases;
-          } else {
-            $scope.aliases = data.aliases;
-          }
-        });
-
       }
 
       $scope.cancelCreateAlias = $scope.cancelDeleteAlias = function() {
@@ -116,12 +127,16 @@ solrAdminApp.controller('CollectionsController',
           collections.push($scope.aliasCollections[i].name);
         }
         Collections.createAlias({name: $scope.aliasToCreate, collections: collections.join(",")}, function(data) {
-          $scope.hideAll();
+          $scope.cancelCreateAlias();
+          $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
+          $location.path("/~collections/alias_" + $scope.aliasToCreate);
         });
       }
       $scope.deleteAlias = function() {
-        Collections.deleteAlias({name: $scope.aliasToDelete}, function(data) {
+        Collections.deleteAlias({name: $scope.collection.name}, function(data) {
           $scope.hideAll();
+          $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
+          $location.path("/~collections/");
         });
 
       };
diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js
index 3655639..8b371b6 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -30,6 +30,7 @@ solrAdminServices.factory('System',
     return $resource('admin/collections',
     {'wt':'json', '_':Date.now()}, {
     "list": {params:{action: "LIST"}},
+    "listaliases": {params:{action: "LISTALIASES"}},
     "status": {params:{action: "CLUSTERSTATUS"}},
     "add": {params:{action: "CREATE"}},
     "delete": {params:{action: "DELETE"}},
@@ -140,6 +141,11 @@ solrAdminServices.factory('System',
         }).success(success).error(error);
     }
 })
+.filter('splitByComma', function() {
+  return function(input) {
+    return input === undefined ? input : input.split(',');
+  }
+})
 .factory('Luke',
   ['$resource', function($resource) {
     return $resource(':core/admin/luke', {core: '@core', wt:'json', _:Date.now()}, {
diff --git a/solr/webapp/web/partials/alias_overview.html b/solr/webapp/web/partials/alias_overview.html
new file mode 100644
index 0000000..0cf8124
--- /dev/null
+++ b/solr/webapp/web/partials/alias_overview.html
@@ -0,0 +1,46 @@
+<!--
+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="dashboard">
+
+  <div class="clearfix">
+
+    <div id="collection" class="block collprops">
+
+      <h2><span>Alias: {{selectedCollection.name}}</span></h2>
+
+      <div class="content">
+
+        <dl>
+
+          <dt><span>Collections:</span></dt>
+          <dd>
+            <span ng-repeat="coll in (selectedCollection.collections | splitByComma)">
+              <a href="#~collections/{{coll}}">{{coll}}</a>{{$last ? '' : ', '}}
+            </span>
+          </dd>
+
+          <span ng-repeat="(key, value) in selectedCollection.properties">
+            <dt><span>{{ key }}</span></dt>
+            <dd>{{ value }}</dd>
+          </span>
+        </dl>
+
+      </div>
+    </div>
+  </div>
+
+</div>
diff --git a/solr/webapp/web/partials/collection_overview.html b/solr/webapp/web/partials/collection_overview.html
index ba09bd9..cf1c18c 100644
--- a/solr/webapp/web/partials/collection_overview.html
+++ b/solr/webapp/web/partials/collection_overview.html
@@ -18,7 +18,7 @@ limitations under the License.
 
   <div class="clearfix">
 
-    <div id="collection" class="block fieldlist">
+    <div id="collection" class="block collprops">
 
       <h2><span>Collection: {{selectedCollection.name}}</span></h2>
 
diff --git a/solr/webapp/web/partials/collections.html b/solr/webapp/web/partials/collections.html
index 60ef47a..198030c 100644
--- a/solr/webapp/web/partials/collections.html
+++ b/solr/webapp/web/partials/collections.html
@@ -18,119 +18,117 @@ limitations under the License.
 
   <div id="ui-block" style="display:none">&nbsp;</div><!-- @todo what is this for? -->
 
-  <div id="frame">
-
-    <div id="actions" class="actions clearfix">
-
-      <button id="add" class="action" ng-click="showAddCollection()"><span>Add Collection</span></button>
-
-      <button id="delete" class="warn requires-core" ng-click="showDeleteCollection()" ng-show="collection"><span>Delete</span></button>
-      <button id="reload" class="requires-core" ng-click="reloadCollection()" ng-show="collection"
-         ng-class="{success: reloadSuccess, warn: reloadFailure}"><span>Reload</span></button>
-      <button id="create-alias" class="action requires-core" ng-click="toggleCreateAlias()"><span>Create Alias</span></button>
-      <button id="delete-alias" class="action requires-core" ng-click="toggleDeleteAlias()"><span>Delete Alias</span></button>
+  <div class="actions clearfix">
+    <div class="action add" data-rel="add" ng-show="showAdd" style="left:0px">
 
-      <div class="action add" data-rel="add" ng-show="showAdd" style="left:0px">
+      <form>
 
-        <form>
-
-          <p class="clearfix"><label for="add_name">name:</label>
+        <p class="clearfix"><label for="add_name">name:</label>
           <input type="text" name="name" id="add_name" ng-model="newCollection.name" placeholder="new collection"></p>
 
-          <p class="clearfix"><label for="add_config">config set:</label>&nbsp;
+        <p class="clearfix"><label for="add_config">config set:</label>&nbsp;
           <select chosen ng-options="config.name as config.name for config in configs" name="config_name" id="add_config" ng-model="newCollection.configName">
           </select>
-          </p>
+        </p>
 
-          <p class="clearfix"><label for="add_numShards">numShards:</label>
+        <p class="clearfix"><label for="add_numShards">numShards:</label>
           <input type="text" name="numShards" id="add_numShards" ng-model="newCollection.numShards"></p>
 
-          <p class="clearfix"><label for="add_replicationFactor">replicationFactor:</label>
+        <p class="clearfix"><label for="add_replicationFactor">replicationFactor:</label>
           <input type="text" name="replicationFactor" id="add_replicationFactor" ng-model="newCollection.replicationFactor"></p>
 
-          <p class="clearfix"><a ng-click="showAdvanced=!showAdvanced">
-              <span id="add_advanced" ng-class="{open: showAdvanced}">Show advanced</span></a></p>
-          <div ng-show="showAdvanced">
+        <p class="clearfix"><a ng-click="showAdvanced=!showAdvanced">
+          <span id="add_advanced" ng-class="{open: showAdvanced}">Show advanced</span></a></p>
+        <div ng-show="showAdvanced">
 
-              <p>Advanced options: </p>
-              <p class="clearfix"><label for="add_router_name">router:</label>
-              <select name="routerName" id="add_router_name" ng-model="newCollection.routerName">
-                <option value="compositeId">Composite ID</option>
-                <option value="implicit">Implicit</option>
-              </select>
-              </p>
-
-              <p class="clearfix"><label for="add_maxShardsPerNode">maxShardsPerNode:</label>
-              <input type="text" name="replicationFactor" id="add_maxShardsPerNode" ng-model="newCollection.maxShardsPerNode"></p>
+          <p>Advanced options: </p>
+          <p class="clearfix"><label for="add_router_name">router:</label>
+            <select name="routerName" id="add_router_name" ng-model="newCollection.routerName">
+              <option value="compositeId">Composite ID</option>
+              <option value="implicit">Implicit</option>
+            </select>
+          </p>
 
-              <p class="clearfix"><label for="add_shards">shards:</label>
-              <input type="text" name="shards" id="add_shards" ng-model="newCollection.shards"></p>
+          <p class="clearfix"><label for="add_maxShardsPerNode">maxShardsPerNode:</label>
+            <input type="text" name="replicationFactor" id="add_maxShardsPerNode" ng-model="newCollection.maxShardsPerNode"></p>
 
-              <p class="clearfix"><label for="add_routerField">router.field:</label>
-              <input type="text" name="routerField" id="add_routerField" ng-model="newCollection.routerField"></p>
+          <p class="clearfix"><label for="add_shards">shards:</label>
+            <input type="text" name="shards" id="add_shards" ng-model="newCollection.shards"></p>
 
-              <p class="clearfix"><label for="add_autoAddReplicas">autoAddReplicas:</label>
-                <select name="autoAddReplicas" id="add_autoAddReplicas" ng-model="newCollection.autoAddReplicas">
-                  <option value="true">true</option>
-                  <option value="false">false</option>
-                </select>
-              </p>
+          <p class="clearfix"><label for="add_routerField">router.field:</label>
+            <input type="text" name="routerField" id="add_routerField" ng-model="newCollection.routerField"></p>
 
-          </div>
-          <p class="clearfix note error" ng-show="addMessage">
-            <span>{{addMessage}}</span>
+          <p class="clearfix"><label for="add_autoAddReplicas">autoAddReplicas:</label>
+            <select name="autoAddReplicas" id="add_autoAddReplicas" ng-model="newCollection.autoAddReplicas">
+              <option value="true">true</option>
+              <option value="false">false</option>
+            </select>
           </p>
 
-          <p class="clearfix buttons">
-            <button type="submit" class="submit" ng-click="addCollection()"><span>Add Collection</span></button>
-            <button type="reset" class="reset" ng-click="cancelAddCollection()"><span>Cancel</span></button>
-          </p>
+        </div>
+        <p class="clearfix note error" ng-show="addMessage">
+          <span>{{addMessage}}</span>
+        </p>
 
-        </form>
+        <p class="clearfix buttons">
+          <button type="submit" class="submit" ng-click="addCollection()"><span>Add Collection</span></button>
+          <button type="reset" class="reset" ng-click="cancelAddCollection()"><span>Cancel</span></button>
+        </p>
 
-      </div>
+      </form>
 
-      <div class="action delete" ng-show="showDelete">
+    </div>
+    <div class="action create-alias" ng-show="showCreateAlias">
 
-        <form>
+      <form>
 
-          <p>Please type collection name to confirm deletion:</p>
-          <p class="clearfix"><label for="collectiondeleteConfirm">Collection</label>
-          <input type="text" ng-model="collectionDeleteConfirm" id="collectionDeleteConfirm"></p>
+        <input type="hidden" name="core" data-core="current">
 
-          <p class="clearfix note error" ng-show="deleteMessage">
-            <span>{{deleteMessage}}</span>
-          </p>
+        <p class="clearfix"><label for="alias">Alias Name:</label>
+          <input type="text" name="alias" ng-model="aliasToCreate" id="alias"></p>
 
-          <p class="clearfix buttons">
-            <button class="submit" ng-click="deleteCollection()"><span>Delete</span></button>
-            <button type="reset" class="reset" ng-click="showDelete=false"><span>Cancel</span></button>
-          </p>
-        </form>
+        <p class="clearfix"><label for="aliasCollections">Collections:</label>
+          <select multiple id="aliasCollections" ng-model="aliasCollections" ng-options="collection.name for collection in collections" class="other">
+          </select></p>
 
-      </div>
 
-      <div class="action create-alias" ng-show="showCreateAlias">
+        <p class="clearfix note error" ng-show="renameMessage">
+          <span>{{renameMessage}}</span>
+        </p>
 
-        <form>
+        <p class="clearfix buttons">
+          <button class="submit" ng-click="createAlias()"><span>Create Alias</span></button>
+          <button type="reset" class="reset" ng-click="cancelCreateAlias()"><span>Cancel</span></button>
+        </p>
+      </form>
 
-          <input type="hidden" name="core" data-core="current">
+    </div>
+  </div>
 
-          <p class="clearfix"><label for="alias">Alias Name:</label>
-          <input type="text" name="alias" ng-model="aliasToCreate" id="alias"></p>
+  <div id="frame">
 
-          <p class="clearfix"><label for="aliasCollections">Collections:</label>
-          <select multiple id="aliasCollections" ng-model="aliasCollections" ng-options="collection.name for collection in collections" class="other">
-          </select></p>
+    <div id="actions" class="actions clearfix" ng-show="collection">
+
+      <button id="delete-collection" class="warn requires-core" ng-click="showDeleteCollection()" ng-show="collection && collection.type === 'collection'"><span>Delete collection</span></button>
+      <button id="delete-alias" class="action requires-core" ng-click="toggleDeleteAlias()" ng-show="collection && collection.type === 'alias'"><span>Delete alias</span></button>
+      <button id="reload" class="requires-core" ng-click="reloadCollection()" ng-show="collection && collection.type === 'collection'"
+         ng-class="{success: reloadSuccess, warn: reloadFailure}"><span>Reload</span></button>
+
+      <div class="action delete" ng-show="showDelete">
 
+        <form>
+
+          <p>Please type collection name to confirm deletion:</p>
+          <p class="clearfix"><label for="collectiondeleteConfirm">Collection</label>
+          <input type="text" ng-model="collectionDeleteConfirm" id="collectionDeleteConfirm"></p>
 
-          <p class="clearfix note error" ng-show="renameMessage">
-            <span>{{renameMessage}}</span>
+          <p class="clearfix note error" ng-show="deleteMessage">
+            <span>{{deleteMessage}}</span>
           </p>
 
           <p class="clearfix buttons">
-            <button class="submit" ng-click="createAlias()"><span>Create Alias</span></button>
-            <button type="reset" class="reset" ng-click="cancelCreateAlias()"><span>Cancel</span></button>
+            <button class="submit" ng-click="deleteCollection()"><span>Delete</span></button>
+            <button type="reset" class="reset" ng-click="showDelete=false"><span>Cancel</span></button>
           </p>
         </form>
 
@@ -139,27 +137,12 @@ limitations under the License.
       <div class="action delete-alias" ng-show="showDeleteAlias">
 
         <form>
-          <span ng-show="aliases">
-          <p class="clearfix"><label for="deleteAlias">Alias:</label>
-          <select id="deleteAlias" ng-model="aliasToDelete" ng-options="alias as alias for (alias, collections) in aliases" class="other">
-          </select></p>
-
-          <p class="clearfix note error" ng-show="swapMessage">
-            <span>{{swapMessage}}</span>
-          </p>
+          <p>Are you sure you want to delete alias {{ collection.name }}?</p>
 
           <p class="clearfix buttons">
             <button type="submit" class="submit" ng-click="deleteAlias()"><span>Delete Alias</span></button>
             <button type="reset" class="reset" ng-click="cancelDeleteAlias()"><span>Cancel</span></button>
           </p>
-          </span>
-          <span ng-hide="aliases">
-              <p>No aliases to delete.</p>
-          <p class="clearfix buttons">
-            <button type="reset" class="reset" ng-click="cancelDeleteAlias()"><span>Cancel</span></button>
-          </p>
-          </span>
-
         </form>
 
       </div>
@@ -168,12 +151,42 @@ limitations under the License.
 
 
     <div class="requires-core" ng-hide="collection">
-      <h2>Please select a collection</h2>
+      <h2 ng-show="aliases.length + collections.length > 0">Please select a collection or alias</h2>
     </div>
 
     <div id="data" class="requires-core clearfix" ng-show="collection">
 
-      <div class="block" id="collection-data">
+      <div class="block" id="alias-data" ng-show="collection.type === 'alias'">
+
+        <h2>Alias: {{collection.name}}</h2>
+
+        <div class="message-container">
+          <div class="message"></div>
+        </div>
+
+        <div class="content">
+
+          <ul>
+
+            <li>
+              <dl class="clearfix">
+                <dt><span>Collections:</span></dt>
+                <dd>
+                  <span ng-repeat="coll in (collection.collections | splitByComma)">
+                    <a href="#~collections/{{coll}}">{{coll}}</a>{{$last ? '' : ', '}}
+                  </span>
+                </dd>
+                <div ng-repeat="(key, value) in collection.properties">
+                  <dt><span>{{ key }}</span></dt>
+                  <dd><span>{{ value }}</span></dd>
+                </div>
+              </dl>
+            </li>
+          </ul>
+        </div>
+      </div>
+
+      <div class="block" id="collection-data" ng-show="collection.type === 'collection'">
 
           <h2>Collection: {{collection.name}}</h2>
 
@@ -215,12 +228,21 @@ limitations under the License.
               <dt><span>autoAddReplicas:</span></dt>
                 <dd>{{collection.autoAddReplicas}}</dd>
             </dl></li>
+
+            <li ng-show="collection.aliases"><dl class="clearfix">
+              <dt><span>aliases:</span></dt>
+              <dd>
+                  <span ng-repeat="coll in collection.aliases">
+                    <a href="#~collections/alias_{{coll}}">{{coll}}</a>{{$last ? '' : ', '}}
+                  </span>
+              </dd>
+            </dl></li>
           </ul>
 
         </div>
       </div>
 
-      <div class="block" id="shard-data">
+      <div class="block" id="shard-data" ng-show="collection.type === 'collection'">
         <div class="content shard" ng-repeat="shard in collection.shards">
           <a ng-click="toggleShard(shard)">
             <h2>
@@ -358,9 +380,16 @@ limitations under the License.
   </div>
 
   <div id="navigation" class="requires-core clearfix">
+    <button id="add" class="action" ng-click="showAddCollection()"><span>Add Collection</span></button>
+    <ul>
+      <li ng-repeat="c in collections" ng-class="{current: collection.name == c.name && collection.type === 'collection'}"><a href="#~collections/{{c.name}}">{{c.name}}</a></li>
+    </ul>
+    <hr/>
+    <button id="create-alias" class="action requires-core" ng-click="toggleCreateAlias()" ng-disabled="collections.length == 0"><span>Create Alias</span></button>
     <ul>
-      <li ng-repeat="c in collections" ng-class="{current: collection.name == c.name}"><a href="#~collections/{{c.name}}">{{c.name}}</a></li>
+      <li ng-repeat="c in aliases" ng-class="{current: collection.name == c.name && collection.type === 'alias'}"><a href="#~collections/alias_{{c.name}}">{{c.name}}</a></li>
     </ul>
+
   </div>
 
 </div>