You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@falcon.apache.org by pe...@apache.org on 2016/08/22 04:47:59 UTC

[06/16] falcon git commit: FALCON-2118 Proposal for new UI changes

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/controllers/root-controller.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/controllers/root-controller.js b/falcon-ui/app/js/controllers/root-controller.js
index 709f6b2..b5dadaa 100644
--- a/falcon-ui/app/js/controllers/root-controller.js
+++ b/falcon-ui/app/js/controllers/root-controller.js
@@ -22,11 +22,11 @@
 
   app.controller('RootCtrl', [
     "$scope", "$timeout", "Falcon", "FileApi", "EntityModel",
-    "$state", "X2jsService", "ValidationService", "SpinnersFlag", "EntityFalcon",
+    "$state", "X2jsService", "ValidationService", "SpinnersFlag", "EntityFalcon", '$localStorage',
     function ($scope, $timeout, Falcon, FileApi,
-              EntityModel, $state, X2jsService, validationService, SpinnersFlag, EntityFalcon) {
+              EntityModel, $state, X2jsService, validationService, SpinnersFlag, EntityFalcon, $localStorage) {
 
-      var resultsPerPage = 10;
+      var resultsPerPage = 20;
 
       $scope.server = Falcon;
       $scope.validations = validationService;
@@ -35,28 +35,28 @@
 
       $scope.pages = [];
       $scope.nextPages = false;
+      $scope.hasClusters = true;
 
       $scope.handleFile = function (evt) {
         Falcon.logRequest();
         FileApi.loadFile(evt).then(function () {
           if (EntityModel.type === 'Type not recognized') {
-            Falcon.logResponse('error', {status: 'ERROR', message:'Invalid xml. File not uploaded'}, false);
+            Falcon.logResponse('error', {status: 'ERROR', message:"Entity type not recognized"}, false);
           } else {
-            Falcon.postSubmitEntity(FileApi.fileRaw, EntityModel.type).success(function (response) {
-              Falcon.logResponse('success', response, false);
-              $scope.refreshList($scope.tags);
-            }).error(function (err) {
-              Falcon.logResponse('error', err, false);
-            });
+            var entityType = EntityModel.type;
+            $state.go("forms." + entityType + ".general", {'action':'import'}, {reload: true});
           }
-
         });
       };
 
-      $scope.goPage = function(page){
-        $scope.loading = true;
+      $scope.goPage = function(page, type){
+        $scope.currentPage = page;
+        if (!(type && type == 'list')) {
+          $scope.loading = true;
+        }
+
         var offset = (page-1) * resultsPerPage;
-        EntityFalcon.searchEntities($scope.entityName, $scope.entityTags, $scope.entityType, offset).then(function() {
+        return EntityFalcon.searchEntities($scope.entityName, $scope.entityTags, $scope.entityType, offset).then(function() {
           if (EntityFalcon.data !== null) {
             $scope.actualPage = page;
             $scope.searchList = EntityFalcon.data.entity;
@@ -72,14 +72,16 @@
                 $scope.pages[i].enabled = true;
               }
             }
-            if($scope.searchList.length === 0){
+            if($scope.searchList.length === 0 && !(type && type == 'list')){
               Falcon.warningMessage("No results matched the search criteria.");
             }
             $timeout(function() {
               angular.element('#tagsInput').focus();
             }, 0, false);
             Falcon.responses.listLoaded = true;
-            $scope.loading = false;
+            if (!(type && type == 'list')) {
+              $scope.loading = false;
+            }
           }
         });
       };
@@ -128,13 +130,134 @@
       };
 
       $scope.cancel = function (type, state) {
+        var message = type + ' edition cancelled';
+        if(type === 'cluster'){
+          message = 'Create New Cluster operation cancelled'
+        }
         var cancelInfo = {
           state: state || $state.current.name,
-          message: type + ' edition canceled '
+          message: message
         };
         Falcon.logResponse('cancel', cancelInfo, type, false);
       };
 
+      $scope.displayResults = function (remove) {
+        $state.go("main");
+        $scope.refreshList($scope.tags);
+        (!remove) ? $scope.persistSearch($scope.tags) : '';
+
+      };
+
+      $scope.persistSearch = function(newTag){
+        var storedTags = $localStorage['SearchedTag'],
+            flagExit = false,
+            newval = newTag[newTag.length -1].text;
+
+
+        if(storedTags !== undefined && newTag !== undefined && newTag.length > 0){
+
+          for(var t=0; t<storedTags.length; t++){
+            if(storedTags[t].toLowerCase() === newval.toLowerCase()) {
+              flagExit = true;
+              break;
+            }
+          }
+          if(!flagExit ) {
+            storedTags.push(newval);
+            $localStorage['SearchedTag'] = storedTags;
+          }
+
+          if(storedTags.length > 5) { storedTags.splice(0,1); $localStorage['SearchedTag'] = storedTags; }
+
+        } else if(newTag !== undefined && newTag.length > 0) {
+          $localStorage['SearchedTag'] = [newval];
+        }
+
+      };
+
+      $scope.clearTags = function(){
+        $scope.tags = [];
+        $scope.refreshList($scope.tags);
+      };
+
+$scope.loadTags = function(query) {
+        var tags = new Array(), storedTags = $localStorage['SearchedTag'], tagAdded = false, tempTags = [];
+        if(!$scope.$parent.nameFounded){
+          tags.push({ text: 'Name:' + query });
+          tempTags.push('Name:' + query);
+        }
+
+        if(storedTags != undefined && storedTags.length > 0){
+          for(var e=0; e < storedTags.length; e++){
+              if(storedTags[e].toLowerCase().indexOf(query.toLowerCase()) != -1){
+                 if(tempTags.indexOf(storedTags[e]) == -1) {
+                    tags.push({ text: storedTags[e] });
+                    tempTags.push('Name:' + query);
+                    tagAdded = true;
+               }
+              }
+          }
+        }
+
+
+        if(!$scope.$parent.typeFounded && !tagAdded){
+          var queryAux = query.toUpperCase();
+          if(queryAux === "F" || queryAux === "FE" || queryAux === "FEE" || queryAux === "FEED"){
+            tags.push({ text: 'Type:feed'});
+          }
+          if(queryAux === "P" || queryAux === "PR" || queryAux === "PRO" || queryAux === "PROC" || queryAux === "PROCE"
+              || queryAux === "PROCES" || queryAux === "PROCESS"){
+            tags.push({ text: 'Type:process'});
+          }
+          if(queryAux === "M" || queryAux === "MI" || queryAux === "MIR" || queryAux === "MIRR" || queryAux === "MIRRO"
+              || queryAux === "MIRROR"){
+            tags.push({ text: 'Type:mirror'});
+          }
+        }
+        if(query !== "*"){
+          tags.push({ text: 'Tag:' + query });
+        }
+        return tags;
+      };
+
+      $scope.clusterInterfaceLabels = function(interfaceType) {
+        switch (interfaceType) {
+          case "readonly":
+            return "File System Read Endpoint Address";
+          case "write":
+            return "File System Default Address";
+          case "execute":
+            return "Yarn Resource Manager Address";
+          case "workflow":
+            return "Workflow Address";
+          case "messaging":
+            return "Message Broker Address";
+          case "registry":
+            return "Metadata Catalog Registry";
+          case "spark":
+            return "Spark";
+          default:
+            return "";
+        }
+      };
+
+      $scope.displayEntities = function (type) {
+        $state.go("main", { 'fromAction' : 'listEntities'});
+        $scope.entityType = type;
+        $scope.entityName = '';
+        $scope.entityTags = '';
+        $scope.goPage(1, 'list');
+      };
+
+      $scope.feedPropertiesLabels = {
+        queueName: 'Queue Name',
+        jobPriority: 'Job Priority',
+        timeout: 'Timeout',
+        parallel: 'Parallel',
+        maxMaps: 'Max Maps',
+        mapBandwidthKB: 'Map Bandwidth KB'
+      };
+
     }]);
 
 }());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js b/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js
new file mode 100644
index 0000000..3f047ce
--- /dev/null
+++ b/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js
@@ -0,0 +1,262 @@
+/**
+ * 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.
+ */
+(function () {
+  'use strict';
+
+  var snapshotModule = angular.module('app.controllers.snapshot', [ 'app.services' ]);
+
+  snapshotModule.controller('SnapshotController', [
+    "$scope", "$interval", "$controller", "Falcon", "EntityModel", "$state", "X2jsService", "DateHelper",
+    "RouteHelper", "ValidationService", "SpinnersFlag", "$timeout", "$rootScope", "clustersList",
+    "$cookieStore", "SnapshotModel", "EntityFactory", "ExtensionSerializer",
+    function ($scope, $interval, $controller, Falcon, EntityModel, $state, X2jsService, DateHelper,
+       RouteHelper, validationService, SpinnersFlag, $timeout, $rootScope, clustersList,
+       $cookieStore, snapshotModel, entityFactory, extensionSerializer) {
+
+      var stateMatrix = {
+        general : {previous : '', next : 'summary'},
+        summary : {previous : 'general', next : ''}
+      };
+
+      $scope.entityType = 'snapshot';
+      $scope.skipUndo = false;
+      $scope.clusterErrorMessage = '';
+      unwrapClusters(clustersList);
+
+      //extending root controller
+      $controller('EntityRootCtrl', {
+        $scope: $scope
+      });
+
+      $scope.$on('$destroy', function() {
+        var defaultProcess = entityFactory.newEntity('snapshot'),
+          nameIsEqual = ($scope.snapshot.name == null || $scope.snapshot.name === ""),
+          ACLIsEqual = angular.equals($scope.snapshot.ACL, defaultProcess.ACL);
+
+        if (!$scope.skipUndo && (!nameIsEqual || !ACLIsEqual)) {
+          $scope.$parent.models.snapshotModel = $scope.snapshot;
+          if ($scope.cloningMode) {
+            $scope.$parent.models.snapshotModel.clone = true;
+          }
+          if ($scope.editingMode) {
+            $scope.$parent.models.snapshotModel.edit = true;
+          }
+          $scope.$parent.cancel('snapshot', $rootScope.previousState);
+        }
+      });
+
+       $scope.isActive = function (route) {
+        return route === $state.current.name;
+      };
+
+      $scope.isCompleted = function (route) {
+        return $state.get(route).data && $state.get(route).data.completed;
+      };
+
+      $scope.loadOrCreateEntity = function() {
+        var type = $scope.entityType;
+
+        if(!snapshotModel && $scope.$parent.models.snapshotModel) {
+          var snapshotObj = $scope.$parent.models.snapshotModel;
+          $scope.$parent.models.snapshotModel = null;
+          return snapshotObj;
+        }
+        $scope.$parent.models.snapshotModel = null;
+        return snapshotModel ? extensionSerializer.serializeExtensionModel(snapshotModel, 'snapshot')
+          : entityFactory.newEntity(type);
+      };
+
+      $scope.init = function() {
+        $scope.baseInit();
+        var type = $scope.entityType;
+        $scope[type] = $scope.loadOrCreateEntity();
+        if(snapshotModel && snapshotModel.clone === true) {
+          $scope.cloningMode = true;
+          $scope.editingMode = false;
+          $scope[type].name = "";
+        } else if(snapshotModel && snapshotModel.edit === true) {
+          $scope.editingMode = true;
+          $scope.cloningMode = false;
+        } else{
+          $scope.editingMode = false;
+          $scope.cloningMode = false;
+        }
+        console.log($scope.editingMode);
+      }
+
+      $scope.init();
+
+      //----------------TAGS---------------------//
+      $scope.addTag = function () {
+        $scope.snapshot.tags.push({key: null, value: null});
+      };
+
+      $scope.removeTag = function (index) {
+        if (index >= 0 && $scope.snapshot.tags.length > 1) {
+          $scope.snapshot.tags.splice(index, 1);
+        }
+      };
+
+      //----------- Alerts -----------//
+      $scope.addAlert = function () {
+        $scope.snapshot.alerts.push($scope.snapshot.alert.email);
+        $scope.snapshot.alert = {email: ""};
+      };
+      $scope.removeAlert = function (index) {
+        $scope.snapshot.alerts.splice(index, 1);
+      };
+
+      //----------------- DATE INPUTS -------------------//
+      $scope.dateFormat = 'MM/dd/yyyy';
+
+      $scope.openStartDatePicker = function ($event) {
+        $event.preventDefault();
+        $event.stopPropagation();
+        $scope.startOpened = true;
+      };
+
+      $scope.openEndDatePicker = function ($event) {
+        $event.preventDefault();
+        $event.stopPropagation();
+        $scope.endOpened = true;
+      };
+
+      $scope.constructDate = function () {
+        if ($scope.snapshot.validity.start && $scope.snapshot.validity.end
+          && $scope.snapshot.validity.startTime && $scope.snapshot.validity.endTime) {
+          $scope.snapshot.validity.startISO = DateHelper.createISO(
+            $scope.snapshot.validity.start, $scope.snapshot.validity.startTime, $scope.snapshot.validity.timezone);
+          $scope.snapshot.validity.endISO = DateHelper.createISO(
+            $scope.snapshot.validity.end, $scope.snapshot.validity.endTime, $scope.snapshot.validity.timezone);
+        }
+      };
+
+      $scope.$watch(function () {
+        return $scope.snapshot.validity.timezone;
+      }, function () {
+        return $scope.constructDate();
+      });
+
+      $scope.validateCluster = function() {
+        if ($scope.snapshot.source.cluster === $scope.snapshot.target.cluster) {
+          $scope.clusterErrorMessage = 'Target cannot be the same as the Source';
+        } else {
+          $scope.clusterErrorMessage = '';
+        }
+      };
+
+      $scope.goNext = function (formInvalid) {
+        $state.current.data = $state.current.data || {};
+        $state.current.data.completed = !formInvalid;
+
+        SpinnersFlag.show = true;
+        if (!validationService.nameAvailable || formInvalid) {
+          validationService.displayValidations.show = true;
+          validationService.displayValidations.nameShow = true;
+          SpinnersFlag.show = false;
+          return;
+        }
+        if ($scope.clusterErrorMessage !== '') {
+          SpinnersFlag.show = false;
+          return;
+        }
+        validationService.displayValidations.show = false;
+        validationService.displayValidations.nameShow = false;
+        $state.go(RouteHelper.getNextState($state.current.name, stateMatrix));
+        angular.element('body, html').animate({scrollTop: 0}, 500);
+      };
+
+      $scope.goBack = function () {
+        SpinnersFlag.backShow = true;
+        validationService.displayValidations.show = false;
+        validationService.displayValidations.nameShow = false;
+        $state.go(RouteHelper.getPreviousState($state.current.name, stateMatrix));
+        angular.element('body, html').animate({scrollTop: 0}, 500);
+      };
+
+      $scope.save = function (formInvalid) {
+        SpinnersFlag.saveShow = true;
+
+        $state.current.data = $state.current.data || {};
+        $state.current.data.completed = !formInvalid;
+        if (!validationService.nameAvailable || formInvalid) {
+          validationService.displayValidations.show = true;
+          validationService.displayValidations.nameShow = true;
+          SpinnersFlag.saveShow = false;
+          return;
+        }
+        if ($scope.clusterErrorMessage !== '') {
+          SpinnersFlag.saveShow = false;
+          return;
+        }
+        validationService.displayValidations.show = false;
+        validationService.displayValidations.nameShow = false;
+
+        var snapshotData = extensionSerializer.convertObjectToString(
+          extensionSerializer.serializeExtensionProperties($scope.snapshot, 'snapshot'));
+
+        if($scope.editingMode) {
+          Falcon.postUpdateExtension(snapshotData, 'HDFS-SNAPSHOT-MIRRORING')
+            .success(function (response) {
+              $scope.skipUndo = true;
+              Falcon.logResponse('success', response, false);
+              SpinnersFlag.saveShow = false;
+              $state.go('main');
+
+            })
+            .error(function (err) {
+              Falcon.logResponse('error', err, false);
+              SpinnersFlag.saveShow = false;
+              angular.element('body, html').animate({scrollTop: 0}, 300);
+            });
+        } else {
+          Falcon.postSubmitExtension(snapshotData, 'HDFS-SNAPSHOT-MIRRORING')
+            .success(function (response) {
+              $scope.skipUndo = true;
+              Falcon.logResponse('success', response, false);
+              SpinnersFlag.saveShow = false;
+              $state.go('main');
+            })
+            .error(function (err) {
+              Falcon.logResponse('error', err, false);
+              SpinnersFlag.saveShow = false;
+              angular.element('body, html').animate({scrollTop: 0}, 300);
+            });
+        }
+      };
+
+      function unwrapClusters(clusters) {
+	if(clusters !== undefined && clusters !== null && clusters !== "null"){
+		$scope.clusterList = [];
+          var typeOfData = Object.prototype.toString.call(clusters.entity);
+          if(typeOfData === "[object Array]") {
+            $scope.clusterList = clusters.entity;
+          } else if(typeOfData === "[object Object]") {
+            $scope.clusterList = [clusters.entity];
+          } else {
+            console.log("type of data not recognized");
+          }
+	}
+      }
+
+      if($state.current.name !== "forms.snapshot.general"){
+        $state.go("forms.snapshot.general");
+      }
+
+    }]);
+}());

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/controllers/themeController.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/controllers/themeController.js b/falcon-ui/app/js/controllers/themeController.js
new file mode 100644
index 0000000..513b19e
--- /dev/null
+++ b/falcon-ui/app/js/controllers/themeController.js
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+(function() {
+    'use strict';
+
+    var app = angular.module('app.controllers.theme', []);
+
+    app.controller('ThemeController', ["$scope", function($scope) {
+
+        $scope.theme = 'default';
+        $scope.themeChange = function(theme) {
+            $scope.theme = theme;
+        };
+    }]);
+
+}());

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/acl-permissions.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/acl-permissions.js b/falcon-ui/app/js/directives/acl-permissions.js
new file mode 100644
index 0000000..98ef508
--- /dev/null
+++ b/falcon-ui/app/js/directives/acl-permissions.js
@@ -0,0 +1,103 @@
+/**
+ * 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.
+ */
+(function () {
+  'use strict';
+
+  var aclPermissionsModule = angular.module('app.directives.acl-permissions',[]);
+
+  aclPermissionsModule.directive('aclPermissions', ['$timeout',function ($timeout) {
+      return {
+          restrict : 'EA',
+          templateUrl: 'html/directives/aclPermissions.html',
+          link: function ($scope, element, attrs) {
+
+            var permission = {
+              'owner' : 0,
+              'groups' : 0,
+              'others' : 0
+            }
+
+            function getNestedProperty(obj, path) {
+              var paths = path.split('.'),
+              current = obj;
+              for (var i = 0; i < paths.length; i++) {
+                if (current[paths[i]] == undefined) {
+                  return undefined;
+                } else {
+                  current = current[paths[i]];
+                }
+              }
+              return current;
+            };
+
+            function setNestedProperty(obj, path, value) {
+              var paths = path.split('.'),
+              current = obj;
+              for (var i = 0; i < paths.length -1; i++) {
+                  current = current[paths[i]];
+              }
+              current[paths[paths.length - 1]] = value;
+            };
+
+            var defaultPermission = getNestedProperty($scope, attrs.aclModel);
+
+            function setPermission() {
+              if(defaultPermission){
+                var permissionArray = defaultPermission.substring(2).split("");
+                permissionArray.forEach(function(value, index){
+                  var permissionValue = parseInt(value);
+                  var type = Object.keys(permission)[index];
+                  permission[type] = value;
+                  var checked = element.find('input[name=' + type + ']');
+                  var readPermission = 4,
+                      writePermission = 2,
+                      executePermission = 1;
+                  if(permissionValue & readPermission){
+                    angular.element(checked[0]).attr('checked','checked');
+                  }
+                  if(permissionValue & writePermission){
+                    angular.element(checked[1]).attr('checked','checked');
+                  }
+                  if(permissionValue & executePermission){
+                    angular.element(checked[2]).attr('checked','checked');
+                  }
+                });
+              }
+            };
+
+            $scope.calculatePermission = function(type){
+              var checked = element.find('input[name=' + type + ']:checked');
+              var total = 0;
+              angular.forEach(checked,function(element){
+                total += parseInt(element.value);
+              });
+              permission[type] = total;
+              var effectivePermission = '0x';
+              angular.forEach(permission, function(value,key){
+                effectivePermission = effectivePermission + value;
+              });
+              setNestedProperty($scope, attrs.aclModel, effectivePermission);
+            };
+
+            $timeout(function () {
+              setPermission();
+            }, 0);
+          }
+      };
+  }]);
+})();

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/chart.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/chart.js b/falcon-ui/app/js/directives/chart.js
index f9d6983..f630a9f 100644
--- a/falcon-ui/app/js/directives/chart.js
+++ b/falcon-ui/app/js/directives/chart.js
@@ -453,7 +453,7 @@
   });
 
 
-  d3Module.controller('chartCtrl', [ "$scope", "Falcon", function($scope, Falcon) {
+  d3Module.controller('chartCtrl', [ "$scope", "Falcon", "DateHelper", function($scope, Falcon, DateHelper) {
 
     var formatFL = d3.time.format.utc("%A %d"),
         formatSL = d3.time.format.utc("%b %Y"),
@@ -500,7 +500,7 @@
 
     };
 
-    $scope.dateFormat ='MM/dd/yyyy';
+    $scope.dateFormat = DateHelper.getLocaleDateFormat();
     $scope.openDatePicker = function($event) {
       $event.preventDefault();
       $event.stopPropagation();

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/check-name.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/check-name.js b/falcon-ui/app/js/directives/check-name.js
index bea0e22..edb3697 100644
--- a/falcon-ui/app/js/directives/check-name.js
+++ b/falcon-ui/app/js/directives/check-name.js
@@ -20,24 +20,28 @@
 
   var checkNameModule = angular.module('app.directives.check-name', ['app.services.falcon', 'app.services.validation']);
 
-  checkNameModule.directive('checkName', [ "ValidationService", "$timeout", "Falcon", "EntityFalcon", function (validationService, $timeout, Falcon, EntityFalcon) {
+  checkNameModule.directive('checkName', [ "ValidationService", "$timeout", "Falcon", "EntityFalcon","$q", function (validationService, $timeout, Falcon, EntityFalcon,$q) {
     return {
-      replace: false,
       scope: {
         checkName: "="
       },
-      restrict: 'A',
-      link: function (scope, element) {
-
-        var options = scope.checkName,
-          //entities = scope.$parent.lists[options.type + 'List'],
-          type = options.type,
-          name = element[0].value;
-
+    require: ['ngModel','^form'],
+    link: function(scope, element, attrs, ctrls) {
+      var options = scope.checkName,
+        //entities = scope.$parent.lists[options.type + 'List'],
+        type = options.type,
+        name = element[0].value,
+        errorMsg,
+        ngModelCtrl = ctrls[0],
+        formCtrl = ctrls[1];
         if (!options.check) {
           return;
         }
 
+        element.parent()
+          .append("<div class='nameInputDisplay hidden'>" +
+              "</div><label class='custom-danger nameValidationMessage'></label>");
+
         scope.$watch(function () {
           return element[0].value;
         }, function () {
@@ -45,121 +49,81 @@
             if (element[0].value.length === 0) {
               element.addClass('empty');
             }
-            getNameAvailability(function() {
-              getMessage();
-              if (element.hasClass('ng-valid') && validationService.nameAvailable) {
-                angular.element('.nameValidationMessage').addClass('hidden');
-              } else {
-                element.parent().addClass("showValidationStyle");
-                angular.element('.nameValidationMessage').removeClass('hidden');
-                element.removeClass('empty');
-              }
-            });
           }
-
-
         });
 
-        function getLabels() {
-          element.parent()
-            .append("<div class='nameInputDisplay hidden'>" +
-                "</div><label class='custom-danger nameValidationMessage'></label>");
-        }
-
-        function getNameAvailability(fn) {
-          name = element[0].value;
-          if (name.length === 0) {
-            angular.element('.nameInputDisplay').addClass('hidden');
-          }else{
-            Falcon.logRequest();
-            Falcon.getEntityDefinition(type, name).success(function (data) {
-              Falcon.logResponse('success', data, false, true);
-              validationService.nameAvailable = false;
-              if (name.length === 0) {
-                angular.element('.nameInputDisplay').addClass('hidden');
-              } else if (!validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) {
-                angular.element('.nameInputDisplay').html('Name unavailable')
-                    .removeClass('custom-success hidden').addClass('custom-danger');
-              } else if (validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) {
-                angular.element('.nameInputDisplay').html('Name available')
-                    .removeClass('custom-danger hidden').addClass('custom-success');
-              } else if (element.hasClass('ng-invalid-pattern') && name.length > 0) {
-                angular.element('.nameInputDisplay').addClass('hidden');
-              }
-              if (fn) { fn(); } //>callback
-            }).error(function (err) {
-              Falcon.logResponse('error', err, false, true);
-              validationService.nameAvailable = true;
-              if (name.length === 0) {
-                angular.element('.nameInputDisplay').addClass('hidden');
-              } else if (!validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) {
-                angular.element('.nameInputDisplay').html('Name unavailable')
-                    .removeClass('custom-success hidden').addClass('custom-danger');
-              } else if (validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) {
-                angular.element('.nameInputDisplay').html('Name available')
-                    .removeClass('custom-danger hidden').addClass('custom-success');
-              } else if (element.hasClass('ng-invalid-pattern') && name.length > 0) {
-                angular.element('.nameInputDisplay').addClass('hidden');
+        scope.$watch(function(){
+          return ngModelCtrl.$valid;
+        }, function(newValue, oldValue){
+          if(ngModelCtrl.$dirty && newValue){
+            errorMsg = "Name available";
+            validationService.nameAvailable = true;
+          }else {
+              if(ngModelCtrl.$error.uniqueName){
+                  errorMsg = validationService.messages.name.unavailable;
+                  validationService.nameAvailable = false;
+              }else if(ngModelCtrl.$error.required){
+                  errorMsg = validationService.messages.name.empty;
+              }else if(ngModelCtrl.$error.pattern){
+                  errorMsg = validationService.messages.name.patternInvalid;
               }
-              if (fn) { fn(); } //>callback
-            });
           }
+        });
 
-        }
-
-        function getMessage() {
-          if (name.length === 0) {
-            element.addClass('empty');
-            angular.element('.nameValidationMessage').html(validationService.messages.name.empty).addClass('hidden');
-
-          } else if (!validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) {
-            element.addClass('empty');
-            angular.element('.nameValidationMessage')
-              .html(validationService.messages.name.unavailable).addClass('hidden');
-
-          } else if (element.hasClass('ng-invalid-pattern') && name.length > 0) {
-            element.removeClass('empty');
+        scope.$watch(function(){
+          return errorMsg;
+        },function(newValue, oldValue){
+          if(newValue && newValue.length > 0){
             element.parent().addClass("showValidationStyle");
-            angular.element('.nameValidationMessage')
-              .html(validationService.messages.name.patternInvalid).removeClass('hidden');
-
-          } else if (element.hasClass('ng-valid') && name.length > 0) {
+            element.removeClass('empty');
+            if(errorMsg ==='Name available'){
+              angular.element('.nameInputDisplay').removeClass('custom-danger hidden').addClass('custom-success');
+              angular.element('.nameInputDisplay').text(newValue);
+              angular.element('.nameValidationMessage').addClass("hidden");
+            }else{
+              angular.element('.nameValidationMessage').text(newValue);
+              angular.element('.nameValidationMessage').removeClass('hidden').addClass('custom custom-danger');
+              angular.element('.nameInputDisplay').addClass("hidden");
+            }
+          }else{
             element.parent().removeClass("showValidationStyle");
-            angular.element('.nameValidationMessage').addClass('hidden');
           }
-        }
-        function addListeners() {
-          element.bind('keyup', function () {
-            getNameAvailability();
-            getMessage();
-          });
-          element.bind('focus', function () {
-            element.removeClass('empty');
-          });
-          element.bind('blur', function () {
-            if (element.hasClass('ng-valid') && validationService.nameAvailable) {
-              angular.element('.nameValidationMessage').addClass('hidden');
+        });
 
-            } else {
-              element.parent().addClass("showValidationStyle");
-              angular.element('.nameValidationMessage').removeClass('hidden');
-              element.removeClass('empty');
-            }
+        ngModelCtrl.$asyncValidators.uniqueName = function isNameAvailable(modelValue,viewValue) {
+          Falcon.logRequest();
+          var def = $q.defer();
+          Falcon.getEntityDefinition(type, modelValue).success(function (data) {
+            Falcon.logResponse('success', data, false, true);
+            def.reject();
+          }).error(function (err) {
+            Falcon.logResponse('error', err, false, true);
+            def.resolve();
           });
+          return def.promise;
         }
 
-        function init() {
-          getLabels();
-          addListeners();
-          getNameAvailability();
-          getMessage();
+        element.bind('blur',function(event){
+            if(ngModelCtrl.$valid){
+              angular.element('.nameValidationMessage').addClass('hidden');
+            }else{
+              angular.element('.nameValidationMessage').removeClass('hidden');
+            }
+        });
 
-          $timeout(function () { element.trigger('focus'); }, 20);
-        }
+        element.bind('focus',function(event){
+            if(formCtrl.$submitted && ngModelCtrl.$pristine && ngModelCtrl.$error.required){
+              angular.element('.nameValidationMessage').removeClass('hidden');
+              element.parent().addClass('showValidationStyle');
+            }else{
+              angular.element('.nameValidationMessage').addClass('hidden');
+              element.parent().removeClass('showValidationStyle');
+            }
+        });
 
-        init();
+        $timeout(function () { element.trigger('focus'); }, 0);
       }
-    };
+    }
   }]);
 
-}());
\ No newline at end of file
+}());

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/dependencies-graph.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/dependencies-graph.js b/falcon-ui/app/js/directives/dependencies-graph.js
index c8faa74..bd37358 100644
--- a/falcon-ui/app/js/directives/dependencies-graph.js
+++ b/falcon-ui/app/js/directives/dependencies-graph.js
@@ -18,17 +18,16 @@
 (function () {
   'use strict';
 
-	var entitiesListModule = angular.module('app.directives.dependencies-graph', ['app.services' ]);
+  var entitiesListModule = angular.module('app.directives.dependencies-graph', ['app.services' ]);
 
-  entitiesListModule.controller('DependenciesGraphCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService', 'EntityModel',
-                                      function($scope, Falcon, X2jsService, $window, encodeService, EntityModel) {
+  entitiesListModule.controller('DependenciesGraphCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
+    function($scope, Falcon, X2jsService, $window, encodeService) {
 
 
 
-  }]);
+    }]);
 
-  entitiesListModule.directive('dependenciesGraph', ["$timeout", 'Falcon', '$filter', '$state', 'X2jsService', 'EntityModel',
-                                              function($timeout, Falcon, $filter, $state, X2jsService, EntityModel) {
+  entitiesListModule.directive('dependenciesGraph', ["$timeout", 'Falcon', '$filter', function($timeout, Falcon, $filter) {
     return {
       scope: {
         type: "=",
@@ -49,16 +48,17 @@
             return type + '/' + name;
           }
 
-          function getOrCreateNode(type, name) {
+          function getOrCreateNode(type, name, tag) {
             var k = key(type, name);
             if (nodes[k] !== undefined)
               return nodes[k];
 
             var n = {
-              "id": next_node_id++,
+              "guid": next_node_id++,
               "type": type,
               "name": name,
-              "dependency": []
+              "arrowDirections": tag,
+              "children": []
             };
             nodes[k] = n;
             return n;
@@ -81,7 +81,8 @@
                   var l = data.entity.length;
                   for (var i = 0; i < l; ++i) {
                     var e = data.entity[i];
-                    var d = getOrCreateNode(e.type, e.name);
+                    var tag = e.hasOwnProperty('tags') && e.tags !== null ? e.tags.tag : "Input";
+                    var d = getOrCreateNode(e.type, e.name, tag);
                     var src = null, dst = null;
                     if (d.type === "cluster") {
                       src = node; dst = d;
@@ -94,11 +95,10 @@
                         src = node; dst = d;
                       }
                     }
-                    //console.log(src.name + '->' + dst.name);
-                    src.dependency.push(dst.id);
+                    src.children.push(d);
                   }
 
-                  done_callback(nodes);
+                  done_callback(node);
                 })
                 .error(function (err) {
                   Falcon.logResponse('error', err, false, true);
@@ -106,181 +106,141 @@
           }
 
           function load() {
-            var n = getOrCreateNode(entity_type, entity_name);
+            var n = getOrCreateNode(entity_type, entity_name, "Input");
             loadEntry(n);
           }
           load();
         };
 
-        var plotDependencyGraph = function(nodes, element) {
-          var NODE_WIDTH  = 150;
-          var NODE_HEIGHT = 50;
-          var RECT_ROUND  = 10;
-          var SEPARATION  = 40;
-          var UNIVERSAL_SEP = 80;
-
-          var svg = d3.select(element).append("svg");
-
+        var plotDependencyGraph = function(nodes, container) {
+          var element = d3.select(container.element),
+              width = Math.max(container.width, 960),
+              height = Math.max(container.height, 300);
+
+          var margin = {
+            top: 30,
+            right: 30,
+            bottom: 30,
+            left: 80
+          };
+          width = width - margin.right - margin.left;
+          height = height - margin.top - margin.bottom;
           // Function to draw the lines of the edge
-          var LINE_FUNCTION = d3.svg.line()
-              .x(function(d) { return d.x; })
-              .y(function(d) { return d.y; })
-              .interpolate('basis');
-
-          // Mappining from id to a node
-          var node_by_id = {};
-
-          var layout = null;
-
-          /**
-           * Calculate the intersection point between the point p and the edges of the rectangle rect
-           **/
-          function intersectRect(rect, p) {
-            var cx = rect.x, cy = rect.y, dx = p.x - cx, dy = p.y - cy, w = rect.width / 2, h = rect.height / 2;
-
-            if (dx == 0)
-              return { "x": p.x, "y": rect.y + (dy > 0 ? h : -h) };
-
-            var slope = dy / dx;
-
-            var x0 = null, y0 = null;
-            if (Math.abs(slope) < rect.height / rect.width) {
-              // intersect with the left or right edges of the rect
-              x0 = rect.x + (dx > 0 ? w : -w);
-              y0 = cy + slope * (x0 - cx);
-            } else {
-              y0 = rect.y + (dy > 0 ? h : -h);
-              x0 = cx + (y0 - cy) / slope;
-            }
-
-            return { "x": x0, "y": y0 };
-          }
-
-          function drawNode(u, value) {
-            var root = svg.append('g').classed('node', true)
-                .attr('transform', 'translate(' + -value.width/2 + ',' + -value.height/2 + ')');
-
-            var node = node_by_id[u];
-
-
+          var i = 0;
 
 
-            var fo = root.append('foreignObject')
-                .attr('x', value.x)
-                .attr('y', value.y)
-                .attr('width', value.width)
-                .attr('height', value.height)
-                .attr('class', 'foreignObject');
-
-
-            var txt = fo.append('xhtml:div')
-                .text(node.name)
-                .classed('node-name', true)
-                .classed('node-name-' + node.type, true);
-
-            var rect = root.append('rect')
-              .attr('width', value.width)
-              .attr('height', value.height)
-              .attr('x', value.x)
-              .attr('y', value.y)
-              .attr('rx', RECT_ROUND)
-              .attr('ry', RECT_ROUND)
-
-              .on('click', function () {
-
-                Falcon.logRequest();
-                Falcon.getEntityDefinition(node.type.toLowerCase(), node.name)
-                  .success(function (data) {
-                    Falcon.logResponse('success', data, false, true);
-                    var entityModel = X2jsService.xml_str2json(data);
-                    EntityModel.type = node.type.toLowerCase();
-                    EntityModel.name = node.name;
-                    EntityModel.model = entityModel;
-                    $state.go('entityDetails');
-                  })
-                  .error(function (err) {
-                    Falcon.logResponse('error', err, false, false);
-                  });
-
+          var tree = d3.layout.tree()
+              .size([height, width]);
 
+          var diagonal = d3.svg.diagonal()
+              .projection(function(d) {
+                return [d.y, d.x];
               });
 
-          }
-
-          function drawEdge(e, u, v, value) {
-            var root = svg.append('g').classed('edge', true);
-
-            root.append('path')
-                .attr('marker-end', 'url(#arrowhead)')
-                .attr('d', function() {
-                  var points = value.points;
-
-                  var source = layout.node(u);
-                  var target = layout.node(v);
-
-                  var p0 = points.length === 0 ? target : points[0];
-                  var p1 = points.length === 0 ? source : points[points.length - 1];
-
-                  points.unshift(intersectRect(source, p0));
-                  points.push(intersectRect(target, p1));
-
-                  return LINE_FUNCTION(points);
+          var svg = element.select('svg')
+              .attr('width', width + margin.right + margin.left)
+              .attr('height', height + margin.top + margin.bottom)
+              .select('g')
+              .attr('transform',
+              'translate(' + margin.left + ',' + margin.right + ')');
+
+          svg.append("svg:defs").append("svg:marker").attr("id", "output-arrow").attr("viewBox", "0 0 10 10")
+              .attr("refX", 20).attr("refY", 5).attr("markerUnits", "strokeWidth").attr("markerWidth", 6)
+              .attr("markerHeight", 9).attr("orient", "auto").append("svg:path").attr("d", "M 0 0 L 10 5 L 0 10 z");
+
+          //marker for input type graph
+          svg.append("svg:defs")
+              .append("svg:marker")
+              .attr("id", "input-arrow")
+              .attr("viewBox", "0 0 10 10")
+              .attr("refX", -7)
+              .attr("refY", 5)
+              .attr("markerUnits", "strokeWidth")
+              .attr("markerWidth", 6)
+              .attr("markerHeight", 9)
+              .attr("orient", "auto")
+              .append("svg:path")
+              .attr("d", "M -2 5 L 8 0 L 8 10 z");
+
+          var root = nodes;
+          function update(source) {
+
+            // Compute the new tree layout.
+            var nodes1 = tree.nodes(source).reverse(),
+                links = tree.links(nodes1);
+            // Normalize for fixed-depth.
+            nodes1.forEach(function(d) {
+              d.y = d.depth * 180;
+            });
+
+            // Declare the nodes\ufffd
+            var node = svg.selectAll('g.node')
+                .data(nodes1, function(d) {
+
+                  return d.id || (d.id = ++i);
+                });
+            // Enter the nodes.
+            var nodeEnter = node.enter().append('g')
+                .attr('class', 'node')
+                .attr('transform', function(d) {
+                  return 'translate(' + d.y + ',' + d.x + ')';
                 });
-          }
 
-          function postRender() {
-            svg
-                .append('svg:defs')
-                .append('svg:marker')
-                .attr('id', 'arrowhead')
-                .attr('viewBox', '0 0 10 10')
-                .attr('refX', 8)
-                .attr('refY', 5)
-                .attr('markerUnits', 'strokewidth')
-                .attr('markerWidth', 8)
-                .attr('markerHeight', 5)
-                .attr('orient', 'auto')
-                .attr('style', 'fill: #333')
-                .append('svg:path')
-                .attr('d', 'M 0 0 L 10 5 L 0 10 z');
-          }
+            nodeEnter.append("image")
+                .attr("xlink:href", function(d) {
+                  //return d.icon;
+                  return d.type === 'cluster' ? 'css/img/cloud.png' : 'css/img/feed.png';
+                })
+                .attr("x", "-18px")
+                .attr("y", "-18px")
+                .attr("width", "34px")
+                .attr("height", "34px");
+
+            nodeEnter.append('text')
+                .attr('x', function(d) {
+                  return d.children || d._children ?
+                  (5) * -1 : +15;
+                })
+                .attr('dy', '-1.75em')
+                .attr('text-anchor', function(d) {
+                  return d.children || d._children ? 'middle' : 'middle';
+                })
+                .text(function(d) {
+                  return d.name;
+                })
 
-          function plot() {
-            var g = new dagre.Digraph();
+                .style('fill-opacity', 1);
 
-            for (var key in nodes) {
-              var n = nodes[key];
-              node_by_id[n.id] = n;
-              g.addNode(n.id, { "width": NODE_WIDTH, "height": NODE_HEIGHT });
-            }
+            // Declare the links\ufffd
+            var link = svg.selectAll('path.link')
+                .data(links, function(d) {
+                  return d.target.id;
+                });
 
-            for (var key in nodes) {
-              var n = nodes[key];
-              for (var i = 0, l = n.dependency.length; i < l; ++i) {
-                var d = n.dependency[i];
-                g.addEdge(null, n.id, d);
-              }
-            }
+            link.enter().insert('path', 'g')
+                .attr('class', 'link')
+              //.style('stroke', function(d) { return d.target.level; })
+                .style('stroke', 'green')
+                .attr('d', diagonal);
+            link.attr("marker-start", function (d) {
+              if(d.target.arrowDirections==="Input")
+                return "url(#input-arrow)";
+            }); //if input
+            link.attr("marker-end", function (d) {
+              if(d.target.arrowDirections==="Output")
+                return "url(#output-arrow)";
+            }); //if outPut
 
-            layout = dagre.layout()
-                .universalSep(UNIVERSAL_SEP).rankSep(SEPARATION)
-                .run(g);
-            layout.eachEdge(drawEdge);
-            layout.eachNode(drawNode);
+          }
 
-            var graph = layout.graph();
+          update(root);
 
-            svg.attr("width", graph.width);
-            svg.attr("height", graph.height);
 
-            postRender();
-          }
-          plot();
         };
 
         var visualizeDependencyGraph = function(type, name) {
           loadDependencyGraph(type, name, function(nodes) {
-            plotDependencyGraph(nodes, element[0]);
+            plotDependencyGraph(nodes, {element:element[0], height:element[0].offsetHeight,width:element[0].offsetWidth});
           });
         };
 

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/directives.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/directives.js b/falcon-ui/app/js/directives/directives.js
index 9c388ea..e1366b6 100644
--- a/falcon-ui/app/js/directives/directives.js
+++ b/falcon-ui/app/js/directives/directives.js
@@ -29,9 +29,21 @@
     'app.directives.validation-message',
     'chart-module',
     'app.directives.dependencies-graph',
-    'app.directives.lineage-graph'
+    'app.directives.lineage-graph',
+    'tooltip',
+    'app.directives.feed-cluster-partitions',
+    'app.directives.acl-permissions',
+    'app.directives.interface-endpoint'
   ]);
 
+  directivesModule.directive('errorNav', function () {
+    return {
+      replace: false,
+      restrict: 'A',
+      templateUrl: 'html/error.html'
+    };
+  });
+
   directivesModule.directive('navHeader', function () {
     return {
       replace: false,
@@ -74,7 +86,7 @@
       template: '{{output}}',
       link: function (scope) {
         if (scope.value.quantity) {
-          scope.output = scope.prefix + ' ' + scope.value.quantity + ' ' + scope.value.unit;
+          scope.output = (scope.prefix ? scope.prefix + ' ' : '') + scope.value.quantity + ' ' + scope.value.unit;
         } else {
           scope.output = 'Not specified';
         }
@@ -87,16 +99,21 @@
       restrict: 'E',
       replace: false,
       scope: {
-        ngModel: '='
+        ngModel: '=',
+        required: '='
       },
       templateUrl: 'html/directives/timeZoneSelectDv.html'
     };
   });
 
-  directivesModule.directive('simpleDate', ['$filter', function ($filter) {
+  directivesModule.directive('simpleDate', ['$filter','DateHelper', function ($filter, DateHelper) {
     return {
       require: 'ngModel',
       link: function (scope, element, attrs, ngModelController) {
+        var dateFormat = DateHelper.getLocaleDateFormat();
+
+        element.attr('title','Date should be entered in '+ dateFormat.toLowerCase() + ' format.');
+
         ngModelController.$parsers.push(function (data) {
           //convert data from view format to model format
           return data;
@@ -104,7 +121,7 @@
         ngModelController.$formatters.push(function (date) {
           //convert data from model format to view format
           if (date !== "") {
-            date = $filter('date')(date, 'MM/dd/yyyy');
+            date = $filter('date')(date, dateFormat);
           }
           return date;
         });
@@ -135,6 +152,7 @@
           resize();
         });
         var resize = function () {
+          element[0].style.resize = "vertical";
           element[0].style.height = "250px";
           return element[0].style.height = "" + element[0].scrollHeight + "px";
         };
@@ -179,4 +197,259 @@
     };
   }]);
 
-}());
\ No newline at end of file
+  directivesModule.directive('scrollToError', ['$timeout',function ($timeout) {
+      return {
+          require : "^form",
+          restrict : 'A',
+          link: function (scope, element,attrs,form) {
+              element.on('mousedown',function(event){
+                event.preventDefault();
+              });
+              element.on('click', function () {
+                  var formElement = angular.element('form[name="' + form.$name + '"]');
+                  var firstInvalid = formElement[0].querySelector('.ng-invalid');
+                  $timeout(function() {
+                    if (firstInvalid) {
+                      firstInvalid.blur();
+                      firstInvalid.focus();
+                    }
+                  },0)
+              });
+          }
+      };
+  }]);
+
+  directivesModule.directive('feedFormClusterDetails', function () {
+    return {
+      replace: false,
+      restrict: 'EA',
+      templateUrl: 'html/feed/feedFormClusterDetailsTpl.html',
+      link: function ($scope, $element) {
+        $scope.$on('forms.feed.clusters:submit', function() {
+          $scope.cluster.isAccordionOpened = $element.find('.ng-invalid').length > 0;
+        });
+      }
+    };
+  });
+
+  directivesModule.directive('feedFormDataSource', function () {
+    return {
+      replace: false,
+      restrict: 'EA',
+      templateUrl: 'html/feed/feedFormDataSourceTpl.html',
+      link: function ($scope, $element) {
+        if($scope.feed.dataTransferType === 'import'){
+          $scope.dataSourceType = 'source';
+          $scope.dataTransferAction = 'extract';
+          if ($scope.feed.import === undefined) {
+            $scope.feed.import = { 'source' : {
+              'extract' : {'type' : 'full', 'mergepolicy' : 'snapshot'}, 'columnsType' : 'all', 'fields' : {} } };
+          } else {
+            if ($scope.feed.import.source.fields && $scope.feed.import.source.fields.exlcudes) {
+              $scope.feed.import.source.columnsType = 'exclude';
+            } else if ($scope.feed.import.source.fields && $scope.feed.import.source.fields.includes) {
+              $scope.feed.import.source.columnsType = 'include';
+            } else {
+              $scope.feed.import.source.columnsType = 'all';
+            }
+          }
+        } else {
+          $scope.dataSourceType = 'target';
+          $scope.dataTransferAction = 'load';
+          if ($scope.feed.export === undefined) {
+            $scope.feed.export = { 'target' : {
+              'load' : {'type' : 'updateonly' }, 'columnsType' : 'all', 'fields' : {} } };
+          } else {
+            if ($scope.feed.export.target.fields && $scope.feed.export.target.fields.exlcudes) {
+              $scope.feed.export.target.columnsType = 'exclude';
+            } else if ($scope.feed.export.target.fields && $scope.feed.export.target.fields.includes) {
+              $scope.feed.export.target.columnsType = 'include';
+            } else {
+              $scope.feed.export.target.columnsType = 'all';
+            }
+          }
+        }
+      }
+    };
+  });
+
+  directivesModule.directive('feedFormHiveStorage', ['EntityFactory',function (entityFactory) {
+    return {
+      replace: false,
+      scope: {
+        storageInfo:"=",
+        add:"&",
+        show:'&',
+        toggleAdvancedOptions:'&',
+        openDatePicker:'&',
+        constructDate:'&',
+        reset:'&',
+        validations:'=',
+        required:'='
+      },
+      restrict: 'EA',
+      templateUrl: 'html/directives/feedFormHiveStorage.html',
+      link: function ($scope) {
+        $scope.valiationMessage= JSON.stringify($scope.validations.messages.number);
+        if($scope.storageInfo.type ==='target'){
+          $scope.buttonText = 'Destination';
+        }else{
+          $scope.buttonText = 'Source'
+        }
+        if(!$scope.storageInfo.clusterStorage){
+          $scope.cluster = entityFactory.newCluster($scope.storageInfo.type, 'hive', "", null);
+        }else{
+          $scope.cluster = $scope.storageInfo.clusterStorage;
+        }
+        $scope.toggleAdvancedOptions = function(){
+          $scope.showingAdvancedOptions = !$scope.showingAdvancedOptions;
+        };
+        $scope.reset = function(){
+          $scope.storageInfo.feedClusters.forEach(function (cluster, index) {
+            if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) {
+              $scope.storageInfo.feedClusters[index]
+                = entityFactory.newCluster($scope.storageInfo.type, 'hive', '', null);
+            }
+          });
+        };
+        $scope.checkDuplicateClusterOnTarget = function() {
+          if ($scope.cluster.type === 'target'
+            && $scope.cluster.name !== ''
+            && $scope.storageInfo.feedClusters.filter(function (cluster) {
+            return cluster.name === $scope.cluster.name && cluster.type === 'source';
+          }).length > 0) {
+            return true;
+          }
+          return false;
+        };
+        $scope.findClusterExists = function(newClusterName, newClusterType, clusterList) {
+          $scope.clusterExists = clusterList.filter(function (cluster) {
+            return cluster.name === newClusterName && cluster.type === newClusterType;
+          }).length > 1;
+        };
+        $scope.addCluster = function(clusterDetails){
+          var cluster = entityFactory.newCluster(clusterDetails.type, clusterDetails.dataTransferType, "", null);
+          $scope.storageInfo.feedClusters.unshift(cluster);
+          //$scope.add({value : clusterDetails});
+          //$scope.reset();
+        };
+        $scope.deleteCluster = function() {
+          $scope.storageInfo.feedClusters.forEach(function (cluster, index) {
+            if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) {
+              $scope.storageInfo.feedClusters.splice(index, 1);
+            }
+          });
+        };
+      }
+    };
+  }]);
+
+  directivesModule.directive('feedFormHdfsStorage', ['EntityFactory',function (entityFactory) {
+    return {
+      replace: false,
+      scope: {
+        storageInfo:"=",
+        add:"&",
+        show:'&',
+        toggleAdvancedOptions:'&',
+        openDatePicker:'&',
+        constructDate:'&',
+        reset:'&',
+        validations:'=',
+        required:'='
+      },
+      restrict: 'EA',
+      templateUrl: 'html/directives/feedFormHdfsStorage.html',
+      require:"^form",
+      link: function ($scope, $element, $attrs, $form) {
+        $scope.valiationMessage= JSON.stringify($scope.validations.messages.number);
+        if($scope.storageInfo.type ==='target'){
+          $scope.buttonText = 'Destination';
+        }else{
+          $scope.buttonText = 'Source'
+        }
+        if(!$scope.storageInfo.clusterStorage){
+          $scope.cluster = entityFactory.newCluster($scope.storageInfo.type, 'hdfs', "", null);
+        }else{
+          $scope.cluster = $scope.storageInfo.clusterStorage;
+          if (!$scope.cluster.storage.fileSystem) {
+            $scope.cluster.storage = { 'fileSystem' : entityFactory.newClusterFileSystem() };
+          }
+        }
+        $scope.toggleAdvancedOptions = function(){
+          $scope.showingAdvancedOptions = !$scope.showingAdvancedOptions;
+        };
+        $scope.reset = function(){
+          $scope.storageInfo.feedClusters.forEach(function (cluster, index) {
+            if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) {
+              $scope.storageInfo.feedClusters[index]
+                = entityFactory.newCluster($scope.storageInfo.type, 'hdfs', '', null);
+            }
+          });
+        };
+        $scope.checkDuplicateClusterOnTarget = function() {
+          if ($scope.cluster.type === 'target'
+            && $scope.cluster.name !== ''
+            && $scope.storageInfo.feedClusters.filter(function (cluster) {
+            return cluster.name === $scope.cluster.name && cluster.type === 'source';
+          }).length > 0) {
+            return true;
+          }
+          return false;
+        };
+        $scope.findClusterExists = function(newClusterName, newClusterType, clusterList) {
+          $scope.clusterExists = clusterList.filter(function (cluster) {
+            return cluster.name === newClusterName && cluster.type === newClusterType;
+          }).length > 1;
+        };
+        $scope.addCluster = function(clusterDetails, feedForm){
+          var cluster = entityFactory.newCluster(clusterDetails.type, clusterDetails.dataTransferType, "", null);
+          $scope.storageInfo.feedClusters.unshift(cluster);
+          //$scope.add({value : clusterDetails});
+          //$scope.reset();
+        };
+        $scope.deleteCluster = function() {
+          $scope.storageInfo.feedClusters.forEach(function (cluster, index) {
+            if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) {
+              $scope.storageInfo.feedClusters.splice(index, 1);
+            }
+          });
+        };
+      }
+    };
+  }]);
+
+  directivesModule.directive('simpleDatePicker', ['$filter','DateHelper', function ($filter, DateHelper) {
+    return {
+      require: 'ngModel',
+      link: function ($scope, $element, $attrs, ngModelController) {
+        $element.datepicker();
+
+        var dateFormat = DateHelper.getLocaleDateFormat();
+
+        $element.attr('title','Date should be entered in '+ dateFormat.toLowerCase() + ' format.');
+
+        ngModelController.$parsers.push(function (date) {
+          //convert data from view format to model format
+          return new Date(date);
+        });
+        ngModelController.$formatters.push(function (date) {
+          //convert data from model format to view format
+          if (date !== "") {
+            date = $filter('date')(date, dateFormat);
+          }
+          return date;
+        });
+      }
+    };
+  }]);
+
+  directivesModule.directive('mandatoryField', function () {
+    return {
+      replace: false,
+      restrict: 'E',
+      template: '<span>*</span>'
+    };
+  });
+
+}());

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/entities-search-list.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/entities-search-list.js b/falcon-ui/app/js/directives/entities-search-list.js
index 3e76f63..9171fbd 100644
--- a/falcon-ui/app/js/directives/entities-search-list.js
+++ b/falcon-ui/app/js/directives/entities-search-list.js
@@ -20,8 +20,16 @@
 
 	var entitiesListModule = angular.module('app.directives.entities-search-list', ['app.services' ]);
 
-  entitiesListModule.controller('EntitiesSearchListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
-                                      function($scope, Falcon, X2jsService, $window, encodeService) {
+  entitiesListModule.controller('EntitiesSearchListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService', '$rootScope',
+                                      function($scope, Falcon, X2jsService, $window, encodeService, $rootScope) {
+
+    $scope.isSafeMode = function() {
+      return $rootScope.safeMode;
+    };
+
+    $scope.isSuperUser = function() {
+      return $rootScope.superUser;
+    };
 
     $scope.downloadEntity = function(type, name) {
       Falcon.logRequest();
@@ -47,7 +55,7 @@
     };
   });
 
-  entitiesListModule.directive('entitiesSearchList', ["$timeout", 'Falcon', function($timeout, Falcon) {
+  entitiesListModule.directive('entitiesSearchList', ["$timeout", 'Falcon', "$state", "$rootScope", function($timeout, Falcon, $state, $rootScope) {
     return {
       scope: {
         input: "=",
@@ -78,7 +86,7 @@
         }, true);
 
         scope.selectedRows = [];
-        scope.mirrorTag = "_falcon_mirroring_type";
+        scope.mirrorTag = "_falcon_extension_name";
 
         scope.checkedRow = function (name) {
           var isInArray = false;
@@ -159,6 +167,17 @@
               scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true };
             }
 
+            var selectedClusterRows = scope.selectedRows.filter(function(entity) {
+              return entity.type == 'cluster' || entity.type == 'CLUSTER';
+            });
+            if (selectedClusterRows && selectedClusterRows.length > 0) {
+              scope.selectedDisabledButtons = {
+                schedule:true,
+                suspend:true,
+                resume:true
+              };
+            }
+
             if(scope.selectedRows.length === 0) {
               scope.selectedDisabledButtons = {
                 schedule:true,
@@ -166,6 +185,7 @@
                 resume:true
               };
             }
+
           }, 50);
         };
 
@@ -209,16 +229,48 @@
         };
 
         scope.scopeEdit = function () {
-          scope.edit(scope.selectedRows[0].type, scope.selectedRows[0].name);
+          var selectedRow = scope.selectedRows[0];
+          if (selectedRow.type.toLowerCase() === 'cluster' && (!$rootScope.safeMode || !$rootScope.superUser)) {
+            return;
+          }
+          var state = 'forms.' + selectedRow.type.toLowerCase();
+          var selectedEntity = scope.input.filter(function(value){
+            return value.name === selectedRow.name;
+          });
+          if(selectedRow.type.toLowerCase() === 'process' && selectedEntity[0].tags
+            && scope.isMirror(selectedEntity[0].tags.tag)){
+            var mirrorType = scope.getMirrorType(selectedEntity[0].tags.tag);
+            if (mirrorType === 'hdfs-mirror' || mirrorType === 'hive-mirror') {
+              state = 'forms.dataset';
+            } else {
+              state = 'forms.snapshot';
+            }
+          }
+          $state.go(state, {'name' : selectedRow.name, 'action' : 'edit'});
         };
+
         scope.scopeClone = function () {
-          scope.clone(scope.selectedRows[0].type, scope.selectedRows[0].name);
+          var selectedRow = scope.selectedRows[0];
+          var state = 'forms.' + selectedRow.type.toLowerCase();
+          var selectedEntity = scope.input.filter(function(value){
+            return value.name === selectedRow.name;
+          });
+          if(selectedRow.type.toLowerCase() === 'process' && selectedEntity[0].tags
+            && scope.isMirror(selectedEntity[0].tags.tag)){
+            var mirrorType = scope.getMirrorType(selectedEntity[0].tags.tag);
+            if (mirrorType === 'hdfs-mirror' || mirrorType === 'hive-mirror') {
+              state = 'forms.dataset';
+            } else {
+              state = 'forms.snapshot';
+            }
+          }
+          $state.go(state, {'name' : selectedRow.name, 'action' : 'clone'});
         };
         scope.goEntityDefinition = function(name, type) {
           scope.entityDefinition(name, type);
         };
         scope.goEntityDetails = function(name, type) {
-          scope.entityDetails(name, type);
+          $state.go('entityDetails',{'name' : name, 'type' : type});
         };
 
         scope.scopeRemove = function () {
@@ -279,19 +331,31 @@
           return flag;
         };
 
+        scope.getMirrorType = function(tags) {
+          if (tags.indexOf('_falcon_extension_name=HDFS-MIRRORING') !== -1) {
+            return "hdfs-mirror";
+          } else if (tags.indexOf('_falcon_extension_name=HDFS-SNAPSHOT-MIRRORING') !== -1) {
+            return "snapshot";
+          } else if (tags.indexOf('_falcon_extension_name=HIVE-MIRRORING') !== -1) {
+            return "hive-mirror";
+          }
+        };
+
         scope.displayIcon = function (type, tags) {
           if(type === "FEED"){
             return "entypo download";
-          }else if(type === "PROCESS" && scope.isMirror(tags)){
+          } else if(type === "CLUSTER"){
+            return "entypo archive";
+          } else if(type === "PROCESS" && scope.isMirror(tags)){
             return "glyphicon glyphicon-duplicate";
-          }else{
+          } else{
             return "entypo cycle";
           }
         };
 
         scope.displayType = function (tag) {
           var tagKeyVal = tag.split("=");
-          if(tagKeyVal[0] === "_falcon_mirroring_type"){
+          if(tagKeyVal[0] === "_falcon_extension_name"){
             return tagKeyVal[1];
           }else{
             return "";
@@ -302,4 +366,4 @@
     };
   }]);
 
-})();
\ No newline at end of file
+})();

http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/feed-cluster-partitions.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/feed-cluster-partitions.js b/falcon-ui/app/js/directives/feed-cluster-partitions.js
new file mode 100644
index 0000000..8fadb90
--- /dev/null
+++ b/falcon-ui/app/js/directives/feed-cluster-partitions.js
@@ -0,0 +1,119 @@
+/**
+ * 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.
+ */
+(function () {
+  'use strict';
+
+  var module = angular.module('app.directives.feed-cluster-partitions', []);
+
+  module.directive('feedFormClusterPartitions', function () {
+    return {
+      replace: false,
+      restrict: 'EA',
+      templateUrl: 'html/feed/feedFormClusterPartitionsTpl.html',
+      link: function ($scope, $element) {
+        function addFeedPartitionsIfNotExists(partitions, existingPartitions) {
+          var runningPartition;
+          for (var i=0;i<partitions.length;i++) {
+            runningPartition = partitions[i];
+            if (runningPartition.trim() != "") {
+              var matchedPartitions = existingPartitions.filter(function(element) {
+                return element.partitionName === runningPartition;
+              });
+              if (matchedPartitions.length < 1) {
+                existingPartitions.splice(i, 0, {"partitionName" : runningPartition,
+                  "partitionText" : "",
+                  "mapping" : "any"});
+              }
+            }
+          }
+        }
+
+        function removeClusterPartitionsIfNotExists(existingPartitions, partitions) {
+          var runningPartition;
+          var updatedExistingParititons = [];
+          for (var i=0;i<existingPartitions.length;i++) {
+            runningPartition = existingPartitions[i];
+            var matchedPartitions = partitions.filter(function(element) {
+              return element === runningPartition.partitionName;
+            });
+            if (matchedPartitions.length > 0) {
+              updatedExistingParititons.push(runningPartition);
+            }
+          }
+          return updatedExistingParititons;
+        }
+
+        function createDefaultClusterPartitions() {
+          var currentPartitions = ($scope.feedPartitions.trim() === "") ? [] : $scope.feedPartitions.split(",");
+          addFeedPartitionsIfNotExists(currentPartitions, $scope.partitionList);
+          $scope.partitionList = removeClusterPartitionsIfNotExists($scope.partitionList, currentPartitions);
+        }
+
+        function createSpecificClusterPartitions() {
+          var partitionExpressions = $scope.cluster.partition.split("/");
+          var currentPartitions = ($scope.feedPartitions.trim() === "") ? [] : $scope.feedPartitions.split(",");
+          partitionExpressions.forEach(function(partitionExpression, index) {
+            $scope.partitionList.push({"partitionName" : currentPartitions[index],
+              "partitionText" : (partitionExpression == "*") ? "" : partitionExpression,
+              "mapping" : (partitionExpression == "*") ? "any" : "mappedPartition"
+            });
+          });
+        }
+
+        $scope.preparePartitionExpression = function(partitionList) {
+          var partitionExpression = "";
+          for(var i=0;i<partitionList.length;i++) {
+            var partition = partitionList[i];
+            if (partition.mapping === 'any') {
+              partitionExpression = partitionExpression.concat("*");
+            } else if (partition.mapping === 'mappedPartition') {
+              partitionExpression = partitionExpression.concat(partition.partitionText);
+            }
+            if (i < partitionList.length-1) {
+              partitionExpression = partitionExpression.concat("/");
+            }
+          }
+          $scope.cluster.partition = partitionExpression;
+        }
+
+        $scope.partitionList = [];
+        if ($scope.cluster.partition != undefined) {
+          $scope.selectPartition = true;
+          createSpecificClusterPartitions()
+        } else {
+          $scope.selectPartition = false;
+          createDefaultClusterPartitions();
+        }
+
+        $scope.$watch('selectPartition', function() {
+          if ($scope.selectPartition) {
+            $scope.preparePartitionExpression($scope.partitionList);
+          } else {
+            delete $scope.cluster.partition;
+          }
+        });
+
+        $scope.$on('feed:createClusterPartitions', function() {
+          createDefaultClusterPartitions();
+          $scope.preparePartitionExpression($scope.partitionList);
+        });
+      }
+    };
+  });
+
+})();