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
