You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@falcon.apache.org by so...@apache.org on 2015/10/13 02:04:01 UTC

[08/22] falcon git commit: FALCON-1315 Update falcon ui for HiveDR, secure clusters and bug fixes. Contributed by Armando Reyna/Venkat Ranganathan.

http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/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
new file mode 100644
index 0000000..c8faa74
--- /dev/null
+++ b/falcon-ui/app/js/directives/dependencies-graph.js
@@ -0,0 +1,294 @@
+/**
+ * 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 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.directive('dependenciesGraph', ["$timeout", 'Falcon', '$filter', '$state', 'X2jsService', 'EntityModel',
+                                              function($timeout, Falcon, $filter, $state, X2jsService, EntityModel) {
+    return {
+      scope: {
+        type: "=",
+        name: "="
+      },
+      controller: 'DependenciesGraphCtrl',
+      restrict: "EA",
+      templateUrl: 'html/directives/dependenciesGraphDv.html',
+      link: function (scope, element) {
+
+        var loadDependencyGraph = function(entity_type, entity_name, done_callback) {
+          var nodes = {};
+          var next_node_id = 0;
+
+          var requests_in_fly = 0;
+
+          function key(type, name) {
+            return type + '/' + name;
+          }
+
+          function getOrCreateNode(type, name) {
+            var k = key(type, name);
+            if (nodes[k] !== undefined)
+              return nodes[k];
+
+            var n = {
+              "id": next_node_id++,
+              "type": type,
+              "name": name,
+              "dependency": []
+            };
+            nodes[k] = n;
+            return n;
+          }
+
+          function loadEntry(node) {
+            var type = node.type, name = node.name, k = key(type, name);
+
+            Falcon.logRequest();
+            Falcon.getEntityDependencies(type, name)
+                .success(function (data) {
+                  Falcon.logResponse('success', data, false, true);
+
+                  if (data.entity == null)
+                    return;
+
+                  if (!($.isArray(data.entity)))
+                    data.entity = new Array(data.entity);
+
+                  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 src = null, dst = null;
+                    if (d.type === "cluster") {
+                      src = node; dst = d;
+                    } else if (d.type === "process") {
+                      src = d; dst = node;
+                    } else {
+                      if (node.type === "cluster") {
+                        src = d; dst = node;
+                      } else {
+                        src = node; dst = d;
+                      }
+                    }
+                    //console.log(src.name + '->' + dst.name);
+                    src.dependency.push(dst.id);
+                  }
+
+                  done_callback(nodes);
+                })
+                .error(function (err) {
+                  Falcon.logResponse('error', err, false, true);
+                });
+          }
+
+          function load() {
+            var n = getOrCreateNode(entity_type, entity_name);
+            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");
+
+          // 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 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);
+                  });
+
+
+              });
+
+          }
+
+          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);
+                });
+          }
+
+          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');
+          }
+
+          function plot() {
+            var g = new dagre.Digraph();
+
+            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 });
+            }
+
+            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);
+              }
+            }
+
+            layout = dagre.layout()
+                .universalSep(UNIVERSAL_SEP).rankSep(SEPARATION)
+                .run(g);
+            layout.eachEdge(drawEdge);
+            layout.eachNode(drawNode);
+
+            var graph = layout.graph();
+
+            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]);
+          });
+        };
+
+        //console.log(scope.type + " " + scope.name);
+        visualizeDependencyGraph(scope.type, scope.name);
+
+      }
+    };
+  }]);
+
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/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 c839269..9c388ea 100644
--- a/falcon-ui/app/js/directives/directives.js
+++ b/falcon-ui/app/js/directives/directives.js
@@ -20,11 +20,16 @@
 
   var directivesModule = angular.module('app.directives', [
     'app.services',
-    'app.directives.entities-list',
+    //'app.directives.entities-list',
+    'app.directives.entities-search-list',
+    'app.directives.instances-list',
     'app.directives.server-messages',
     'app.directives.entity',
     'app.directives.check-name',
-    'app.directives.validation-message'
+    'app.directives.validation-message',
+    'chart-module',
+    'app.directives.dependencies-graph',
+    'app.directives.lineage-graph'
   ]);
 
   directivesModule.directive('navHeader', function () {
@@ -88,4 +93,90 @@
     };
   });
 
+  directivesModule.directive('simpleDate', ['$filter', function ($filter) {
+    return {
+      require: 'ngModel',
+      link: function (scope, element, attrs, ngModelController) {
+        ngModelController.$parsers.push(function (data) {
+          //convert data from view format to model format
+          return data;
+        });
+        ngModelController.$formatters.push(function (date) {
+          //convert data from model format to view format
+          if (date !== "") {
+            date = $filter('date')(date, 'MM/dd/yyyy');
+          }
+          return date;
+        });
+      }
+    };
+  }]);
+
+  directivesModule.directive('ngEnter', function () {
+    return function (scope, element, attrs) {
+      element.bind("keydown keypress", function (event) {
+        if (event.which === 13) {
+          scope.$apply(function () {
+            scope.$eval(attrs.ngEnter);
+          });
+          event.preventDefault();
+        }
+      });
+    };
+  });
+
+  directivesModule.directive('elastic', ['$timeout', function ($timeout) {
+    return {
+      restrict: 'A',
+      link: function ($scope, element) {
+        $scope.$watch(function () {
+          return element[0].value;
+        }, function () {
+          resize();
+        });
+        var resize = function () {
+          element[0].style.height = "250px";
+          return element[0].style.height = "" + element[0].scrollHeight + "px";
+        };
+        $timeout(resize, 0);
+      }
+    };
+  }
+  ]);
+
+  directivesModule.directive('autofocus', ['$timeout', function ($timeout) {
+    return {
+      restrict: 'A',
+      link: function ($scope, element) {
+        $timeout(function () {
+          element.trigger('focus');
+        }, 20);
+      }
+    };
+  }
+  ]);
+
+  directivesModule.filter('dateFormatter', function () {
+    return function (date) {
+      console.log(date);
+      var dates = date.split('T')[0],
+        time = date.split('T')[1].split('Z')[0].split('.')[0];
+      return dates + ' ' + time;
+    };
+  });
+
+  directivesModule.directive('onBlur', [function () {
+    return {
+      restrict: 'A',
+      link: function (scope, elm, attrs) {
+        elm.bind('blur', function () {
+          if (attrs.onBlur)
+            scope[attrs.onBlur]();
+          else
+            return false;
+        });
+      }
+    };
+  }]);
+
 }());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/entities-list.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/entities-list.js b/falcon-ui/app/js/directives/entities-list.js
index aeced2f..065806a 100644
--- a/falcon-ui/app/js/directives/entities-list.js
+++ b/falcon-ui/app/js/directives/entities-list.js
@@ -19,10 +19,10 @@
   'use strict';
 
 	var entitiesListModule = angular.module('app.directives.entities-list', ['app.services' ]);
-	
+
   entitiesListModule.controller('EntitiesListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
                                       function($scope, Falcon, X2jsService, $window, encodeService) {
-                                        
+
     $scope.downloadEntity = function(type, name) {
       Falcon.logRequest();
       Falcon.getEntityDefinition(type, name) .success(function (data) {
@@ -32,26 +32,26 @@
         Falcon.logResponse('error', err, false);
       });
     };
-    
+
   }]);
-	
+
 	entitiesListModule.filter('tagFilter', function () {
     return function (items) {
       var filtered = [], i;
       for (i = 0; i < items.length; i++) {
         var item = items[i];
         if(!item.list || !item.list.tag) { item.list = {tag:[""]}; }
-        filtered.push(item); 
+        filtered.push(item);
       }
       return filtered;
     };
   });
-	
+
   entitiesListModule.directive('entitiesList', ["$timeout", 'Falcon', function($timeout, Falcon) {
     return {
       scope: {
         input: "=",
-        schedule: "=",  
+        schedule: "=",
         suspend: "=",
         clone: "=",
         remove: "=",
@@ -73,6 +73,15 @@
         }, true);
 
         scope.selectedRows = [];
+        scope.checkedRow = function (name) {
+          var isInArray = false;
+          scope.selectedRows.forEach(function(item) {
+            if (name === item.name) {
+              isInArray = true;
+            }
+          });
+          return isInArray;
+        };
         scope.simpleFilter = {};
         scope.selectedDisabledButtons = {
           schedule:true,
@@ -131,10 +140,10 @@
         };
 
         scope.scopeEdit = function () {
-          scope.edit(scope.selectedRows[0].type, scope.selectedRows[0].name);       
+          scope.edit(scope.selectedRows[0].type, scope.selectedRows[0].name);
         };
         scope.scopeClone = function () {
-          scope.clone(scope.selectedRows[0].type, scope.selectedRows[0].name);        
+          scope.clone(scope.selectedRows[0].type, scope.selectedRows[0].name);
         };
         scope.goEntityDetails = function(name, type) {
           scope.entityDetails(name, type);
@@ -142,18 +151,18 @@
 
         scope.scopeRemove = function () {
           var i;
-          for(i = 0; i < scope.selectedRows.length; i++) {   
-            var multiRequestType = scope.selectedRows[i].type.toLowerCase();          
-            Falcon.responses.multiRequest[multiRequestType] += 1;          
-            scope.remove(scope.selectedRows[i].type, scope.selectedRows[i].name);          
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            scope.remove(scope.selectedRows[i].type, scope.selectedRows[i].name);
           }
         };
 
         scope.scopeSchedule = function () {
-          var i;      
+          var i;
           for(i = 0; i < scope.selectedRows.length; i++) {
             var multiRequestType = scope.selectedRows[i].type.toLowerCase();
-            Falcon.responses.multiRequest[multiRequestType] += 1;   
+            Falcon.responses.multiRequest[multiRequestType] += 1;
             scope.schedule(scope.selectedRows[i].type, scope.selectedRows[i].name);
           }
         };
@@ -162,7 +171,7 @@
           var i;
           for(i = 0; i < scope.selectedRows.length; i++) {
             var multiRequestType = scope.selectedRows[i].type.toLowerCase();
-            Falcon.responses.multiRequest[multiRequestType] += 1;   
+            Falcon.responses.multiRequest[multiRequestType] += 1;
             scope.suspend(scope.selectedRows[i].type, scope.selectedRows[i].name);
           }
         };
@@ -170,20 +179,20 @@
           var i;
           for(i = 0; i < scope.selectedRows.length; i++) {
             var multiRequestType = scope.selectedRows[i].type.toLowerCase();
-            Falcon.responses.multiRequest[multiRequestType] += 1;   
+            Falcon.responses.multiRequest[multiRequestType] += 1;
             scope.resume(scope.selectedRows[i].type, scope.selectedRows[i].name);
           }
         };
 
         scope.download = function() {
           var i;
-          for(i = 0; i < scope.selectedRows.length; i++) {       
+          for(i = 0; i < scope.selectedRows.length; i++) {
             scope.downloadEntity(scope.selectedRows[i].type, scope.selectedRows[i].name);
           }
         };
-   
+
       }
     };
   }]);
-   
+
 })();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/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
new file mode 100644
index 0000000..3e76f63
--- /dev/null
+++ b/falcon-ui/app/js/directives/entities-search-list.js
@@ -0,0 +1,305 @@
+/**
+ * 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 entitiesListModule = angular.module('app.directives.entities-search-list', ['app.services' ]);
+
+  entitiesListModule.controller('EntitiesSearchListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
+                                      function($scope, Falcon, X2jsService, $window, encodeService) {
+
+    $scope.downloadEntity = function(type, name) {
+      Falcon.logRequest();
+      Falcon.getEntityDefinition(type, name) .success(function (data) {
+        Falcon.logResponse('success', data, false, true);
+        $window.location.href = 'data:application/octet-stream,' + encodeService.encode(data);
+      }).error(function (err) {
+        Falcon.logResponse('error', err, false);
+      });
+    };
+
+  }]);
+
+  entitiesListModule.filter('tagFilter', function () {
+    return function (items) {
+      var filtered = [], i;
+      for (i = 0; i < items.length; i++) {
+        var item = items[i];
+        if(!item.list || !item.list.tag) { item.list = {tag:[""]}; }
+        filtered.push(item);
+      }
+      return filtered;
+    };
+  });
+
+  entitiesListModule.directive('entitiesSearchList', ["$timeout", 'Falcon', function($timeout, Falcon) {
+    return {
+      scope: {
+        input: "=",
+        schedule: "=",
+        suspend: "=",
+        clone: "=",
+        remove: "=",
+        edit: "=",
+        type: "@",
+        tags: "=",
+        focusSearch: "=",
+        entityDetails:"=",
+        entityDefinition:"=",
+        resume:"=",
+        refresh: "=",
+        pages: "=",
+        goPage: "="
+      },
+      controller: 'EntitiesSearchListCtrl',
+      restrict: "EA",
+      templateUrl: 'html/directives/entitiesSearchListDv.html',
+      link: function (scope) {
+        scope.server = Falcon;
+        scope.$watch('input', function() {
+          scope.selectedRows = [];
+          scope.checkButtonsToShow();
+
+        }, true);
+
+        scope.selectedRows = [];
+        scope.mirrorTag = "_falcon_mirroring_type";
+
+        scope.checkedRow = function (name) {
+          var isInArray = false;
+          scope.selectedRows.forEach(function(item) {
+            if (name === item.name) {
+              isInArray = true;
+            }
+          });
+          return isInArray;
+        };
+
+        var nameOrder = true;
+        scope.toggleSortOrder = function () {
+          Falcon.orderBy.enable = true;
+          if (nameOrder) {
+            Falcon.orderBy.name = 'asc';
+          } else {
+            Falcon.orderBy.name = 'desc';
+          }
+          nameOrder = !nameOrder;
+          scope.$parent.refreshList(scope.$parent.tags);
+
+        };
+
+        scope.simpleFilter = {};
+
+        scope.selectedDisabledButtons = {
+          schedule:true,
+          suspend:true,
+          resume:true
+        };
+
+        scope.checkButtonsToShow = function() {
+          var statusCount = {
+            "SUBMITTED":0,
+            "RUNNING":0,
+            "SUSPENDED":0,
+            "UNKNOWN":0
+          };
+
+          $timeout(function() {
+
+            if(scope.selectedRows.length === scope.input.length){
+              scope.selectedAll = true;
+            }else{
+              scope.selectedAll = false;
+            }
+
+            scope.selectedRows.forEach(function(entity) {
+              statusCount[entity.status] = statusCount[entity.status]+1;
+            });
+
+            if(statusCount.SUBMITTED > 0) {
+              if(statusCount.RUNNING > 0 || statusCount.SUSPENDED > 0 || statusCount.UNKNOWN > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:false, suspend:true, resume:true };
+              }
+            }
+            if(statusCount.RUNNING > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.SUSPENDED > 0 || statusCount.UNKNOWN > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:false, resume:true };
+              }
+            }
+            if (statusCount.SUSPENDED > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.RUNNING > 0 || statusCount.UNKNOWN > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:false };
+              }
+            }
+            if (statusCount.UNKNOWN > 0) {
+              scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true };
+            }
+
+            if(scope.selectedRows.length === 0) {
+              scope.selectedDisabledButtons = {
+                schedule:true,
+                suspend:true,
+                resume:true
+              };
+            }
+          }, 50);
+        };
+
+        var isSelected = function(item){
+          var selected = false;
+          scope.selectedRows.forEach(function(entity) {
+            if(angular.equals(item, entity)){
+              selected = true;
+            }
+          });
+          return selected;
+        };
+
+        scope.checkAll = function () {
+          if(scope.selectedRows.length === scope.input.length){
+            angular.forEach(scope.input, function (item) {
+              scope.selectedRows.pop();
+            });
+          }else{
+            angular.forEach(scope.input, function (item) {
+              var checkbox = {name:item.name, type:item.type, status:item.status};
+              if(!isSelected(checkbox)){
+                scope.selectedRows.push(checkbox);
+              }
+            });
+          }
+        };
+
+        scope.addTag = function(text){
+          var added = false;
+          angular.forEach(scope.tags, function (scopeTag) {
+            if(scopeTag.text === text){
+              added = true;
+            }
+          });
+          if(!added){
+            var tag = {text:"Tag:"+text};
+            scope.tags.push(tag);
+            scope.focusSearch();
+          }
+        };
+
+        scope.scopeEdit = function () {
+          scope.edit(scope.selectedRows[0].type, scope.selectedRows[0].name);
+        };
+        scope.scopeClone = function () {
+          scope.clone(scope.selectedRows[0].type, scope.selectedRows[0].name);
+        };
+        scope.goEntityDefinition = function(name, type) {
+          scope.entityDefinition(name, type);
+        };
+        scope.goEntityDetails = function(name, type) {
+          scope.entityDetails(name, type);
+        };
+
+        scope.scopeRemove = function () {
+          var i;
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            scope.remove(scope.selectedRows[i].type, scope.selectedRows[i].name);
+          }
+        };
+
+        scope.scopeSchedule = function () {
+          var i;
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            scope.schedule(scope.selectedRows[i].type, scope.selectedRows[i].name);
+          }
+        };
+
+        scope.scopeSuspend = function () {
+          var i;
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            scope.suspend(scope.selectedRows[i].type, scope.selectedRows[i].name);
+          }
+        };
+        scope.scopeResume = function () {
+          var i;
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            scope.resume(scope.selectedRows[i].type, scope.selectedRows[i].name);
+          }
+        };
+
+        scope.download = function() {
+          var i;
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            scope.downloadEntity(scope.selectedRows[i].type, scope.selectedRows[i].name);
+          }
+        };
+
+        scope.scopeGoPage = function (page) {
+          scope.goPage(page);
+        };
+
+        scope.isMirror = function(tags){
+          var flag = false;
+          if(tags !== undefined){
+            tags.forEach(function(tag) {
+              if(tag.indexOf(scope.mirrorTag) !== -1){
+                flag = true;
+              }
+            });
+          }
+          return flag;
+        };
+
+        scope.displayIcon = function (type, tags) {
+          if(type === "FEED"){
+            return "entypo download";
+          }else if(type === "PROCESS" && scope.isMirror(tags)){
+            return "glyphicon glyphicon-duplicate";
+          }else{
+            return "entypo cycle";
+          }
+        };
+
+        scope.displayType = function (tag) {
+          var tagKeyVal = tag.split("=");
+          if(tagKeyVal[0] === "_falcon_mirroring_type"){
+            return tagKeyVal[1];
+          }else{
+            return "";
+          }
+        };
+
+      }
+    };
+  }]);
+
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/instances-list.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/instances-list.js b/falcon-ui/app/js/directives/instances-list.js
new file mode 100644
index 0000000..269c7c1
--- /dev/null
+++ b/falcon-ui/app/js/directives/instances-list.js
@@ -0,0 +1,832 @@
+/**
+ * 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 entitiesListModule = angular.module('app.directives.instances-list', ['app.services' ]);
+
+  entitiesListModule.controller('InstancesListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
+                                      function($scope, Falcon, X2jsService, $window, encodeService) {
+
+    //$scope.downloadEntity = function(logURL) {
+    //  Falcon.logRequest();
+    //  Falcon.getInstanceLog(logURL) .success(function (data) {
+    //    Falcon.logResponse('success', data, false, true);
+    //    $window.location.href = 'data:application/octet-stream,' + encodeService.encode(data);
+    //  }).error(function (err) {
+    //    Falcon.logResponse('error', err, false);
+    //  });
+    //};
+
+    $scope.downloadEntity = function(logURL) {
+      $window.location.href = logURL;
+    };
+
+  }]);
+
+  entitiesListModule.filter('tagFilter', function () {
+    return function (items) {
+      var filtered = [], i;
+      for (i = 0; i < items.length; i++) {
+        var item = items[i];
+        if(!item.list || !item.list.tag) { item.list = {tag:[""]}; }
+        filtered.push(item);
+      }
+      return filtered;
+    };
+  });
+
+  entitiesListModule.directive('instancesList', ["$timeout", 'Falcon', '$filter', function($timeout, Falcon, $filter) {
+    return {
+      scope: {
+        input: "=",
+        type: "=",
+        name: "=",
+        start: "=",
+        end: "=",
+        instanceDetails:"=",
+        refresh: "=",
+        pages: "=",
+        nextPages: "=",
+        prevPages: "=",
+        goPage: "=",
+        changePagesSet: "="
+      },
+      controller: 'InstancesListCtrl',
+      restrict: "EA",
+      templateUrl: 'html/directives/instancesListDv.html',
+      link: function (scope) {
+        scope.server = Falcon;
+        scope.$watch(function () { return scope.input; }, function() {
+          scope.selectedRows = [];
+          scope.checkButtonsToShow();
+        }, true);
+
+        var resultsPerPage = 10;
+        var visiblePages = 3;
+        scope.selectedRows = [];
+        scope.$parent.refreshInstanceList(scope.type, scope.name, scope.start, scope.end);
+
+        scope.startSortOrder = "desc";
+        scope.endSortOrder = "desc";
+        scope.statusSortOrder = "desc";
+
+        scope.checkedRow = function (name) {
+          var isInArray = false;
+          scope.selectedRows.forEach(function(item) {
+            if (name === item.instance) {
+              isInArray = true;
+            }
+          });
+          return isInArray;
+        };
+
+        scope.simpleFilter = {};
+
+        scope.selectedDisabledButtons = {
+          schedule:true,
+          suspend:true,
+          resume:true,
+          stop:true
+        };
+
+        scope.checkButtonsToShow = function() {
+          var statusCount = {
+            "SUBMITTED":0,
+            "RUNNING":0,
+            "SUSPENDED":0,
+            "UNKNOWN":0,
+            "KILLED":0,
+            "WAITING":0,
+            "FAILED":0,
+            "SUCCEEDED":0
+          };
+
+          $timeout(function() {
+
+            if(scope.selectedRows.length === scope.input.length){
+              scope.selectedAll = true;
+            }else{
+              scope.selectedAll = false;
+            }
+
+            scope.selectedRows.forEach(function(instance) {
+              statusCount[instance.status] = statusCount[instance.status]+1;
+            });
+
+            if(statusCount.SUBMITTED > 0) {
+              if(statusCount.RUNNING > 0 || statusCount.SUSPENDED > 0 || statusCount.UNKNOWN > 0 || statusCount.KILLED > 0 || statusCount.WAITING > 0 || statusCount.FAILED > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:false, suspend:true, resume:true, stop:true, rerun:true };
+              }
+            }
+            if(statusCount.RUNNING > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.SUSPENDED > 0 || statusCount.UNKNOWN > 0 || statusCount.KILLED > 0 || statusCount.WAITING > 0 || statusCount.FAILED > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:false, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:false, resume:true, stop:false, rerun:true  };
+              }
+            }
+            if (statusCount.SUSPENDED > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.RUNNING > 0 || statusCount.UNKNOWN > 0 || statusCount.KILLED > 0 || statusCount.WAITING > 0 || statusCount.FAILED > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:false, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:false, stop:false, rerun:true };
+              }
+            }
+            if (statusCount.KILLED > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.SUSPENDED > 0 || statusCount.RUNNING > 0 || statusCount.UNKNOWN > 0 || statusCount.WAITING > 0 || statusCount.FAILED > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:false };
+              }
+            }
+            if(statusCount.WAITING > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.RUNNING > 0 || statusCount.SUSPENDED > 0 || statusCount.UNKNOWN > 0 || statusCount.KILLED > 0 || statusCount.FAILED > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true  };
+              }
+            }
+            if (statusCount.FAILED > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.SUSPENDED > 0 || statusCount.RUNNING > 0 || statusCount.UNKNOWN > 0 || statusCount.KILLED > 0 || statusCount.WAITING > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:false };
+              }
+            }
+            if(statusCount.SUCCEEDED > 0) {
+              if(statusCount.SUBMITTED > 0 || statusCount.RUNNING > 0 || statusCount.SUSPENDED > 0 || statusCount.UNKNOWN > 0 || statusCount.KILLED > 0 || statusCount.WAITING > 0 || statusCount.FAILED > 0) {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true };
+              }
+              else {
+                scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:false };
+              }
+            }
+            if (statusCount.UNKNOWN > 0) {
+              scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true, stop:true, rerun:true };
+            }
+
+            if(scope.selectedRows.length === 0) {
+              scope.selectedDisabledButtons = {
+                schedule:true,
+                resume:true,
+                suspend:true,
+                stop:true,
+                rerun:true
+              };
+            }
+          }, 50);
+        };
+
+        var isSelected = function(item){
+          var selected = false;
+          scope.selectedRows.forEach(function(entity) {
+            if(angular.equals(item, entity)){
+              selected = true;
+            }
+          });
+          return selected;
+        }
+
+        scope.checkAll = function () {
+          if(scope.selectedRows.length >= scope.input.length){
+            angular.forEach(scope.input, function (item) {
+              scope.selectedRows.pop();
+            });
+          }else{
+            angular.forEach(scope.input, function (item) {
+              var checkbox = {'instance':item.instance, 'startTime':item.startTime, 'endTime':item.endTime, 'status':item.status, 'type':scope.type, 'logFile':item.logFile};
+              if(!isSelected(checkbox)){
+                scope.selectedRows.push(checkbox);
+              }
+            });
+          }
+        };
+
+        scope.goInstanceDetails = function(instance) {
+          scope.instanceDetails(instance);
+        };
+
+        var resumeInstance = function (type, name, start, end, refresh) {
+          Falcon.logRequest();
+          Falcon.postResumeInstance(type, name, start, end)
+            .success(function (message) {
+              Falcon.logResponse('success', message, type);
+              if(refresh){
+                scope.$parent.refreshInstanceList(scope.type, scope.name, scope.start, scope.end);
+              }
+            })
+            .error(function (err) {
+              Falcon.logResponse('error', err, type);
+
+            });
+        };
+
+        var suspendInstance = function (type, name, start, end, refresh) {
+          Falcon.logRequest();
+          Falcon.postSuspendInstance(type, name, start, end)
+            .success(function (message) {
+              Falcon.logResponse('success', message, type);
+              if(refresh){
+                scope.$parent.refreshInstanceList(scope.type, scope.name, scope.start, scope.end);
+              }
+            })
+            .error(function (err) {
+              Falcon.logResponse('error', err, type);
+
+            });
+        };
+
+        var reRunInstance = function (type, name, start, end, refresh) {
+          Falcon.logRequest();
+          Falcon.postReRunInstance(type, name, start, end)
+            .success(function (message) {
+              Falcon.logResponse('success', message, type);
+              if(refresh){
+                scope.$parent.refreshInstanceList(scope.type, scope.name, scope.start, scope.end);
+              }
+            })
+            .error(function (err) {
+              Falcon.logResponse('error', err, type);
+
+            });
+        };
+
+        var killInstance = function (type, name, start, end, refresh) {
+          Falcon.logRequest();
+          Falcon.postKillInstance(type, name, start, end)
+            .success(function (message) {
+              Falcon.logResponse('success', message, type);
+              if(refresh){
+                scope.$parent.refreshInstanceList(scope.type, scope.name, scope.start, scope.end);
+              }
+            })
+            .error(function (err) {
+              Falcon.logResponse('error', err, type);
+
+            });
+        };
+
+        scope.scopeResume = function () {
+          for(var i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            var start = scope.selectedRows[i].instance;
+            var end = addOneMin(start);
+            var refresh = i === scope.selectedRows.length-1 ? true : false;
+            resumeInstance(scope.type, scope.name, start, end, refresh);
+          }
+        };
+
+        scope.scopeSuspend = function () {
+          for(var i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            var start = scope.selectedRows[i].instance;
+            var end = addOneMin(start);
+            var refresh = i === scope.selectedRows.length-1 ? true : false;
+            suspendInstance(scope.type, scope.name, start, end, refresh);
+          }
+        };
+
+        scope.scopeRerun = function () {
+          for(var i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            var start = scope.selectedRows[i].instance;
+            var end = addOneMin(start);
+            var refresh = i === scope.selectedRows.length-1 ? true : false;
+            reRunInstance(scope.type, scope.name, start, end, refresh);
+          }
+        };
+
+        scope.scopeKill = function () {
+          for(var i = 0; i < scope.selectedRows.length; i++) {
+            var multiRequestType = scope.selectedRows[i].type.toLowerCase();
+            Falcon.responses.multiRequest[multiRequestType] += 1;
+            var start = scope.selectedRows[i].instance;
+            var end = addOneMin(start);
+            var refresh = i === scope.selectedRows.length-1 ? true : false;
+            killInstance(scope.type, scope.name, start, end, refresh);
+          }
+        };
+
+        scope.download = function() {
+          var i;
+          for(i = 0; i < scope.selectedRows.length; i++) {
+            scope.downloadEntity(scope.selectedRows[i].logFile);
+          }
+        };
+
+        scope.scopeGoPage = function (page) {
+          scope.goPage(page);
+        };
+
+        scope.scopeNextOffset = function (page) {
+          var offset = (parseInt(scope.pages[0].label)+(visiblePages-1))*resultsPerPage;
+          scope.changePagesSet(offset, page, 0, scope.start, scope.end);
+        };
+
+        scope.scopePrevOffset = function (page) {
+          var offset = (parseInt(scope.pages[0].label)-(visiblePages+1))*resultsPerPage;
+          scope.changePagesSet(offset, page, visiblePages-1, scope.start, scope.end);
+        };
+
+        scope.validateDate = function(event, type){
+          var which = event.which || event.keyCode;
+          var charStr = String.fromCharCode(which);
+          event.preventDefault();
+          if (
+            which == 8 || which == 46 || which == 37 || which == 39 ||
+            (which >= 48 && which <= 57)
+          ) {
+            if(type == "start"){
+              if(scope.startFilter){
+                if(scope.startFilter.length == 1){
+                  //mm
+                  var prevChar = scope.startFilter.substring(scope.startFilter.length-1);
+                  prevChar = parseInt(prevChar);
+                  if(prevChar < 1){
+                    if(prevChar == 0 && charStr == 0){
+
+                    }else if(charStr <= 9){
+                      scope.startFilter += charStr + "/";
+                    }
+                  }else{
+                    if(charStr <= 2){
+                      scope.startFilter += charStr + "/";
+                    }
+                  }
+                }else if(scope.startFilter.length == 2){
+                  //mm/
+                  if(charStr <= 3){
+                    scope.startFilter += "/" + charStr;
+                  }
+                }else if(scope.startFilter.length == 3){
+                  //mm/d
+                  if(charStr <= 3){
+                    scope.startFilter += charStr;
+                  }
+                }else if(scope.startFilter.length == 4){
+                  //mm/dd
+                  var prevChar = scope.startFilter.substring(scope.startFilter.length-1);
+                  prevChar = parseInt(prevChar);
+                  if(prevChar < 3){
+                    if(prevChar == 0 && charStr == 0){
+
+                    }else if(charStr <= 9){
+                      scope.startFilter += charStr + "/";
+                    }
+                  }else{
+                    if(charStr <= 1){
+                      scope.startFilter += charStr + "/";
+                    }
+                  }
+                }else if(scope.startFilter.length == 5){
+                  //mm/dd/
+                  if(charStr <= 2){
+                    scope.startFilter += "/" + charStr;
+                  }
+                }else if(scope.startFilter.length == 6){
+                  //mm/dd/y
+                  if(charStr <= 2){
+                    scope.startFilter += charStr;
+                  }
+                }else if(scope.startFilter.length == 7){
+                  //mm/dd/yy
+                  if(charStr <= 9){
+                    scope.startFilter += charStr;
+                  }
+                }else if(scope.startFilter.length == 8){
+                  //mm/dd/yyy
+                  if(charStr <= 9){
+                    scope.startFilter += charStr;
+                  }
+                }else if(scope.startFilter.length == 9){
+                  //mm/dd/yyyy
+                  if(charStr <= 9){
+                    scope.startFilter += charStr + " ";
+                  }
+                }else if(scope.startFilter.length == 10){
+                  //mm/dd/yyyy
+                  if(charStr <= 2){
+                    scope.startFilter += " " + charStr;
+                  }
+                }else if(scope.startFilter.length == 11){
+                  //mm/dd/yyyy h
+                  if(charStr <= 2){
+                    scope.startFilter += charStr;
+                  }
+                }else if(scope.startFilter.length == 12){
+                  //mm/dd/yyyy hh
+                  var prevChar = scope.startFilter.substring(scope.startFilter.length-1);
+                  prevChar = parseInt(prevChar);
+                  if(prevChar < 2){
+                    if(charStr <= 9){
+                      scope.startFilter += charStr + ":";
+                    }
+                  }else{
+                    if(charStr <= 4){
+                      scope.startFilter += charStr + ":";
+                    }
+                  }
+                }else if(scope.startFilter.length == 13){
+                  //mm/dd/yyyy hh:
+                  if(charStr <= 5){
+                    scope.startFilter += ":" + charStr;
+                  }
+                }else if(scope.startFilter.length == 14){
+                  //mm/dd/yyyy hh:m
+                  if(charStr <= 5){
+                    scope.startFilter += charStr;
+                  }
+                }else if(scope.startFilter.length == 15){
+                  //mm/dd/yyyy hh:mm
+                  if(charStr <= 9){
+                    scope.startFilter += charStr;
+                    scope.startFilterError = false;
+                  }
+                }
+              }else{
+                //m
+                if(charStr <= 1){
+                  scope.startFilter = charStr;
+                }
+              }
+            }else{
+              if(scope.endFilter){
+                if(scope.endFilter.length == 1){
+                  //mm
+                  var prevChar = scope.endFilter.substring(scope.endFilter.length-1);
+                  prevChar = parseInt(prevChar);
+                  if(prevChar < 1){
+                    if(prevChar == 0 && charStr == 0){
+
+                    }else if(charStr <= 9){
+                      scope.endFilter += charStr + "/";
+                    }
+                  }else{
+                    if(charStr <= 2){
+                      scope.endFilter += charStr + "/";
+                    }
+                  }
+                }else if(scope.endFilter.length == 2){
+                  //mm/
+                  if(charStr <= 3){
+                    scope.endFilter += "/" + charStr;
+                  }
+                }else if(scope.endFilter.length == 3){
+                  //mm/d
+                  if(charStr <= 3){
+                    scope.endFilter += charStr;
+                  }
+                }else if(scope.endFilter.length == 4){
+                  //mm/dd
+                  var prevChar = scope.endFilter.substring(scope.endFilter.length-1);
+                  prevChar = parseInt(prevChar);
+                  if(prevChar < 3){
+                    if(prevChar == 0 && charStr == 0){
+
+                    }else if(charStr <= 9){
+                      scope.endFilter += charStr + "/";
+                    }
+                  }else{
+                    if(charStr <= 1){
+                      scope.endFilter += charStr + "/";
+                    }
+                  }
+                }else if(scope.endFilter.length == 5){
+                  //mm/dd/
+                  if(charStr <= 2){
+                    scope.endFilter += "/" + charStr;
+                  }
+                }else if(scope.endFilter.length == 6){
+                  //mm/dd/y
+                  if(charStr <= 2){
+                    scope.endFilter += charStr;
+                  }
+                }else if(scope.endFilter.length == 7){
+                  //mm/dd/yy
+                  if(charStr <= 9){
+                    scope.endFilter += charStr;
+                  }
+                }else if(scope.endFilter.length == 8){
+                  //mm/dd/yyy
+                  if(charStr <= 9){
+                    scope.endFilter += charStr;
+                  }
+                }else if(scope.endFilter.length == 9){
+                  //mm/dd/yyyy
+                  if(charStr <= 9){
+                    scope.endFilter += charStr + " ";
+                  }
+                }else if(scope.endFilter.length == 10){
+                  //mm/dd/yyyy
+                  if(charStr <= 2){
+                    scope.endFilter += " " + charStr;
+                  }
+                }else if(scope.endFilter.length == 11){
+                  //mm/dd/yyyy h
+                  if(charStr <= 2){
+                    scope.endFilter += charStr;
+                  }
+                }else if(scope.endFilter.length == 12){
+                  //mm/dd/yyyy hh
+                  var prevChar = scope.endFilter.substring(scope.endFilter.length-1);
+                  prevChar = parseInt(prevChar);
+                  if(prevChar < 2){
+                    if(charStr <= 9){
+                      scope.endFilter += charStr + ":";
+                    }
+                  }else{
+                    if(charStr <= 4){
+                      scope.endFilter += charStr + ":";
+                    }
+                  }
+                }else if(scope.endFilter.length == 13){
+                  //mm/dd/yyyy hh:
+                  if(charStr <= 5){
+                    scope.endFilter += ":" + charStr;
+                  }
+                }else if(scope.endFilter.length == 14){
+                  //mm/dd/yyyy hh:m
+                  if(charStr <= 5){
+                    scope.endFilter += charStr;
+                  }
+                }else if(scope.endFilter.length == 15){
+                  //mm/dd/yyyy hh:mm
+                  if(charStr <= 9){
+                    scope.endFilter += charStr;
+                    scope.endFilterError = false;
+                  }
+                }
+              }else{
+                //m
+                if(charStr <= 1){
+                  scope.endFilter = charStr;
+                }
+              }
+            }
+          }
+        };
+
+        var changeDateFormat = function(date){
+          var completeDate = date.split(" ");
+          var dates = completeDate[0].split("/");
+          date = dates[2] + "-" + dates[0] + "-" + dates[1] + "T" + completeDate[1] + "Z";
+          return date;
+        };
+
+        var validateDateFormat = function(date){
+          var char = date.substring(0, 1);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(1, 2);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(2, 3);
+          if(char != "/"){
+            return false;
+          }
+          char = date.substring(3, 4);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(4, 5);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(5, 6);
+          if(char != "/"){
+            return false;
+          }
+          char = date.substring(6, 7);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(7, 8);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(8, 9);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(9, 10);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(10, 11);
+          if(char != " "){
+            return false;
+          }
+          char = date.substring(11, 12);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(12, 13);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(13, 14);
+          if(char != ":"){
+            return false;
+          }
+          char = date.substring(14, 15);
+          if(isNaN(char)){
+            return false;
+          }
+          char = date.substring(15, 16);
+          if(isNaN(char)){
+            return false;
+          }
+          return true;
+        };
+
+        scope.filterInstances = function(orderBy){
+          var start;
+          var end;
+          var executeFilter = false;
+          scope.startFilterError = false;
+          scope.endFilterError = false;
+          scope.startAfterEndError = false;
+          scope.startAfterNominalError = false;
+          scope.startBeforeNominalError = false;
+          scope.endAfterNominalError = false;
+          scope.endBeforeNominalError = false;
+          var nominalStartDate = new Date(scope.start);
+          var nominalEndDate = new Date(scope.end);
+          if(scope.startFilter && scope.endFilter){
+            if(scope.startFilter.length == 16 && scope.endFilter.length == 16){
+              if(!validateDateFormat(scope.startFilter)){
+                executeFilter = false;
+                scope.startFilterError = true;
+              }else if(!validateDateFormat(scope.endFilter)){
+                executeFilter = false;
+                scope.endFilterError = true;
+              }else{
+                start = changeDateFormat(scope.startFilter);
+                var filterStartDate = new Date(start);
+                end = changeDateFormat(scope.endFilter);
+                var filterEndDate = new Date(end);
+                if(filterStartDate > filterEndDate){
+                  executeFilter = false;
+                  scope.startAfterEndError = true;
+                }else{
+                  if(filterStartDate < nominalStartDate){
+                    executeFilter = false;
+                    scope.startAfterNominalError = true;
+                  }else if(filterStartDate > nominalEndDate){
+                    executeFilter = false;
+                    scope.startBeforeNominalError = true;
+                  }else if(filterEndDate < nominalStartDate){
+                    executeFilter = false;
+                    scope.endAfterNominalError = true;
+                  }else if(filterEndDate > nominalEndDate){
+                    executeFilter = false;
+                    scope.endBeforeNominalError = true;
+                  }else{
+                    executeFilter = true;
+                  }
+                }
+              }
+            }else{
+              if(scope.startFilter.length != 16){
+                scope.startFilterError = true;
+              }
+              if(scope.endFilter.length != 16){
+                scope.endFilterError = true;
+              }
+            }
+          }else if(scope.startFilter){
+            scope.endFilterError = false;
+            if(scope.startFilter.length == 16){
+              if(!validateDateFormat(scope.startFilter)){
+                executeFilter = false;
+                scope.startFilterError = true;
+              }else{
+                start = changeDateFormat(scope.startFilter);
+                var filterStartDate = new Date(start);
+                if(filterStartDate < nominalStartDate){
+                  executeFilter = false;
+                  scope.startAfterNominalError = true;
+                }else if(filterStartDate > nominalEndDate){
+                  executeFilter = false;
+                  scope.startBeforeNominalError = true;
+                }else{
+                  executeFilter = true;
+                }
+              }
+            }else{
+              scope.startFilterError = true;
+            }
+          }else if(scope.endFilter){
+            scope.startFilterError = false;
+            if(scope.endFilter.length == 16){
+              if(!validateDateFormat(scope.endFilter)){
+                executeFilter = false;
+                scope.endFilterError = true;
+              }else{
+                end = changeDateFormat(scope.endFilter);
+                var filterEndDate = new Date(end);
+                if(filterEndDate < nominalStartDate){
+                  executeFilter = false;
+                  scope.endAfterNominalError = true;
+                }else if(filterEndDate > nominalEndDate){
+                  executeFilter = false;
+                  scope.endBeforeNominalError = true;
+                }else{
+                  executeFilter = true;
+                }
+              }
+            }else{
+              scope.endFilterError = true;
+            }
+          }else{
+            executeFilter = true;
+          }
+
+          if(executeFilter){
+            var sortOrder = "";
+            if(orderBy){
+              if(orderBy === "startTime"){
+                if(scope.startSortOrder === "desc"){
+                  scope.startSortOrder = "asc";
+                }else{
+                  scope.startSortOrder = "desc";
+                }
+                sortOrder = scope.startSortOrder;
+              }else if(orderBy === "endTime"){
+                if(scope.endSortOrder === "desc"){
+                  scope.endSortOrder = "asc";
+                }else{
+                  scope.endSortOrder = "desc";
+                }
+                sortOrder = scope.endSortOrder;
+              }else if(orderBy === "status"){
+                if(scope.statusSortOrder === "desc"){
+                  scope.statusSortOrder = "asc";
+                }else{
+                  scope.statusSortOrder = "desc";
+                }
+                sortOrder = scope.statusSortOrder;
+              }
+            }else{
+              orderBy = "startTime";
+              sortOrder = "desc";
+            }
+
+            if(!start){
+              start = scope.start;
+            }
+            if(!end){
+              end = scope.end;
+            }
+
+            scope.$parent.refreshInstanceList(scope.type, scope.name, start, end, scope.statusFilter, orderBy, sortOrder);
+          }
+        }
+
+        var addOneMin = function(time){
+          var newtime = parseInt(time.substring(time.length-3, time.length-1));
+          if(newtime === 59){
+            newtime = 0;
+          }else{
+            newtime++;
+          }
+          if(newtime < 10){
+            newtime = "0"+newtime;
+          }
+          return time.substring(0, time.length-3) + newtime + "Z";
+        }
+
+      }
+    };
+  }]);
+
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/lineage-graph.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/directives/lineage-graph.js b/falcon-ui/app/js/directives/lineage-graph.js
new file mode 100644
index 0000000..883d2a7
--- /dev/null
+++ b/falcon-ui/app/js/directives/lineage-graph.js
@@ -0,0 +1,288 @@
+/**
+ * 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 entitiesListModule = angular.module('app.directives.lineage-graph', ['app.services' ]);
+
+  entitiesListModule.controller('LineageGraphCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService',
+                                      function($scope, Falcon, X2jsService, $window, encodeService) {
+  }]);
+
+  entitiesListModule.directive('lineageGraph', ["$timeout", 'Falcon', '$filter', function($timeout, Falcon, $filter) {
+    return {
+      scope: {
+        type: "=",
+        name: "=",
+        instance: "=",
+        start: "=",
+        end: "="
+      },
+      controller: 'LineageGraphCtrl',
+      restrict: "EA",
+      templateUrl: 'html/directives/lineageGraphDv.html',
+      link: function (scope, element) {
+
+        var CIRCLE_RADIUS = 12, RANK_SEPARATION = 120, LABEL_WIDTH = 120, LABEL_HEIGHT = 80, LABEL_PADDING = 20;
+        var PREFIX = '/api/graphs/lineage';
+
+        var data = {
+          queue : {},
+          nodes : {},
+          edges : {},
+          active_node_id : null
+        };
+
+        function process_queue(done_cb) {
+          var q = data.queue;
+          if (q.length === 0) {
+            done_cb();
+            return;
+          }
+
+          function filter_node(n) {
+            return n.type === 'process-instance' || n.type === 'feed-instance';
+          }
+
+          function is_nonterminal_label(e) {
+            return e._label === 'input' || e._label === 'output';
+          }
+
+          function visit_neighbor(p) {
+            var vid = p.id, depth = p.depth;
+            if (depth === 0) {
+              process_queue(done_cb);
+              return;
+            }
+
+            Falcon.logRequest();
+            Falcon.getInstanceVerticesDirection(vid, 'both')
+                .success(function (resp) {
+                  Falcon.logResponse('success', resp, false, true);
+                  for (var i = 0; i < resp.results.length; ++i) {
+                    var n = resp.results[i];
+                    if (data.nodes[n._id] !== undefined || !filter_node(n)) {
+                      continue;
+                    }
+                    data.nodes[n._id] = n;
+                    q.push({'id': n._id, 'depth': depth - 1});
+                  }
+                })
+                .error(function (err) {
+                  Falcon.logResponse('error', err, false, true);
+                })
+                .finally(function () {
+                  process_queue(done_cb);
+                });
+          }
+
+          var v = data.queue.pop();
+
+          Falcon.logRequest();
+          Falcon.getInstanceVerticesDirection(v.id, 'bothE')
+              .success(function (resp) {
+                Falcon.logResponse('success', resp, false, true);
+                var terminal_has_in_edge = false, terminal_has_out_edge = false;
+                var node = data.nodes[v.id];
+                for (var i = 0; i < resp.results.length; ++i) {
+                  var e = resp.results[i];
+                  if (is_nonterminal_label(e)) {
+                    terminal_has_in_edge = terminal_has_in_edge || e._inV === v.id;
+                    terminal_has_out_edge = terminal_has_out_edge || e._outV === v.id;
+                  }
+                  if (data.edges[e._id] !== undefined) {
+                    continue;
+                  }
+                  data.edges[e._id] = e;
+                }
+                node.is_terminal = !(terminal_has_in_edge && terminal_has_out_edge);
+              })
+              .error(function (err) {
+                Falcon.logResponse('error', err, false, true);
+              })
+              .finally(function () {
+                visit_neighbor(v);
+              });
+        }
+
+        function draw_graph() {
+          var svg = d3.select('#lineage-graph').html('').append('svg:g');
+
+          var LINE_FUNCTION = d3.svg.line()
+              .x(function(d) { return d.x; })
+              .y(function(d) { return d.y; })
+              .interpolate('basis');
+
+          function draw_node_functor(node_map) {
+            function expand_node(d) {
+              return function() {
+                data.queue.push({'id': d._id, 'depth': 1});
+                update_graph();
+              };
+            }
+
+            function show_info_functor(d) {
+              return function() {
+                svg.selectAll('.lineage-node').classed('lineage-node-active', false);
+                d3.select(this).classed('lineage-node-active', true);
+                data.active_node_id = d._id;
+                Falcon.logRequest();
+                Falcon.getInstanceVerticesProps(d._id)
+                    .success(function (resp) {
+                      Falcon.logResponse('success', resp, false, true);
+                        $('#lineage-info-panel').html(resp);
+                    })
+                    .error(function (err) {
+                      Falcon.logResponse('error', err, false, true);
+                    });
+              };
+            }
+
+            return function(id, d) {
+              var data = node_map[id];
+              var r = svg.append('g')
+                  .attr('transform', 'translate(' + d.x + ',' + d.y + ')');
+
+              var c = r.append('circle')
+                  .attr('data-node-id', id)
+                  .attr('class', 'lineage-node lineage-node-' + data.type)
+                  .attr('r', CIRCLE_RADIUS)
+                  .on('click', show_info_functor(data));
+
+              if (!data.is_terminal) {
+                c.on('dblclick', expand_node(data));
+              } else {
+                c.classed('lineage-node-terminal', true);
+              }
+
+              var name = node_map[id].name;
+              r.append('title').text(name);
+
+              var fo = r.append('foreignObject')
+                  .attr('transform', 'translate(' + (-LABEL_WIDTH / 2) + ', ' + LABEL_PADDING + ' )')
+                  .attr('width', LABEL_WIDTH)
+                  .attr('height', LABEL_HEIGHT);
+
+              fo.append('xhtml:div').text(name)
+                  .attr('class', 'lineage-node-text');
+            };
+          }
+
+          function draw_edge(layout) {
+            return function(e, u, v, value) {
+              var r = svg.append('g').attr('class', 'lineage-link');
+              r.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 p = points.length === 0 ? source : points[points.length - 1];
+
+                    var r = CIRCLE_RADIUS;
+                    var sx = p.x, sy = p.y, tx = target.x, ty = target.y;
+                    var l = Math.sqrt((tx - sx) * (tx - sx) + (ty - sy) * (ty - sy));
+                    var dx = r / l * (tx - sx), dy = r / l * (ty - sy);
+
+                    points.unshift({'x': source.x, 'y': source.y});
+                    points.push({'x': target.x - dx, 'y': target.y - dy});
+                    return LINE_FUNCTION(points);
+                  });
+            };
+          }
+
+          var node_map = {};
+          var g = new dagre.Digraph();
+          for (var k in data.nodes) {
+            var n = data.nodes[k];
+            g.addNode(n._id, { 'width': CIRCLE_RADIUS * 2 + LABEL_WIDTH, 'height': CIRCLE_RADIUS * 2 + LABEL_HEIGHT});
+            node_map[n._id] = n;
+          }
+
+          for (var k in data.edges) {
+            var e = data.edges[k];
+            var src = node_map[e._inV], dst = node_map[e._outV];
+            if (src !== undefined && dst !== undefined) {
+              g.addEdge(null, e._outV, e._inV);
+            }
+          }
+
+          var layout = dagre.layout().rankSep(RANK_SEPARATION).rankDir('LR').run(g);
+          layout.eachEdge(draw_edge(layout));
+          layout.eachNode(draw_node_functor(node_map));
+
+          function post_render() {
+            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: #ccc')
+                .append('svg:path')
+                .attr('d', 'M 0 0 L 10 5 L 0 10 z');
+          }
+
+          var bb = layout.graph();
+          $('#lineage-graph').attr('width', bb.width);
+          //$('#lineage-graph').attr('width', '100%');
+          $('#lineage-graph').attr('height', bb.height);
+          post_render();
+        }
+
+        function update_graph() {
+          process_queue(function() {
+            draw_graph();
+            var id = data.active_node_id;
+            if (id !== null) {
+              d3.select('.node[data-node-id="' + id + '"]').classed('node-active', true);
+            }
+          });
+        }
+
+        var loadLineageGraph = function(entityId, instance_name) {
+          var node_name = entityId + '/' + instance_name;
+          Falcon.logRequest();
+          Falcon.getInstanceVertices(node_name)
+              .success(function (resp) {
+                Falcon.logResponse('success', resp, false, true);
+                var n = resp.results[0];
+                data.queue = [{'id': n._id, 'depth': 1}];
+                data.nodes = {};
+                data.nodes[n._id] = n;
+                update_graph();
+              })
+              .error(function (err) {
+                Falcon.logResponse('error', err, false, true);
+              });
+        };
+
+        loadLineageGraph(scope.name, scope.instance);
+
+      }
+    };
+  }]);
+
+})();
\ No newline at end of file