You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by db...@apache.org on 2016/04/26 09:03:45 UTC

[2/2] ambari git commit: AMBARI-15821 Ability to provide logical urls to view instances, Added URL wizard and other UI fixes(Ashwin Rajeev via dipayanb)

AMBARI-15821 Ability to provide logical urls to view instances, Added URL wizard and other UI fixes(Ashwin Rajeev via dipayanb)


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

Branch: refs/heads/trunk
Commit: f62a927708bddb5f1dffe7342131cb307a550eee
Parents: 614b12f
Author: Dipayan Bhowmick <di...@gmail.com>
Authored: Tue Apr 26 12:33:26 2016 +0530
Committer: Dipayan Bhowmick <di...@gmail.com>
Committed: Tue Apr 26 12:33:26 2016 +0530

----------------------------------------------------------------------
 .../main/resources/ui/admin-web/app/index.html  |   5 +-
 .../ambariViews/CreateViewInstanceCtrl.js       |   5 +
 .../controllers/ambariViews/ViewUrlCtrl.js      | 153 ++++++++
 .../controllers/ambariViews/ViewUrlEditCtrl.js  |  94 +++++
 .../controllers/ambariViews/ViewsEditCtrl.js    |   3 +-
 .../controllers/ambariViews/ViewsListCtrl.js    |  14 +-
 .../ui/admin-web/app/scripts/i18n.config.js     |  26 +-
 .../ui/admin-web/app/scripts/routes.js          |  20 ++
 .../ui/admin-web/app/scripts/services/View.js   | 136 ++++++-
 .../resources/ui/admin-web/app/styles/main.css  |  23 +-
 .../admin-web/app/views/ambariViews/create.html |   8 -
 .../admin-web/app/views/ambariViews/edit.html   |   9 +-
 .../app/views/ambariViews/listTable.html        | 130 +++----
 .../app/views/ambariViews/listUrls.html         |  72 ++++
 .../ui/admin-web/app/views/leftNavbar.html      |  18 +-
 .../ui/admin-web/app/views/urls/create.html     |  51 +++
 .../admin-web/app/views/urls/create_step_1.html |  36 ++
 .../admin-web/app/views/urls/create_step_2.html |  44 +++
 .../admin-web/app/views/urls/create_step_3.html |  60 ++++
 .../ui/admin-web/app/views/urls/edit.html       |  73 ++++
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../resources/ViewUrlResourceDefinition.java    |  54 +++
 .../server/api/services/ViewUrlsService.java    | 144 ++++++++
 .../internal/DefaultProviderModule.java         |   2 +
 .../internal/ViewInstanceResourceProvider.java  |  23 +-
 .../internal/ViewURLResourceProvider.java       | 358 +++++++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   2 +
 .../ambari/server/orm/dao/ViewURLDAO.java       | 111 ++++++
 .../server/orm/entities/ViewInstanceEntity.java |  45 ++-
 .../server/orm/entities/ViewURLEntity.java      | 144 ++++++++
 .../server/upgrade/UpgradeCatalog240.java       |  19 +-
 .../apache/ambari/server/view/ViewRegistry.java |  21 +-
 .../view/configuration/InstanceConfig.java      |  15 -
 .../main/resources/Ambari-DDL-Derby-CREATE.sql  |  13 +-
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |  12 +-
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |  12 +-
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |  14 +-
 .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql     |  13 +-
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql |  12 +-
 .../resources/Ambari-DDL-SQLServer-CREATE.sql   |  13 +-
 .../src/main/resources/META-INF/persistence.xml |  19 +-
 .../ViewInstanceResourceProviderTest.java       |  49 ---
 .../server/upgrade/UpgradeCatalog240Test.java   |  15 +-
 .../ambari/view/ViewInstanceDefinition.java     |   5 -
 44 files changed, 1853 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/index.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/index.html b/ambari-admin/src/main/resources/ui/admin-web/app/index.html
index fa911a6..8d7e8e7 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/index.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/index.html
@@ -90,7 +90,7 @@
       </div>
     </div>
 
-    
+
     <!-- build:js scripts/vendor.js -->
     <!-- bower:js -->
     <script src="bower_components/jquery/dist/jquery.js"></script>
@@ -119,6 +119,7 @@
     <script src="bower_components/bootstrap/js/scrollspy.js"></script>
     <script src="bower_components/bootstrap/js/collapse.js"></script>
     <script src="bower_components/bootstrap/js/tab.js"></script>
+
     <!-- endbuild -->
 
     <!-- build:js scripts/main.js -->
@@ -139,6 +140,8 @@
     <script src="scripts/controllers/groups/GroupsEditCtrl.js"></script>
     <script src="scripts/controllers/ambariViews/ViewsListCtrl.js"></script>
     <script src="scripts/controllers/ambariViews/ViewsEditCtrl.js"></script>
+    <script src="scripts/controllers/ambariViews/ViewUrlCtrl.js"></script>
+    <script src="scripts/controllers/ambariViews/ViewUrlEditCtrl.js"></script>
     <script src="scripts/controllers/ambariViews/CreateViewInstanceCtrl.js"></script>
     <script src="scripts/controllers/clusters/ClustersManageAccessCtrl.js"></script>
     <script src="scripts/controllers/clusters/UserAccessListCtrl.js"></script>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
index 962b795..127bc74 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/CreateViewInstanceCtrl.js
@@ -198,4 +198,9 @@ angular.module('ambariAdminConsole')
     }
   });
 
+
+
+
+
+
 }]);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js
new file mode 100644
index 0000000..0cf8d03
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js
@@ -0,0 +1,153 @@
+/**
+ * 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('ambariAdminConsole')
+.controller('ViewUrlCtrl',['$scope', 'View', 'Alert', 'Cluster', '$routeParams', '$location', 'UnsavedDialog', '$translate', function($scope, View, Alert, Cluster, $routeParams, $location, UnsavedDialog, $translate) {
+  var $t = $translate.instant;
+  $scope.form = {};
+  $scope.constants = {
+    props: $t('views.properties')
+  };
+  var targetUrl = '/viewUrls';
+  $scope.url={};
+  $scope.formHolder = {};
+
+  View.getAllVisibleInstance().then(function(views) {
+    var names = [];
+    var instances=[];
+    views.map(function(view){
+      var nameVersion = view.view_name+" {"+view.version+"}";
+        names.push(nameVersion);
+      instances.push({nameV:nameVersion,instance:view.instance_name,cname:view.view_name,version:view.version});
+    });
+
+    var output = [],
+        keys = [];
+
+    angular.forEach(names, function(item) {
+      var key = item;
+      if(keys.indexOf(key) === -1) {
+        keys.push(key);
+        output.push(item);
+      }
+    });
+
+    $scope.viewsVersions =  output;
+    $scope.viewInstances =  instances;
+
+    if($routeParams.viewName && $routeParams.viewVersion && $routeParams.viewInstanceName){
+      var selectedView = $routeParams.viewName+" {"+$routeParams.viewVersion+"}";
+      $scope.url.selectedView = selectedView;
+      $scope.url.selectedInstance = instances.find(function(inst){
+         return inst.nameV === selectedView && inst.instance === $routeParams.viewInstanceName && inst.version === $routeParams.viewVersion && inst.cname === $routeParams.viewName;
+      });
+    }
+
+  }).catch(function(data) {
+    Alert.error($t('views.alerts.cannotLoadViews'), data.data.message);
+  });
+
+  $scope.filterByName = function(nameV){
+    return function (item) {
+      if (item.nameV === nameV)
+      {
+        return true;
+      }
+      return false;
+    };
+  };
+
+  $scope.chomp = function(viewNameVersion){
+    return viewNameVersion.substr(0,viewNameVersion.indexOf("{")).trim();
+  };
+
+
+  $scope.wizardController = function () {
+    var wizard = this;
+
+    //Model
+    wizard.currentStep = 1;
+    wizard.steps = [
+      {
+        step: 1,
+        name: $t('urls.step1'),
+        template: "views/urls/create_step_1.html"
+      },
+      {
+        step: 2,
+        name: $t('urls.step2'),
+        template: "views/urls/create_step_2.html"
+      },
+      {
+        step: 3,
+        name: $t('urls.step3'),
+        template: "views/urls/create_step_3.html"
+      }
+    ];
+    wizard.user = {};
+
+    //Functions
+    wizard.gotoStep = function(newStep) {
+      $scope.formHolder.form.submitted = true;
+      if (newStep < wizard.currentStep || $scope.formHolder.form.$valid) {
+        wizard.currentStep = newStep;
+      }
+    };
+
+    wizard.getStepTemplate = function(){
+      for (var i = 0; i < wizard.steps.length; i++) {
+        if (wizard.currentStep == wizard.steps[i].step) {
+          return wizard.steps[i].template;
+        }
+      }
+    };
+
+    wizard.save = function() {
+      $scope.formHolder.form.submitted = true;
+
+      if($scope.formHolder.form.$valid){
+
+        var payload = {ViewUrlInfo:{
+              url_name:$scope.url.urlName,
+              url_suffix:$scope.url.suffix,
+              view_instance_version:$scope.url.selectedInstance.version,
+              view_instance_name:$scope.url.selectedInstance.instance,
+              view_instance_common_name:$scope.url.selectedInstance.cname
+        }};
+
+        View.updateShortUrl(payload).then(function(urlStatus) {
+          Alert.success($t('urls.urlCreated', {
+            viewName:$scope.url.selectedInstance.cname ,
+            shortUrl:$scope.url.suffix,
+            urlName:$scope.url.urlName
+          }));
+          $scope.formHolder.form.$setPristine();
+          $location.path(targetUrl);
+        }).catch(function(data) {
+          Alert.error($t('views.alerts.cannotLoadViewUrls'), data.message);
+        });
+
+      }
+    };
+  }
+
+
+
+
+}]);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js
new file mode 100644
index 0000000..93edc69
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlEditCtrl.js
@@ -0,0 +1,94 @@
+/**
+ * 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('ambariAdminConsole')
+.controller('ViewUrlEditCtrl',['$scope', 'View', 'Alert', 'Cluster', '$routeParams', '$location', 'UnsavedDialog', '$translate','ConfirmationModal' ,function($scope, View, Alert, Cluster, $routeParams, $location, UnsavedDialog, $translate,ConfirmationModal) {
+  var $t = $translate.instant;
+  $scope.form = {};
+  $scope.constants = {
+    props: $t('views.properties')
+  };
+  var targetUrl = '/viewUrls';
+
+
+  function setUpEdit(){
+
+      View.getUrlInfo($routeParams.urlName).then(function(url) {
+        $scope.url = url.ViewUrlInfo;
+        $scope.nameVersion = url.ViewUrlInfo.view_instance_common_name +" {" + url.ViewUrlInfo.view_instance_version +"}"
+      }).catch(function(data) {
+        Alert.error($t('views.alerts.cannotLoadViewUrl'), data.data.message);
+      });
+  }
+
+  setUpEdit();
+
+
+  $scope.updateUrl = function() {
+      $scope.url_form.submitted = true;
+
+      if($scope.url_form.$valid){
+
+          var payload = {ViewUrlInfo:{
+              url_name:$scope.url.url_name,
+              url_suffix:$scope.url.url_suffix,
+              view_instance_version:'',
+              view_instance_name:'',
+              view_instance_common_name:''
+          }};
+
+          View.editShortUrl(payload).then(function(urlStatus) {
+              Alert.success($t('urls.urlUpdated', {
+                  viewName:$scope.url.view_instance_common_name ,
+                  shortUrl:$scope.url.suffix,
+                  urlName:$scope.url.url_name
+              }));
+              $scope.url_form.$setPristine();
+              $location.path(targetUrl);
+          }).catch(function(data) {
+              Alert.error($t('views.alerts.cannotLoadViewUrls'), data.data.message);
+          });
+
+      }
+  };
+
+
+    $scope.deleteUrl = function() {
+
+        ConfirmationModal.show(
+            $t('common.delete', {
+                term: $t('urls.url')
+            }),
+            $t('common.deleteConfirmation', {
+                instanceType: $t('urls.url').toLowerCase(),
+                instanceName: '"' + $scope.url.url_name + '"'
+            })
+        ).then(function() {
+            View.deleteUrl($scope.url.url_name).then(function() {
+                $location.path(targetUrl);
+            });
+        });
+
+
+
+    };
+
+
+
+}]);

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
index d46a30f..877e230 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsEditCtrl.js
@@ -244,8 +244,7 @@ angular.module('ambariAdminConsole')
         'ViewInstanceInfo':{
           'visible': $scope.settings.visible,
           'label': $scope.settings.label,
-          'description': $scope.settings.description,
-          'short_url': $scope.settings.shortUrl
+          'description': $scope.settings.description
         }
       };
       return View.updateInstance($routeParams.viewId, $routeParams.version, $routeParams.instanceId, data)

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
index 6d1dc52..ed389e1 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
@@ -18,7 +18,7 @@
 'use strict';
 
 angular.module('ambariAdminConsole')
-.controller('ViewsListCtrl',['$scope', 'View', '$modal', 'Alert', 'ConfirmationModal', '$location', '$translate', function($scope, View, $modal, Alert, ConfirmationModal, $location, $translate) {
+.controller('ViewsListCtrl',['$scope', 'View','$modal', 'Alert', 'ConfirmationModal', '$location', '$translate', function($scope, View, $modal, Alert, ConfirmationModal, $location, $translate) {
   var deferredList = [],
     $t = $translate.instant;
   $scope.constants = {
@@ -128,6 +128,18 @@ angular.module('ambariAdminConsole')
 
   $scope.reloadViews = function () {
     loadViews();
+  };
+
+
+  $scope.listViewUrls = function(){
+    View.allUrls().then(function(urls) {
+      $scope.urls = urls;
+      $scope.ViewNameFilterOptions = urls.items.map(function(url){
+        return url.ViewUrlInfo.view_instance_common_name;
+      });
+    }).catch(function(data) {
+      Alert.error($t('views.alerts.cannotLoadViewUrls'), data.data.message);
+    });
   }
 
 }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
index 68a4bec..3e475d9 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
@@ -32,6 +32,7 @@ angular.module('ambariAdminConsole')
       'signOut': 'Sign out',
       'clusters': 'Clusters',
       'views': 'Views',
+      'viewUrls': 'View URL\'s',
       'roles': 'Roles',
       'users': 'Users',
       'groups': 'Groups',
@@ -132,7 +133,9 @@ angular.module('ambariAdminConsole')
         'cannotLoadClusterStatus': 'Cannot load cluster status',
         'clusterRenamed': 'The cluster has been renamed to {{clusterName}}.',
         'cannotRenameCluster': 'Cannot rename cluster to {{clusterName}}',
-        'unsavedChanges': 'You have unsaved changes. Save changes or discard?'
+        'tooShort': 'URL is too short',
+        'tooLong': 'URL is too long',
+        'onlyText': 'You can add only text urls in the lower case'
       }
     },
 
@@ -202,6 +205,8 @@ angular.module('ambariAdminConsole')
       'pending': 'Pending...',
       'deploying': 'Deploying...',
       'properties': 'properties',
+      'urlCreate':'Create new URL',
+      'urlDelete':'Delete URL',
 
       'alerts': {
         'noSpecialChars': 'Must not contain any special characters.',
@@ -222,10 +227,27 @@ angular.module('ambariAdminConsole')
         'cannotSaveSettings': 'Cannot save settings',
         'cannotSaveProperties': 'Cannot save properties',
         'cannotDeleteInstance': 'Cannot delete instance',
-        'cannotLoadViews': 'Cannot load views'
+        'cannotLoadViews': 'Cannot load views',
+        'cannotLoadViewUrls': 'Cannot load view URL\'s',
+        'cannotLoadViewUrl': 'Cannot load view URL'
       }
     },
 
+    'urls':{
+      'name':'Name',
+      'url':'URL',
+      'create':'Create',
+      'edit':'Edit',
+      'view':'View',
+      'viewInstance':'Instance',
+      'step1':'Create URL',
+      'step2':'Select instance',
+      'step3':'Assign URL',
+      'noUrlsToDisplay':'No short URL\'s configured',
+      'urlCreated':'Created short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>',
+      'urlUpdated':'Updated short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>'
+    },
+
     'clusters': {
       'switchToList': 'Switch&nbsp;to&nbsp;list&nbsp;view',
       'switchToBlock': 'Switch&nbsp;to&nbsp;block&nbsp;view',

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
index 4fc4ea6..0566969 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/routes.js
@@ -88,6 +88,26 @@ angular.module('ambariAdminConsole')
       templateUrl: 'views/ambariViews/listTable.html',
       controller: 'ViewsListCtrl'
     },
+    listViewUrls: {
+      url: '/viewUrls',
+      templateUrl: 'views/ambariViews/listUrls.html',
+      controller: 'ViewsListCtrl'
+    },
+    createViewUrl:{
+      url: '/urls/new',
+      templateUrl: 'views/urls/create.html',
+      controller: 'ViewUrlCtrl'
+    },
+    linkViewUrl:{
+      url: '/urls/link/:viewName/:viewVersion/:viewInstanceName',
+      templateUrl: 'views/urls/create.html',
+      controller: 'ViewUrlCtrl'
+    },
+    editViewUrl:{
+      url: '/urls/edit/:urlName',
+      templateUrl: 'views/urls/edit.html',
+      controller: 'ViewUrlEditCtrl'
+    },
     edit: {
       url: '/views/:viewId/versions/:version/instances/:instanceId/edit',
       templateUrl: 'views/ambariViews/edit.html',

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
index cbe11e4..36bd32d 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js
@@ -24,6 +24,8 @@ angular.module('ambariAdminConsole')
     angular.extend(this, item);
   }
 
+
+
   ViewInstance.find = function(viewName, version, instanceName) {
     var deferred = $q.defer();
     var fields = [
@@ -51,6 +53,114 @@ angular.module('ambariAdminConsole')
   };
 
 
+  function ViewUrl(item) {
+    angular.extend(this, item);
+  }
+
+  function URLStatus(item){
+    angular.element(this,item);
+  }
+
+  ViewUrl.all = function() {
+    var deferred = $q.defer();
+
+    $http({
+      method: 'GET',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls',
+
+    })
+        .success(function(data) {
+          deferred.resolve(new ViewUrl(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+  ViewUrl.updateShortUrl = function(payload){
+    var deferred = $q.defer();
+
+    $http({
+      method: 'POST',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+payload.ViewUrlInfo.url_name,
+      data:payload
+    })
+        .success(function(data) {
+          deferred.resolve(new URLStatus(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+  ViewUrl.deleteUrl = function(urlName){
+    var deferred = $q.defer();
+
+    $http({
+      method: 'DELETE',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+ urlName,
+    })
+        .success(function(data) {
+          deferred.resolve(new URLStatus(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+  ViewUrl.editShortUrl = function(payload){
+    var deferred = $q.defer();
+
+    $http({
+      method: 'PUT',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+payload.ViewUrlInfo.url_name,
+      data:payload
+    })
+        .success(function(data) {
+          deferred.resolve(new URLStatus(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+  ViewUrl.urlInfo =  function(urlName){
+
+    var deferred = $q.defer();
+
+    $http({
+      method: 'GET',
+      dataType: "json",
+      url: Settings.baseUrl + '/view/urls/'+urlName,
+
+    })
+        .success(function(data) {
+          deferred.resolve(new ViewUrl(data));
+        })
+        .error(function(data) {
+          deferred.reject(data);
+        });
+
+    return deferred.promise;
+  };
+
+
+
   function View(item){
     var self = this;
     self.view_name = item.ViewInfo.view_name;
@@ -79,6 +189,27 @@ angular.module('ambariAdminConsole')
     return ViewInstance.find(viewName, version, instanceName);
   };
 
+  View.allUrls =  function(){
+    return ViewUrl.all()
+  };
+
+  View.getUrlInfo = function(urlName){
+    return ViewUrl.urlInfo(urlName);
+  };
+
+  View.deleteUrl = function(urlName){
+    return ViewUrl.deleteUrl(urlName);
+  };
+
+
+  View.updateShortUrl = function(payload){
+    return ViewUrl.updateShortUrl(payload);
+  };
+
+  View.editShortUrl = function(payload){
+    return ViewUrl.editShortUrl(payload);
+  };
+
   View.deleteInstance = function(viewName, version, instanceName) {
     return $http.delete(Settings.baseUrl +'/views/'+viewName+'/versions/'+version+'/instances/'+instanceName, {
       headers: {
@@ -168,8 +299,7 @@ angular.module('ambariAdminConsole')
         visible: instanceInfo.visible,
         icon_path: instanceInfo.icon_path,
         icon64_path: instanceInfo.icon64_path,
-        description: instanceInfo.description,
-        short_url:instanceInfo.shortUrl
+        description: instanceInfo.description
       };
 
     angular.forEach(instanceInfo.properties, function(property) {
@@ -308,7 +438,7 @@ angular.module('ambariAdminConsole')
       url: Settings.baseUrl + '/views',
       params:{
         'fields': fields.join(','),
-        'versions/ViewVersionInfo/system': false
+        'versions/ViewVersionInfo/system' : false
       }
     }).success(function(data) {
       var views = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
index edf8524..b55d89c 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
@@ -97,7 +97,7 @@
   -webkit-transform: rotateX(0deg);
   -ms-transform: rotateX(0deg);
   -o-transform: rotateX(0deg);
-  transform: rotateX(0deg); 
+  transform: rotateX(0deg);
 }
 .editable-list-container .actions-panel.ng-hide{
   -webkit-transform: rotateX(90deg);
@@ -215,6 +215,10 @@
   display: inline-block;
  }
 
+ .small-input{
+   max-width: 300px;
+ }
+
 .paginator{
   margin: 0;
 }
@@ -471,7 +475,7 @@ a.gotoinstance{
   display: block;
   float: left;
   text-decoration: none;
-  
+
 }
 #top-nav .navbar.navbar-static-top .brand.cluster-name{
   margin-left: 10px;
@@ -487,7 +491,7 @@ a.gotoinstance{
 }
 .create-view-form .description h4 span{
   font-weight: normal;
-  
+
 }
 .create-view-form .view-header{
 }
@@ -553,7 +557,7 @@ a.gotoinstance{
   top: 5px;
   right: 50px;
   z-index: 10;
-} 
+}
 .views-list-pane .search-container .close{
   right: 50px;
   top: 5px;
@@ -629,6 +633,9 @@ ul.nav li > a{
 .padding-left-30{
   padding-left: 30px;
 }
+.padding-bottom-30{
+  padding-bottom: 30px;
+}
 .no-margin-bottom{
   margin-bottom: 0!important;
 }
@@ -1136,7 +1143,7 @@ button.btn.btn-xs{
   -ms-transform: translateX(1000px);
   -o-transform: translateX(1000px);
   transform: translateX(1000px);
-  
+
   padding: 0;
   margin: 0;
   max-height: 0;
@@ -1260,15 +1267,15 @@ button.btn.btn-xs{
 }
 
 @-webkit-keyframes stretchdelay {
-  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }  
+  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
   20% { -webkit-transform: scaleY(1.0) }
 }
 
 @keyframes stretchdelay {
-  0%, 40%, 100% { 
+  0%, 40%, 100% {
     transform: scaleY(0.4);
     -webkit-transform: scaleY(0.4);
-  }  20% { 
+  }  20% {
     transform: scaleY(1.0);
     -webkit-transform: scaleY(1.0);
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
index 20ccadb..eaff70b 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/create.html
@@ -77,14 +77,6 @@
         </div>
       </div>
 
-      <div class="form-group" ng-class="{'has-error' : form.instanceCreateForm.submitted }">
-        <label for="" class="control-label col-sm-3">{{'views.shortUrl' | translate}}</label>
-        <div class="col-sm-9">
-          <input type="text" class="form-control" name="short_url" ng-model="instance.shortUrl" maxlength="200">
-        </div>
-      </div>
-
-
       <div class="form-group">
         <div class="col-sm-10 col-sm-offset-3">
           <div class="checkbox">

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
index b41abc8..8eff030 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/edit.html
@@ -78,14 +78,13 @@
           </div>
         </div>
 
-
-        <div class="form-group" ng-class="{'has-error' : form.instanceCreateForm.submitted }">
+        <div class="form-group">
           <label for="" class="control-label col-sm-3">{{'views.shortUrl' | translate}}</label>
           <div class="col-sm-9">
-            <input type="text" class="form-control" name="short_url" ng-model="settings.shortUrl" maxlength="200">
-
+            <p class="form-control-static"><a href="/#/main/view/{{instance.ViewInstanceInfo.view_name}}/{{settings.shortUrl}}" ng-if="settings.shortUrl">{{settings.shortUrl}}</a></p>
+            <a ng-if="!settings.shortUrl" href="#/urls/link/{{instance.ViewInstanceInfo.view_name}}/{{instance.ViewInstanceInfo.version}}/{{instance.ViewInstanceInfo.instance_name}}" class="btn btn-primary createuser-btn"><span class="glyphicon glyphicon-plus"></span> {{'views.urlCreate' | translate}}</a>
+          </div>
           </div>
-        </div>
 
 
         <div class="form-group">

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
index ae71d78..906eef5 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listTable.html
@@ -15,87 +15,87 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 -->
-    
+
 <div class="views-list-table">
-  <div class="clearfix">
-    <ol class="breadcrumb pull-left">
-      <li class="active">{{'common.views' | translate}}</li>
-      <button ng-click="reloadViews()"
-              class="btn btn-xs">
-        <i class="glyphicon glyphicon-refresh"></i>
-      </button>
-    </ol>
-    <div class="pull-right col-sm-4">
-      <div class="input-group search-container">
-        <input type="text" class="form-control search-input" placeholder="{{'common.search' | translate}}" ng-model="viewsFilter" ng-change="getFilteredViews()">
-        <button type="button" class="close clear-search" ng-show="viewsFilter" ng-click="viewsFilter=''; getFilteredViews()"><span aria-hidden="true">&times;</span><span class="sr-only">{{"common.controls.close" | translate}}</span></button>
+    <div class="clearfix">
+        <ol class="breadcrumb pull-left">
+            <li class="active">{{'common.views' | translate}}</li>
+            <button ng-click="reloadViews()"
+                    class="btn btn-xs">
+                <i class="glyphicon glyphicon-refresh"></i>
+            </button>
+        </ol>
+        <div class="pull-right col-sm-4">
+            <div class="input-group search-container">
+                <input type="text" class="form-control search-input" placeholder="{{'common.search' | translate}}" ng-model="viewsFilter" ng-change="getFilteredViews()">
+                <button type="button" class="close clear-search" ng-show="viewsFilter" ng-click="viewsFilter=''; getFilteredViews()"><span aria-hidden="true">&times;</span><span class="sr-only">{{"common.controls.close" | translate}}</span></button>
         <span class="input-group-addon">
           <span class="glyphicon glyphicon-search"></span>
         </span>
-      </div>
+            </div>
+        </div>
     </div>
-  </div>
-  <hr>
-  <div class="row">
-    <div class="col-sm-3 padding-left-30"><h4>{{'views.viewName' | translate}}</h4></div>
-    <div class="col-sm-3"><h4>{{'views.instances' | translate}}</h4></div>
-    <div class="col-sm-6"><h4></h4></div>
-  </div>
-  <accordion close-others="false">
-    <accordion-group ng-repeat="view in filteredViews" is-open="view.isOpened">
-      <accordion-heading>
-        <div class="row">
-          <div class="col-sm-3">
-            <i class="glyphicon glyphicon-chevron-right" ng-class="{'opened': view.isOpened}"></i>
-            {{view.view_name}}
-          </div>
-          <div class="col-sm-3">
+    <hr>
+    <div class="row">
+        <div class="col-sm-3 padding-left-30"><h4>{{'views.viewName' | translate}}</h4></div>
+        <div class="col-sm-3"><h4>{{'views.instances' | translate}}</h4></div>
+        <div class="col-sm-6"><h4></h4></div>
+    </div>
+    <accordion close-others="false">
+        <accordion-group ng-repeat="view in filteredViews" is-open="view.isOpened">
+            <accordion-heading>
+                <div class="row">
+                    <div class="col-sm-3">
+                        <i class="glyphicon glyphicon-chevron-right" ng-class="{'opened': view.isOpened}"></i>
+                        {{view.view_name}}
+                    </div>
+                    <div class="col-sm-3">
             <span ng-repeat="(version, vData) in view.versions">
               {{version}}
                 <span ng-switch="vData.status">
                   <span ng-switch-when="PENDING" class="viewstatus pending" ng-switch-when="true" tooltip="{{'views.pending' | translate}}"></span>
                   <div class="viewstatus deploying" ng-switch-when="DEPLOYING" tooltip="{{'views.deploying' | translate}}">
-                    <div class="rect1"></div>
-                    <div class="rect2"></div>
-                    <div class="rect3"></div>
+                      <div class="rect1"></div>
+                      <div class="rect2"></div>
+                      <div class="rect3"></div>
                   </div>
                   <span ng-switch-when="DEPLOYED">({{vData.count}})</span>
                   <span ng-switch-when="ERROR" tooltip="{{'views.alerts.deployError' | translate}}"><i class="fa fa-exclamation-triangle"></i></span>
                 </span>
               {{$last ? '' : ', '}}
             </span>
-          </div>
-          <div class="col-sm-6">{{view.description}}</div>
+                    </div>
+                    <div class="col-sm-6">{{view.description}}</div>
+                </div>
+            </accordion-heading>
+            <table class="table instances-table">
+                <tbody>
+                <tr ng-repeat="instance in view.instances">
+                    <td class="col-sm-1"></td>
+                    <td class="col-sm-5">
+                        <a href="#/views/{{view.view_name}}/versions/{{instance.ViewInstanceInfo.version}}/instances/{{instance.ViewInstanceInfo.instance_name}}/edit" class="instance-link">{{instance.label}}</a>
+                    </td>
+                    <td class="col-sm-5">{{instance.ViewInstanceInfo.version}}</td>
+                    <td class="col-sm-5 " ><div class="description-column" tooltip="{{instance.ViewInstanceInfo.description}}">{{instance.ViewInstanceInfo.description || 'No description'}}</div>
+                    </td>
+                </tr>
+                </tbody>
+                <tfoot>
+                <tr>
+                    <td class="col-sm-3"></td>
+                    <td class="col-sm-3">
+                        <a tooltip="{{view.canCreateInstance ? '' : constants.unable}}" class="btn btn-default createisntance-btn {{view.canCreateInstance ? '' : 'disabled'}}" href ng-click="gotoCreate(view.view_name, view.canCreateInstance);"><span class="glyphicon glyphicon-plus"></span> {{'views.create' | translate}}</a>
+                    </td>
+                    <td class="col-sm-3"></td>
+                    <td class="col-sm-3">
+                    </td>
+                </tr>
+                </tfoot>
+            </table>
+        </accordion-group>
+        <div class="alert alert-info" ng-show="views && !filteredViews.length">
+            {{'common.alerts.nothingToDisplay' | translate: '{term: constants.views}'}}
         </div>
-      </accordion-heading>
-      <table class="table instances-table">
-        <tbody>
-          <tr ng-repeat="instance in view.instances">
-            <td class="col-sm-1"></td>
-            <td class="col-sm-5">
-              <a href="#/views/{{view.view_name}}/versions/{{instance.ViewInstanceInfo.version}}/instances/{{instance.ViewInstanceInfo.instance_name}}/edit" class="instance-link">{{instance.label}}</a>
-            </td>
-            <td class="col-sm-5">{{instance.ViewInstanceInfo.version}}</td>
-            <td class="col-sm-5 " ><div class="description-column" tooltip="{{instance.ViewInstanceInfo.description}}">{{instance.ViewInstanceInfo.description || 'No description'}}</div>
-            </td>
-          </tr>
-        </tbody>
-        <tfoot>
-          <tr>
-            <td class="col-sm-3"></td>
-            <td class="col-sm-3">
-              <a tooltip="{{view.canCreateInstance ? '' : constants.unable}}" class="btn btn-default createisntance-btn {{view.canCreateInstance ? '' : 'disabled'}}" href ng-click="gotoCreate(view.view_name, view.canCreateInstance);"><span class="glyphicon glyphicon-plus"></span> {{'views.create' | translate}}</a>
-            </td>
-            <td class="col-sm-3"></td>
-            <td class="col-sm-3">
-            </td>
-          </tr>
-        </tfoot>
-      </table>
-    </accordion-group>
-    <div class="alert alert-info" ng-show="views && !filteredViews.length">
-      {{'common.alerts.nothingToDisplay' | translate: '{term: constants.views}'}}
-    </div>
 
-  </accordion>
+    </accordion>
 </div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html
new file mode 100644
index 0000000..a2949f9
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html
@@ -0,0 +1,72 @@
+<!--
+* 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 class="views-list-table" data-ng-init="listViewUrls()">
+
+
+    <div class="clearfix">
+        <ol class="breadcrumb pull-left">
+            <li class="active">{{'common.viewUrls' | translate}}</li>
+        </ol>
+        <div class="pull-right top-margin-4">
+            <link-to route="views.createViewUrl" class="btn btn-primary createuser-btn"><span class="glyphicon glyphicon-plus"></span> {{'views.urlCreate' | translate}}</link-to>
+        </div>
+    </div>
+    <hr>
+    <table class="table table-striped table-hover">
+        <thead>
+        <tr>
+
+            <th>
+               <span class="padding-bottom-30">{{'urls.name' | translate}}</span>
+            </th>
+            <th>
+                <span class="padding-bottom-30">{{'urls.url' | translate}}</span>
+            </th>
+            <th>
+                <span class="padding-bottom-30">{{'urls.view' | translate}}</span>
+            </th>
+            <th>
+                <span class="padding-bottom-30">{{'urls.viewInstance' | translate}}</span>
+            </th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="url in urls.items">
+
+            <td>
+                <a href="#/urls/edit/{{url.ViewUrlInfo.url_name}}">{{url.ViewUrlInfo.url_name}}</a>
+            </td>
+            <td>
+                <a href="/#/main/view/{{url.ViewUrlInfo.view_instance_common_name}}/{{url.ViewUrlInfo.url_suffix}}">/main/view/{{url.ViewUrlInfo.view_instance_common_name}}/{{url.ViewUrlInfo.url_suffix}}</a>
+            </td>
+            <td>
+                <span>{{url.ViewUrlInfo.view_instance_common_name}} {{"{"+url.ViewUrlInfo.view_instance_version+"}"}} </span>
+            </td>
+            <td>
+                <span>{{url.ViewUrlInfo.view_instance_name}}</span>
+            </td>
+
+        </tr>
+        </tbody>
+    </table>
+    <div class="alert alert-info col-sm-12" ng-show="!urls.items.length">
+        {{'urls.noUrlsToDisplay'| translate}}
+    </div>
+
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
index 9bc54ff..b155041 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/leftNavbar.html
@@ -22,19 +22,19 @@
       <div class="cluster-section" ng-show="cluster">
         <div id="cluster-name"  ng-switch on="editCluster.editingName">
           <h5 ng-switch-when="false"><div title={{cluster.Clusters.cluster_name}} class="clusterDisplayName">{{clusterDisplayName()}}</div>
-            <i ng-show="cluster.Clusters.provisioning_state == 'INSTALLED'" 
-               ng-click="toggleEditName()" 
+            <i ng-show="cluster.Clusters.provisioning_state == 'INSTALLED'"
+               ng-click="toggleEditName()"
                class="glyphicon glyphicon-edit pull-right edit-cluster-name renameCluster" tooltip="{{'common.renameCluster' | translate}}">
             </i>
           </h5>
 
-          <form ng-keyup="toggleEditName($event)" 
-                tabindex="1" 
-                name="editClusterNameForm" 
-                class="editClusterNameForm" 
+          <form ng-keyup="toggleEditName($event)"
+                tabindex="1"
+                name="editClusterNameForm"
+                class="editClusterNameForm"
                 ng-switch-when="true"
                 ng-submit="editCluster.name !== cluster.Clusters.cluster_name && editClusterNameForm.newClusterName.$valid && confirmClusterNameChange()">
-            <div class="form-group" 
+            <div class="form-group"
                  ng-class="{'has-error': editClusterNameForm.newClusterName.$invalid && !editClusterNameForm.newClusterName.$pristine }">
               <input
                   autofocus
@@ -91,10 +91,12 @@
     <div class="panel-body">
       <ul class="nav nav-pills nav-stacked">
         <li ng-class="{active: isActive('views.list')}"><link-to route="views.list" class="viewslist-link">{{'common.views' | translate}}</link-to></li>
+        <li ng-class="{active: isActive('views.listViewUrls')}"><link-to route="views.listViewUrls" class="viewsUrlList-link">{{'common.viewUrls' | translate}}</link-to></li>
       </ul>
     </div>
   </div>
 
+
   <div class="panel panel-default">
     <div class="panel-heading"><span class="glyphicon glyphicon-user"></span> {{'common.userGroupManagement' | translate}}</div>
     <div class="panel-body">
@@ -115,4 +117,4 @@
     </div>
   </div>
 </div>
-  
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html
new file mode 100644
index 0000000..eb31b42
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html
@@ -0,0 +1,51 @@
+<!--
+* 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.
+-->
+<ol class="breadcrumb">
+    <li><a href="#/users">{{'urls.url' | translate}}</a></li>
+    <li class="active">{{'urls.create' | translate}}</li>
+</ol>
+<hr>
+    <div class="row">
+        <div class="col-sm-10">
+
+            <div id="wizard-container" ng-controller="wizardController as wizard">
+
+                <div id="wizard-step-container" class="bottom-margin">
+                    <ul class="nav nav-pills nav-justified">
+                        <li ng-repeat="step in wizard.steps" ng-class="{'active':step.step == wizard.currentStep}"><a ng-click="wizard.gotoStep(step.step)" href="">{{step.name}}</a></li>
+                    </ul>
+                </div>
+
+                <div id="wizard-content-container">
+                    <ng-include src="wizard.getStepTemplate()"></ng-include>
+                </div>
+
+                <div id="wizard-navigation-container">
+                    <div class="pull-right">
+                <span class="btn-group">
+                  <button ng-disabled="wizard.currentStep <= 1" class="btn btn-default" name="previous" type="button" ng-click="wizard.gotoStep(wizard.currentStep - 1)"><i class="fa fa-arrow-left"></i> Previous step</button>
+                  <button ng-disabled="wizard.currentStep >= wizard.steps.length" class="btn btn-primary" name="next" type="button" ng-click="wizard.gotoStep(wizard.currentStep + 1)">Next step <i class="fa fa-arrow-right"></i></button>
+                </span>
+                        <button ng-disabled="wizard.currentStep != wizard.steps.length" class="btn btn-success" name="next" type="button" ng-click="wizard.save()"> <i class="fa fa-floppy-o"></i> Save</button>
+                    </div>
+                </div>
+
+            </div>
+
+        </div>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html
new file mode 100644
index 0000000..e19313e
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html
@@ -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.
+-->
+
+<form ng-controller="wizardController as wizard" class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+</form>

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html
new file mode 100644
index 0000000..36bce88
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.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.
+-->
+<form  class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" disabled id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+</form>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html
new file mode 100644
index 0000000..e6b3c1c
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html
@@ -0,0 +1,60 @@
+<!--
+* 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.
+-->
+<form  class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" disabled id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
+    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+    <div class="col-sm-10">
+      <select class="form-control" disabled id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+
+  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_suffix.$error.required  && formHolder.form.submitted}">
+    <label for="urlsuffixin" class="col-sm-2 control-label">{{'views.shortUrl' | translate}}</label>
+    <div class="col-sm-10">
+      <div class="input-group">
+      <span id="basic-addon1" class="input-group-addon">/main/view/{{chomp(url.selectedView)}}/</span><input aria-describedby="basic-addon1" type="text" class="form-control" id="urlsuffixin" name="url_view_suffix" placeholder="{{'views.shortUrl' | translate}}" ng-model="url.suffix" ng-pattern="/[a-z]+/" ng-minlength="3" ng-maxlength="10" required autocomplete="off">
+        </div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.required   && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.minlength   && formHolder.form.submitted">{{'common.alerts.tooShort' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.maxlength   && formHolder.form.submitted">{{'common.alerts.tooLong' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.pattern   && formHolder.form.submitted">{{'common.alerts.onlyText' | translate}}</div>
+
+    </div>
+  </div>
+
+</form>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html
new file mode 100644
index 0000000..de7722e
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/edit.html
@@ -0,0 +1,73 @@
+<!--
+* 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.
+-->
+<ol class="breadcrumb">
+  <li><a href="#/users">{{'urls.url' | translate}}</a></li>
+  <li class="active">{{'urls.edit' | translate}}</li>
+  <div class="pull-right top-margin-4">
+    <button class="btn deleteuser-btn btn-danger" ng-click="deleteUrl()">{{'views.urlDelete' | translate}}</button>
+  </div>
+</ol>
+<hr>
+
+
+<form  class="form-horizontal create-user-form" role="form" novalidate name="url_form" autocomplete="off">
+  <div class="form-group" ng-class="{'has-error' : url_form.url_name.$error.required  && url_form.submitted}">
+    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+    <div class="col-sm-10">
+      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.url_name" required autocomplete="off">
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_name.$error.required  && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+  <div class="form-group" ng-class="{'has-error' : url_form.url_view_name.$error.required  && url_form.submitted}">
+    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+    <div class="col-sm-10">
+      <input class="form-control" disabled id="urlselect" name="url_view_name" ng-model="nameVersion" required>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_name.$error.required  && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+  <div class="form-group" ng-class="{'has-error' : url_form.url_view_instance_name.$error.required  && url_form.submitted}">
+    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+    <div class="col-sm-10">
+      <input class="form-control" disabled id="urlinstanceselect" name="url_view_instance_name"  ng-model="url.view_instance_name" required>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_instance_name.$error.required  && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+    </div>
+  </div>
+
+
+
+  <div class="form-group" ng-class="{'has-error' : url_form.url_view_suffix.$error.required  && url_form.submitted}">
+    <label for="urlsuffixin" class="col-sm-2 control-label">{{'views.shortUrl' | translate}}</label>
+    <div class="col-sm-10">
+      <div class="input-group">
+      <span id="basic-addon1" class="input-group-addon">/main/view/{{url.view_instance_common_name}}/</span><input aria-describedby="basic-addon1" type="text" class="form-control" id="urlsuffixin" name="url_view_suffix" placeholder="{{'views.shortUrl' | translate}}" ng-model="url.url_suffix" ng-pattern="/[a-z]+/" ng-minlength="3" ng-maxlength="10" required autocomplete="off">
+        </div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.required   && url_form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.minlength   && url_form.submitted">{{'common.alerts.tooShort' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.maxlength   && url_form.submitted">{{'common.alerts.tooLong' | translate}}</div>
+      <div class="alert alert-danger top-margin" ng-show="url_form.url_view_suffix.$error.pattern   && url_form.submitted">{{'common.alerts.onlyText' | translate}}</div>
+
+    </div>
+  </div>
+  <div class="pull-right">
+    <button ng-disabled="wizard.currentStep != wizard.steps.length" class="btn btn-success" name="update_url_button" type="button" ng-click="updateUrl()"> <i class="fa fa-edit"></i> Update</button>
+  </div>
+
+</form>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index eed2703..0b77511 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -237,6 +237,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new ViewInstanceResourceDefinition(subResourceDefinitions);
         break;
 
+      case ViewURL:
+        resourceDefinition = new ViewUrlResourceDefinition();
+        break;
+
       case Blueprint:
         resourceDefinition = new BlueprintResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java
new file mode 100644
index 0000000..d2c7b62
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ViewUrlResourceDefinition.java
@@ -0,0 +1,54 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.server.api.resources;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+import java.util.Collections;
+import java.util.Set;
+
+
+/**
+ * View resource definition.
+ */
+public class ViewUrlResourceDefinition extends BaseResourceDefinition {
+
+  // ----- Constructors ------------------------------------------------------
+
+  /**
+   * Construct a view resource definition.
+   */
+  public ViewUrlResourceDefinition() {
+    super(Resource.Type.ViewURL);
+  }
+
+
+  // ----- ResourceDefinition ------------------------------------------------
+
+  @Override
+  public String getPluralName() {
+    return "view_urls";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "view_url";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java
new file mode 100644
index 0000000..3827c18
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ViewUrlsService.java
@@ -0,0 +1,144 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.server.api.services;
+
+import com.google.common.base.Optional;
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.Collections;
+
+
+/**
+ * Service responsible for view resource requests.
+ */
+@Path("/view/urls")
+public class ViewUrlsService extends BaseService {
+
+  /**
+   * Get the list of all registered view URLs
+   * @param headers
+   * @param ui
+
+   * @return collections of all view urls and any instances registered against them
+   */
+  @GET
+  @Produces("text/plain")
+  public Response getViewUrls(@Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createViewUrlResource(Optional.<String>absent()));
+  }
+
+
+  /**
+   * Create a new View URL
+   * @param body
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @POST
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response createUrl(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, body, ui, Request.Type.POST, createViewUrlResource(Optional.of(urlName)));
+  }
+
+
+  /**
+   * Update a view URL
+   * @param body
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @PUT
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response updateUrl(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                            @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, body, ui, Request.Type.PUT, createViewUrlResource(Optional.of(urlName)));
+  }
+
+  /**
+   * Remove a view URL
+   * @param body
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @DELETE
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response deleteUrl(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                            @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, body, ui, Request.Type.DELETE, createViewUrlResource(Optional.of(urlName)));
+  }
+
+
+  /**
+   * Get information about a single view URL
+   * @param headers
+   * @param ui
+   * @param urlName
+   * @return
+   * @throws AuthorizationException
+     */
+  @GET
+  @Path("{urlName}")
+  @Produces("text/plain")
+  public Response getUrl(@Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("urlName") String urlName) throws AuthorizationException {
+    return handleRequest(headers, null, ui, Request.Type.GET, createViewUrlResource(Optional.of(urlName)));
+  }
+
+
+
+
+  // ----- helper methods ----------------------------------------------------
+
+  /**
+   * Create a view URL resource.
+   *
+   * @param urlName Name of the URL
+   *
+   * @return a view URL resource instance
+   */
+  private ResourceInstance createViewUrlResource(Optional<String> urlName) {
+    return createResource(Resource.Type.ViewURL,Collections.singletonMap(Resource.Type.ViewURL, urlName.isPresent()?urlName.get().toString():null));
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
index c7dc117..4e7a032 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
@@ -68,6 +68,8 @@ public class DefaultProviderModule extends AbstractProviderModule {
         return new ViewVersionResourceProvider();
       case ViewInstance:
         return new ViewInstanceResourceProvider();
+      case ViewURL:
+        return new ViewURLResourceProvider();
       case StackServiceComponentDependency:
         return new StackDependencyResourceProvider(propertyIds, keyPropertyIds);
       case Permission:

http://git-wip-us.apache.org/repos/asf/ambari/blob/f62a9277/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
index 6523962..2a9eee7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
@@ -35,6 +35,7 @@ import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.orm.entities.ViewEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.orm.entities.ViewURLEntity;
 import org.apache.ambari.server.security.authorization.RoleAuthorization;
 import org.apache.ambari.server.view.ViewRegistry;
 import org.apache.ambari.server.view.validation.InstanceValidationResultImpl;
@@ -242,7 +243,10 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
     setResourceProperty(resource, VISIBLE_PROPERTY_ID, viewInstanceEntity.isVisible(), requestedIds);
     setResourceProperty(resource, STATIC_PROPERTY_ID, viewInstanceEntity.isXmlDriven(), requestedIds);
     setResourceProperty(resource, CLUSTER_HANDLE_PROPERTY_ID, viewInstanceEntity.getClusterHandle(), requestedIds);
-    setResourceProperty(resource, SHORT_URL_PROPERTY_ID, viewInstanceEntity.getShortUrl(), requestedIds);
+    ViewURLEntity viewUrl = viewInstanceEntity.getViewUrl();
+    if(viewUrl != null) {
+      setResourceProperty(resource, SHORT_URL_PROPERTY_ID, viewUrl.getUrlSuffix(), requestedIds);
+    }
 
     // only allow an admin to access the view properties
     if (ViewRegistry.getInstance().checkAdmin()) {
@@ -344,11 +348,6 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
       viewInstanceEntity.setClusterHandle((String) properties.get(CLUSTER_HANDLE_PROPERTY_ID));
     }
 
-    if (properties.containsKey(SHORT_URL_PROPERTY_ID)) {
-      viewInstanceEntity.setShortUrl((String) properties.get(SHORT_URL_PROPERTY_ID));
-    }
-
-
     Map<String, String> instanceProperties = new HashMap<String, String>();
 
     boolean isUserAdmin = viewRegistry.checkAdmin();
@@ -402,11 +401,6 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
             throw new IllegalStateException("The view " + viewName + " is not loaded.");
           }
 
-          if(!Strings.isNullOrEmpty(instanceEntity.getShortUrl()) && viewRegistry.duplicatedShortUrl(instanceEntity)){
-            throw new DuplicateResourceException("The short url " + instanceEntity.getShortUrl() + " already exists for "+ instanceEntity.getViewEntity().getCommonName() +
-                    " and version "+instanceEntity.getViewEntity().getVersion());
-          }
-
           if (viewRegistry.instanceExists(instanceEntity)) {
             throw new DuplicateResourceException("The instance " + instanceEntity.getName() + " already exists.");
           }
@@ -428,16 +422,9 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
       @Transactional
       @Override
       public Void invoke() throws AmbariException {
-        ViewRegistry       viewRegistry   = ViewRegistry.getInstance();
-
         ViewInstanceEntity instance = toEntity(properties, true);
         ViewEntity         view     = instance.getViewEntity();
 
-        if(!Strings.isNullOrEmpty(instance.getShortUrl()) && viewRegistry.duplicatedShortUrl(instance)){
-          throw new DuplicateResourceException("The short url " + instance.getShortUrl() + " already exists for "+ instance.getViewEntity().getCommonName() +
-                  " and version "+instance.getViewEntity().getVersion());
-        }
-
         if (includeInstance(view.getCommonName(), view.getVersion(), instance.getInstanceName(), false)) {
           try {
             ViewRegistry.getInstance().updateViewInstance(instance);