http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/controllers/root-controller.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/controllers/root-controller.js b/falcon-ui/app/js/controllers/root-controller.js index 709f6b2..b5dadaa 100644 --- a/falcon-ui/app/js/controllers/root-controller.js +++ b/falcon-ui/app/js/controllers/root-controller.js @@ -22,11 +22,11 @@ app.controller('RootCtrl', [ "$scope", "$timeout", "Falcon", "FileApi", "EntityModel", - "$state", "X2jsService", "ValidationService", "SpinnersFlag", "EntityFalcon", + "$state", "X2jsService", "ValidationService", "SpinnersFlag", "EntityFalcon", '$localStorage', function ($scope, $timeout, Falcon, FileApi, - EntityModel, $state, X2jsService, validationService, SpinnersFlag, EntityFalcon) { + EntityModel, $state, X2jsService, validationService, SpinnersFlag, EntityFalcon, $localStorage) { - var resultsPerPage = 10; + var resultsPerPage = 20; $scope.server = Falcon; $scope.validations = validationService; @@ -35,28 +35,28 @@ $scope.pages = []; $scope.nextPages = false; + $scope.hasClusters = true; $scope.handleFile = function (evt) { Falcon.logRequest(); FileApi.loadFile(evt).then(function () { if (EntityModel.type === 'Type not recognized') { - Falcon.logResponse('error', {status: 'ERROR', message:'Invalid xml. File not uploaded'}, false); + Falcon.logResponse('error', {status: 'ERROR', message:"Entity type not recognized"}, false); } else { - Falcon.postSubmitEntity(FileApi.fileRaw, EntityModel.type).success(function (response) { - Falcon.logResponse('success', response, false); - $scope.refreshList($scope.tags); - }).error(function (err) { - Falcon.logResponse('error', err, false); - }); + var entityType = EntityModel.type; + $state.go("forms." + entityType + ".general", {'action':'import'}, {reload: true}); } - }); }; - $scope.goPage = function(page){ - $scope.loading = true; + $scope.goPage = function(page, type){ + $scope.currentPage = page; + if (!(type && type == 'list')) { + $scope.loading = true; + } + var offset = (page-1) * resultsPerPage; - EntityFalcon.searchEntities($scope.entityName, $scope.entityTags, $scope.entityType, offset).then(function() { + return EntityFalcon.searchEntities($scope.entityName, $scope.entityTags, $scope.entityType, offset).then(function() { if (EntityFalcon.data !== null) { $scope.actualPage = page; $scope.searchList = EntityFalcon.data.entity; @@ -72,14 +72,16 @@ $scope.pages[i].enabled = true; } } - if($scope.searchList.length === 0){ + if($scope.searchList.length === 0 && !(type && type == 'list')){ Falcon.warningMessage("No results matched the search criteria."); } $timeout(function() { angular.element('#tagsInput').focus(); }, 0, false); Falcon.responses.listLoaded = true; - $scope.loading = false; + if (!(type && type == 'list')) { + $scope.loading = false; + } } }); }; @@ -128,13 +130,134 @@ }; $scope.cancel = function (type, state) { + var message = type + ' edition cancelled'; + if(type === 'cluster'){ + message = 'Create New Cluster operation cancelled' + } var cancelInfo = { state: state || $state.current.name, - message: type + ' edition canceled ' + message: message }; Falcon.logResponse('cancel', cancelInfo, type, false); }; + $scope.displayResults = function (remove) { + $state.go("main"); + $scope.refreshList($scope.tags); + (!remove) ? $scope.persistSearch($scope.tags) : ''; + + }; + + $scope.persistSearch = function(newTag){ + var storedTags = $localStorage['SearchedTag'], + flagExit = false, + newval = newTag[newTag.length -1].text; + + + if(storedTags !== undefined && newTag !== undefined && newTag.length > 0){ + + for(var t=0; t<storedTags.length; t++){ + if(storedTags[t].toLowerCase() === newval.toLowerCase()) { + flagExit = true; + break; + } + } + if(!flagExit ) { + storedTags.push(newval); + $localStorage['SearchedTag'] = storedTags; + } + + if(storedTags.length > 5) { storedTags.splice(0,1); $localStorage['SearchedTag'] = storedTags; } + + } else if(newTag !== undefined && newTag.length > 0) { + $localStorage['SearchedTag'] = [newval]; + } + + }; + + $scope.clearTags = function(){ + $scope.tags = []; + $scope.refreshList($scope.tags); + }; + +$scope.loadTags = function(query) { + var tags = new Array(), storedTags = $localStorage['SearchedTag'], tagAdded = false, tempTags = []; + if(!$scope.$parent.nameFounded){ + tags.push({ text: 'Name:' + query }); + tempTags.push('Name:' + query); + } + + if(storedTags != undefined && storedTags.length > 0){ + for(var e=0; e < storedTags.length; e++){ + if(storedTags[e].toLowerCase().indexOf(query.toLowerCase()) != -1){ + if(tempTags.indexOf(storedTags[e]) == -1) { + tags.push({ text: storedTags[e] }); + tempTags.push('Name:' + query); + tagAdded = true; + } + } + } + } + + + if(!$scope.$parent.typeFounded && !tagAdded){ + var queryAux = query.toUpperCase(); + if(queryAux === "F" || queryAux === "FE" || queryAux === "FEE" || queryAux === "FEED"){ + tags.push({ text: 'Type:feed'}); + } + if(queryAux === "P" || queryAux === "PR" || queryAux === "PRO" || queryAux === "PROC" || queryAux === "PROCE" + || queryAux === "PROCES" || queryAux === "PROCESS"){ + tags.push({ text: 'Type:process'}); + } + if(queryAux === "M" || queryAux === "MI" || queryAux === "MIR" || queryAux === "MIRR" || queryAux === "MIRRO" + || queryAux === "MIRROR"){ + tags.push({ text: 'Type:mirror'}); + } + } + if(query !== "*"){ + tags.push({ text: 'Tag:' + query }); + } + return tags; + }; + + $scope.clusterInterfaceLabels = function(interfaceType) { + switch (interfaceType) { + case "readonly": + return "File System Read Endpoint Address"; + case "write": + return "File System Default Address"; + case "execute": + return "Yarn Resource Manager Address"; + case "workflow": + return "Workflow Address"; + case "messaging": + return "Message Broker Address"; + case "registry": + return "Metadata Catalog Registry"; + case "spark": + return "Spark"; + default: + return ""; + } + }; + + $scope.displayEntities = function (type) { + $state.go("main", { 'fromAction' : 'listEntities'}); + $scope.entityType = type; + $scope.entityName = ''; + $scope.entityTags = ''; + $scope.goPage(1, 'list'); + }; + + $scope.feedPropertiesLabels = { + queueName: 'Queue Name', + jobPriority: 'Job Priority', + timeout: 'Timeout', + parallel: 'Parallel', + maxMaps: 'Max Maps', + mapBandwidthKB: 'Map Bandwidth KB' + }; + }]); }()); \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js b/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js new file mode 100644 index 0000000..3f047ce --- /dev/null +++ b/falcon-ui/app/js/controllers/snapshot/snapshot-controller.js @@ -0,0 +1,262 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + var snapshotModule = angular.module('app.controllers.snapshot', [ 'app.services' ]); + + snapshotModule.controller('SnapshotController', [ + "$scope", "$interval", "$controller", "Falcon", "EntityModel", "$state", "X2jsService", "DateHelper", + "RouteHelper", "ValidationService", "SpinnersFlag", "$timeout", "$rootScope", "clustersList", + "$cookieStore", "SnapshotModel", "EntityFactory", "ExtensionSerializer", + function ($scope, $interval, $controller, Falcon, EntityModel, $state, X2jsService, DateHelper, + RouteHelper, validationService, SpinnersFlag, $timeout, $rootScope, clustersList, + $cookieStore, snapshotModel, entityFactory, extensionSerializer) { + + var stateMatrix = { + general : {previous : '', next : 'summary'}, + summary : {previous : 'general', next : ''} + }; + + $scope.entityType = 'snapshot'; + $scope.skipUndo = false; + $scope.clusterErrorMessage = ''; + unwrapClusters(clustersList); + + //extending root controller + $controller('EntityRootCtrl', { + $scope: $scope + }); + + $scope.$on('$destroy', function() { + var defaultProcess = entityFactory.newEntity('snapshot'), + nameIsEqual = ($scope.snapshot.name == null || $scope.snapshot.name === ""), + ACLIsEqual = angular.equals($scope.snapshot.ACL, defaultProcess.ACL); + + if (!$scope.skipUndo && (!nameIsEqual || !ACLIsEqual)) { + $scope.$parent.models.snapshotModel = $scope.snapshot; + if ($scope.cloningMode) { + $scope.$parent.models.snapshotModel.clone = true; + } + if ($scope.editingMode) { + $scope.$parent.models.snapshotModel.edit = true; + } + $scope.$parent.cancel('snapshot', $rootScope.previousState); + } + }); + + $scope.isActive = function (route) { + return route === $state.current.name; + }; + + $scope.isCompleted = function (route) { + return $state.get(route).data && $state.get(route).data.completed; + }; + + $scope.loadOrCreateEntity = function() { + var type = $scope.entityType; + + if(!snapshotModel && $scope.$parent.models.snapshotModel) { + var snapshotObj = $scope.$parent.models.snapshotModel; + $scope.$parent.models.snapshotModel = null; + return snapshotObj; + } + $scope.$parent.models.snapshotModel = null; + return snapshotModel ? extensionSerializer.serializeExtensionModel(snapshotModel, 'snapshot') + : entityFactory.newEntity(type); + }; + + $scope.init = function() { + $scope.baseInit(); + var type = $scope.entityType; + $scope[type] = $scope.loadOrCreateEntity(); + if(snapshotModel && snapshotModel.clone === true) { + $scope.cloningMode = true; + $scope.editingMode = false; + $scope[type].name = ""; + } else if(snapshotModel && snapshotModel.edit === true) { + $scope.editingMode = true; + $scope.cloningMode = false; + } else{ + $scope.editingMode = false; + $scope.cloningMode = false; + } + console.log($scope.editingMode); + } + + $scope.init(); + + //----------------TAGS---------------------// + $scope.addTag = function () { + $scope.snapshot.tags.push({key: null, value: null}); + }; + + $scope.removeTag = function (index) { + if (index >= 0 && $scope.snapshot.tags.length > 1) { + $scope.snapshot.tags.splice(index, 1); + } + }; + + //----------- Alerts -----------// + $scope.addAlert = function () { + $scope.snapshot.alerts.push($scope.snapshot.alert.email); + $scope.snapshot.alert = {email: ""}; + }; + $scope.removeAlert = function (index) { + $scope.snapshot.alerts.splice(index, 1); + }; + + //----------------- DATE INPUTS -------------------// + $scope.dateFormat = 'MM/dd/yyyy'; + + $scope.openStartDatePicker = function ($event) { + $event.preventDefault(); + $event.stopPropagation(); + $scope.startOpened = true; + }; + + $scope.openEndDatePicker = function ($event) { + $event.preventDefault(); + $event.stopPropagation(); + $scope.endOpened = true; + }; + + $scope.constructDate = function () { + if ($scope.snapshot.validity.start && $scope.snapshot.validity.end + && $scope.snapshot.validity.startTime && $scope.snapshot.validity.endTime) { + $scope.snapshot.validity.startISO = DateHelper.createISO( + $scope.snapshot.validity.start, $scope.snapshot.validity.startTime, $scope.snapshot.validity.timezone); + $scope.snapshot.validity.endISO = DateHelper.createISO( + $scope.snapshot.validity.end, $scope.snapshot.validity.endTime, $scope.snapshot.validity.timezone); + } + }; + + $scope.$watch(function () { + return $scope.snapshot.validity.timezone; + }, function () { + return $scope.constructDate(); + }); + + $scope.validateCluster = function() { + if ($scope.snapshot.source.cluster === $scope.snapshot.target.cluster) { + $scope.clusterErrorMessage = 'Target cannot be the same as the Source'; + } else { + $scope.clusterErrorMessage = ''; + } + }; + + $scope.goNext = function (formInvalid) { + $state.current.data = $state.current.data || {}; + $state.current.data.completed = !formInvalid; + + SpinnersFlag.show = true; + if (!validationService.nameAvailable || formInvalid) { + validationService.displayValidations.show = true; + validationService.displayValidations.nameShow = true; + SpinnersFlag.show = false; + return; + } + if ($scope.clusterErrorMessage !== '') { + SpinnersFlag.show = false; + return; + } + validationService.displayValidations.show = false; + validationService.displayValidations.nameShow = false; + $state.go(RouteHelper.getNextState($state.current.name, stateMatrix)); + angular.element('body, html').animate({scrollTop: 0}, 500); + }; + + $scope.goBack = function () { + SpinnersFlag.backShow = true; + validationService.displayValidations.show = false; + validationService.displayValidations.nameShow = false; + $state.go(RouteHelper.getPreviousState($state.current.name, stateMatrix)); + angular.element('body, html').animate({scrollTop: 0}, 500); + }; + + $scope.save = function (formInvalid) { + SpinnersFlag.saveShow = true; + + $state.current.data = $state.current.data || {}; + $state.current.data.completed = !formInvalid; + if (!validationService.nameAvailable || formInvalid) { + validationService.displayValidations.show = true; + validationService.displayValidations.nameShow = true; + SpinnersFlag.saveShow = false; + return; + } + if ($scope.clusterErrorMessage !== '') { + SpinnersFlag.saveShow = false; + return; + } + validationService.displayValidations.show = false; + validationService.displayValidations.nameShow = false; + + var snapshotData = extensionSerializer.convertObjectToString( + extensionSerializer.serializeExtensionProperties($scope.snapshot, 'snapshot')); + + if($scope.editingMode) { + Falcon.postUpdateExtension(snapshotData, 'HDFS-SNAPSHOT-MIRRORING') + .success(function (response) { + $scope.skipUndo = true; + Falcon.logResponse('success', response, false); + SpinnersFlag.saveShow = false; + $state.go('main'); + + }) + .error(function (err) { + Falcon.logResponse('error', err, false); + SpinnersFlag.saveShow = false; + angular.element('body, html').animate({scrollTop: 0}, 300); + }); + } else { + Falcon.postSubmitExtension(snapshotData, 'HDFS-SNAPSHOT-MIRRORING') + .success(function (response) { + $scope.skipUndo = true; + Falcon.logResponse('success', response, false); + SpinnersFlag.saveShow = false; + $state.go('main'); + }) + .error(function (err) { + Falcon.logResponse('error', err, false); + SpinnersFlag.saveShow = false; + angular.element('body, html').animate({scrollTop: 0}, 300); + }); + } + }; + + function unwrapClusters(clusters) { + if(clusters !== undefined && clusters !== null && clusters !== "null"){ + $scope.clusterList = []; + var typeOfData = Object.prototype.toString.call(clusters.entity); + if(typeOfData === "[object Array]") { + $scope.clusterList = clusters.entity; + } else if(typeOfData === "[object Object]") { + $scope.clusterList = [clusters.entity]; + } else { + console.log("type of data not recognized"); + } + } + } + + if($state.current.name !== "forms.snapshot.general"){ + $state.go("forms.snapshot.general"); + } + + }]); +}()); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/controllers/themeController.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/controllers/themeController.js b/falcon-ui/app/js/controllers/themeController.js new file mode 100644 index 0000000..513b19e --- /dev/null +++ b/falcon-ui/app/js/controllers/themeController.js @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + var app = angular.module('app.controllers.theme', []); + + app.controller('ThemeController', ["$scope", function($scope) { + + $scope.theme = 'default'; + $scope.themeChange = function(theme) { + $scope.theme = theme; + }; + }]); + +}()); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/acl-permissions.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/acl-permissions.js b/falcon-ui/app/js/directives/acl-permissions.js new file mode 100644 index 0000000..98ef508 --- /dev/null +++ b/falcon-ui/app/js/directives/acl-permissions.js @@ -0,0 +1,103 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + var aclPermissionsModule = angular.module('app.directives.acl-permissions',[]); + + aclPermissionsModule.directive('aclPermissions', ['$timeout',function ($timeout) { + return { + restrict : 'EA', + templateUrl: 'html/directives/aclPermissions.html', + link: function ($scope, element, attrs) { + + var permission = { + 'owner' : 0, + 'groups' : 0, + 'others' : 0 + } + + function getNestedProperty(obj, path) { + var paths = path.split('.'), + current = obj; + for (var i = 0; i < paths.length; i++) { + if (current[paths[i]] == undefined) { + return undefined; + } else { + current = current[paths[i]]; + } + } + return current; + }; + + function setNestedProperty(obj, path, value) { + var paths = path.split('.'), + current = obj; + for (var i = 0; i < paths.length -1; i++) { + current = current[paths[i]]; + } + current[paths[paths.length - 1]] = value; + }; + + var defaultPermission = getNestedProperty($scope, attrs.aclModel); + + function setPermission() { + if(defaultPermission){ + var permissionArray = defaultPermission.substring(2).split(""); + permissionArray.forEach(function(value, index){ + var permissionValue = parseInt(value); + var type = Object.keys(permission)[index]; + permission[type] = value; + var checked = element.find('input[name=' + type + ']'); + var readPermission = 4, + writePermission = 2, + executePermission = 1; + if(permissionValue & readPermission){ + angular.element(checked[0]).attr('checked','checked'); + } + if(permissionValue & writePermission){ + angular.element(checked[1]).attr('checked','checked'); + } + if(permissionValue & executePermission){ + angular.element(checked[2]).attr('checked','checked'); + } + }); + } + }; + + $scope.calculatePermission = function(type){ + var checked = element.find('input[name=' + type + ']:checked'); + var total = 0; + angular.forEach(checked,function(element){ + total += parseInt(element.value); + }); + permission[type] = total; + var effectivePermission = '0x'; + angular.forEach(permission, function(value,key){ + effectivePermission = effectivePermission + value; + }); + setNestedProperty($scope, attrs.aclModel, effectivePermission); + }; + + $timeout(function () { + setPermission(); + }, 0); + } + }; + }]); +})(); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/chart.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/chart.js b/falcon-ui/app/js/directives/chart.js index f9d6983..f630a9f 100644 --- a/falcon-ui/app/js/directives/chart.js +++ b/falcon-ui/app/js/directives/chart.js @@ -453,7 +453,7 @@ }); - d3Module.controller('chartCtrl', [ "$scope", "Falcon", function($scope, Falcon) { + d3Module.controller('chartCtrl', [ "$scope", "Falcon", "DateHelper", function($scope, Falcon, DateHelper) { var formatFL = d3.time.format.utc("%A %d"), formatSL = d3.time.format.utc("%b %Y"), @@ -500,7 +500,7 @@ }; - $scope.dateFormat ='MM/dd/yyyy'; + $scope.dateFormat = DateHelper.getLocaleDateFormat(); $scope.openDatePicker = function($event) { $event.preventDefault(); $event.stopPropagation(); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/check-name.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/check-name.js b/falcon-ui/app/js/directives/check-name.js index bea0e22..edb3697 100644 --- a/falcon-ui/app/js/directives/check-name.js +++ b/falcon-ui/app/js/directives/check-name.js @@ -20,24 +20,28 @@ var checkNameModule = angular.module('app.directives.check-name', ['app.services.falcon', 'app.services.validation']); - checkNameModule.directive('checkName', [ "ValidationService", "$timeout", "Falcon", "EntityFalcon", function (validationService, $timeout, Falcon, EntityFalcon) { + checkNameModule.directive('checkName', [ "ValidationService", "$timeout", "Falcon", "EntityFalcon","$q", function (validationService, $timeout, Falcon, EntityFalcon,$q) { return { - replace: false, scope: { checkName: "=" }, - restrict: 'A', - link: function (scope, element) { - - var options = scope.checkName, - //entities = scope.$parent.lists[options.type + 'List'], - type = options.type, - name = element[0].value; - + require: ['ngModel','^form'], + link: function(scope, element, attrs, ctrls) { + var options = scope.checkName, + //entities = scope.$parent.lists[options.type + 'List'], + type = options.type, + name = element[0].value, + errorMsg, + ngModelCtrl = ctrls[0], + formCtrl = ctrls[1]; if (!options.check) { return; } + element.parent() + .append("<div class='nameInputDisplay hidden'>" + + "</div><label class='custom-danger nameValidationMessage'></label>"); + scope.$watch(function () { return element[0].value; }, function () { @@ -45,121 +49,81 @@ if (element[0].value.length === 0) { element.addClass('empty'); } - getNameAvailability(function() { - getMessage(); - if (element.hasClass('ng-valid') && validationService.nameAvailable) { - angular.element('.nameValidationMessage').addClass('hidden'); - } else { - element.parent().addClass("showValidationStyle"); - angular.element('.nameValidationMessage').removeClass('hidden'); - element.removeClass('empty'); - } - }); } - - }); - function getLabels() { - element.parent() - .append("<div class='nameInputDisplay hidden'>" + - "</div><label class='custom-danger nameValidationMessage'></label>"); - } - - function getNameAvailability(fn) { - name = element[0].value; - if (name.length === 0) { - angular.element('.nameInputDisplay').addClass('hidden'); - }else{ - Falcon.logRequest(); - Falcon.getEntityDefinition(type, name).success(function (data) { - Falcon.logResponse('success', data, false, true); - validationService.nameAvailable = false; - if (name.length === 0) { - angular.element('.nameInputDisplay').addClass('hidden'); - } else if (!validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) { - angular.element('.nameInputDisplay').html('Name unavailable') - .removeClass('custom-success hidden').addClass('custom-danger'); - } else if (validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) { - angular.element('.nameInputDisplay').html('Name available') - .removeClass('custom-danger hidden').addClass('custom-success'); - } else if (element.hasClass('ng-invalid-pattern') && name.length > 0) { - angular.element('.nameInputDisplay').addClass('hidden'); - } - if (fn) { fn(); } //>callback - }).error(function (err) { - Falcon.logResponse('error', err, false, true); - validationService.nameAvailable = true; - if (name.length === 0) { - angular.element('.nameInputDisplay').addClass('hidden'); - } else if (!validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) { - angular.element('.nameInputDisplay').html('Name unavailable') - .removeClass('custom-success hidden').addClass('custom-danger'); - } else if (validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) { - angular.element('.nameInputDisplay').html('Name available') - .removeClass('custom-danger hidden').addClass('custom-success'); - } else if (element.hasClass('ng-invalid-pattern') && name.length > 0) { - angular.element('.nameInputDisplay').addClass('hidden'); + scope.$watch(function(){ + return ngModelCtrl.$valid; + }, function(newValue, oldValue){ + if(ngModelCtrl.$dirty && newValue){ + errorMsg = "Name available"; + validationService.nameAvailable = true; + }else { + if(ngModelCtrl.$error.uniqueName){ + errorMsg = validationService.messages.name.unavailable; + validationService.nameAvailable = false; + }else if(ngModelCtrl.$error.required){ + errorMsg = validationService.messages.name.empty; + }else if(ngModelCtrl.$error.pattern){ + errorMsg = validationService.messages.name.patternInvalid; } - if (fn) { fn(); } //>callback - }); } + }); - } - - function getMessage() { - if (name.length === 0) { - element.addClass('empty'); - angular.element('.nameValidationMessage').html(validationService.messages.name.empty).addClass('hidden'); - - } else if (!validationService.nameAvailable && name.length > 0 && element.hasClass('ng-valid')) { - element.addClass('empty'); - angular.element('.nameValidationMessage') - .html(validationService.messages.name.unavailable).addClass('hidden'); - - } else if (element.hasClass('ng-invalid-pattern') && name.length > 0) { - element.removeClass('empty'); + scope.$watch(function(){ + return errorMsg; + },function(newValue, oldValue){ + if(newValue && newValue.length > 0){ element.parent().addClass("showValidationStyle"); - angular.element('.nameValidationMessage') - .html(validationService.messages.name.patternInvalid).removeClass('hidden'); - - } else if (element.hasClass('ng-valid') && name.length > 0) { + element.removeClass('empty'); + if(errorMsg ==='Name available'){ + angular.element('.nameInputDisplay').removeClass('custom-danger hidden').addClass('custom-success'); + angular.element('.nameInputDisplay').text(newValue); + angular.element('.nameValidationMessage').addClass("hidden"); + }else{ + angular.element('.nameValidationMessage').text(newValue); + angular.element('.nameValidationMessage').removeClass('hidden').addClass('custom custom-danger'); + angular.element('.nameInputDisplay').addClass("hidden"); + } + }else{ element.parent().removeClass("showValidationStyle"); - angular.element('.nameValidationMessage').addClass('hidden'); } - } - function addListeners() { - element.bind('keyup', function () { - getNameAvailability(); - getMessage(); - }); - element.bind('focus', function () { - element.removeClass('empty'); - }); - element.bind('blur', function () { - if (element.hasClass('ng-valid') && validationService.nameAvailable) { - angular.element('.nameValidationMessage').addClass('hidden'); + }); - } else { - element.parent().addClass("showValidationStyle"); - angular.element('.nameValidationMessage').removeClass('hidden'); - element.removeClass('empty'); - } + ngModelCtrl.$asyncValidators.uniqueName = function isNameAvailable(modelValue,viewValue) { + Falcon.logRequest(); + var def = $q.defer(); + Falcon.getEntityDefinition(type, modelValue).success(function (data) { + Falcon.logResponse('success', data, false, true); + def.reject(); + }).error(function (err) { + Falcon.logResponse('error', err, false, true); + def.resolve(); }); + return def.promise; } - function init() { - getLabels(); - addListeners(); - getNameAvailability(); - getMessage(); + element.bind('blur',function(event){ + if(ngModelCtrl.$valid){ + angular.element('.nameValidationMessage').addClass('hidden'); + }else{ + angular.element('.nameValidationMessage').removeClass('hidden'); + } + }); - $timeout(function () { element.trigger('focus'); }, 20); - } + element.bind('focus',function(event){ + if(formCtrl.$submitted && ngModelCtrl.$pristine && ngModelCtrl.$error.required){ + angular.element('.nameValidationMessage').removeClass('hidden'); + element.parent().addClass('showValidationStyle'); + }else{ + angular.element('.nameValidationMessage').addClass('hidden'); + element.parent().removeClass('showValidationStyle'); + } + }); - init(); + $timeout(function () { element.trigger('focus'); }, 0); } - }; + } }]); -}()); \ No newline at end of file +}()); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/dependencies-graph.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/dependencies-graph.js b/falcon-ui/app/js/directives/dependencies-graph.js index c8faa74..bd37358 100644 --- a/falcon-ui/app/js/directives/dependencies-graph.js +++ b/falcon-ui/app/js/directives/dependencies-graph.js @@ -18,17 +18,16 @@ (function () { 'use strict'; - var entitiesListModule = angular.module('app.directives.dependencies-graph', ['app.services' ]); + var entitiesListModule = angular.module('app.directives.dependencies-graph', ['app.services' ]); - entitiesListModule.controller('DependenciesGraphCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService', 'EntityModel', - function($scope, Falcon, X2jsService, $window, encodeService, EntityModel) { + entitiesListModule.controller('DependenciesGraphCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService', + function($scope, Falcon, X2jsService, $window, encodeService) { - }]); + }]); - entitiesListModule.directive('dependenciesGraph', ["$timeout", 'Falcon', '$filter', '$state', 'X2jsService', 'EntityModel', - function($timeout, Falcon, $filter, $state, X2jsService, EntityModel) { + entitiesListModule.directive('dependenciesGraph', ["$timeout", 'Falcon', '$filter', function($timeout, Falcon, $filter) { return { scope: { type: "=", @@ -49,16 +48,17 @@ return type + '/' + name; } - function getOrCreateNode(type, name) { + function getOrCreateNode(type, name, tag) { var k = key(type, name); if (nodes[k] !== undefined) return nodes[k]; var n = { - "id": next_node_id++, + "guid": next_node_id++, "type": type, "name": name, - "dependency": [] + "arrowDirections": tag, + "children": [] }; nodes[k] = n; return n; @@ -81,7 +81,8 @@ var l = data.entity.length; for (var i = 0; i < l; ++i) { var e = data.entity[i]; - var d = getOrCreateNode(e.type, e.name); + var tag = e.hasOwnProperty('tags') && e.tags !== null ? e.tags.tag : "Input"; + var d = getOrCreateNode(e.type, e.name, tag); var src = null, dst = null; if (d.type === "cluster") { src = node; dst = d; @@ -94,11 +95,10 @@ src = node; dst = d; } } - //console.log(src.name + '->' + dst.name); - src.dependency.push(dst.id); + src.children.push(d); } - done_callback(nodes); + done_callback(node); }) .error(function (err) { Falcon.logResponse('error', err, false, true); @@ -106,181 +106,141 @@ } function load() { - var n = getOrCreateNode(entity_type, entity_name); + var n = getOrCreateNode(entity_type, entity_name, "Input"); loadEntry(n); } load(); }; - var plotDependencyGraph = function(nodes, element) { - var NODE_WIDTH = 150; - var NODE_HEIGHT = 50; - var RECT_ROUND = 10; - var SEPARATION = 40; - var UNIVERSAL_SEP = 80; - - var svg = d3.select(element).append("svg"); - + var plotDependencyGraph = function(nodes, container) { + var element = d3.select(container.element), + width = Math.max(container.width, 960), + height = Math.max(container.height, 300); + + var margin = { + top: 30, + right: 30, + bottom: 30, + left: 80 + }; + width = width - margin.right - margin.left; + height = height - margin.top - margin.bottom; // Function to draw the lines of the edge - var LINE_FUNCTION = d3.svg.line() - .x(function(d) { return d.x; }) - .y(function(d) { return d.y; }) - .interpolate('basis'); - - // Mappining from id to a node - var node_by_id = {}; - - var layout = null; - - /** - * Calculate the intersection point between the point p and the edges of the rectangle rect - **/ - function intersectRect(rect, p) { - var cx = rect.x, cy = rect.y, dx = p.x - cx, dy = p.y - cy, w = rect.width / 2, h = rect.height / 2; - - if (dx == 0) - return { "x": p.x, "y": rect.y + (dy > 0 ? h : -h) }; - - var slope = dy / dx; - - var x0 = null, y0 = null; - if (Math.abs(slope) < rect.height / rect.width) { - // intersect with the left or right edges of the rect - x0 = rect.x + (dx > 0 ? w : -w); - y0 = cy + slope * (x0 - cx); - } else { - y0 = rect.y + (dy > 0 ? h : -h); - x0 = cx + (y0 - cy) / slope; - } - - return { "x": x0, "y": y0 }; - } - - function drawNode(u, value) { - var root = svg.append('g').classed('node', true) - .attr('transform', 'translate(' + -value.width/2 + ',' + -value.height/2 + ')'); - - var node = node_by_id[u]; - - + var i = 0; - var fo = root.append('foreignObject') - .attr('x', value.x) - .attr('y', value.y) - .attr('width', value.width) - .attr('height', value.height) - .attr('class', 'foreignObject'); - - - var txt = fo.append('xhtml:div') - .text(node.name) - .classed('node-name', true) - .classed('node-name-' + node.type, true); - - var rect = root.append('rect') - .attr('width', value.width) - .attr('height', value.height) - .attr('x', value.x) - .attr('y', value.y) - .attr('rx', RECT_ROUND) - .attr('ry', RECT_ROUND) - - .on('click', function () { - - Falcon.logRequest(); - Falcon.getEntityDefinition(node.type.toLowerCase(), node.name) - .success(function (data) { - Falcon.logResponse('success', data, false, true); - var entityModel = X2jsService.xml_str2json(data); - EntityModel.type = node.type.toLowerCase(); - EntityModel.name = node.name; - EntityModel.model = entityModel; - $state.go('entityDetails'); - }) - .error(function (err) { - Falcon.logResponse('error', err, false, false); - }); - + var tree = d3.layout.tree() + .size([height, width]); + var diagonal = d3.svg.diagonal() + .projection(function(d) { + return [d.y, d.x]; }); - } - - function drawEdge(e, u, v, value) { - var root = svg.append('g').classed('edge', true); - - root.append('path') - .attr('marker-end', 'url(#arrowhead)') - .attr('d', function() { - var points = value.points; - - var source = layout.node(u); - var target = layout.node(v); - - var p0 = points.length === 0 ? target : points[0]; - var p1 = points.length === 0 ? source : points[points.length - 1]; - - points.unshift(intersectRect(source, p0)); - points.push(intersectRect(target, p1)); - - return LINE_FUNCTION(points); + var svg = element.select('svg') + .attr('width', width + margin.right + margin.left) + .attr('height', height + margin.top + margin.bottom) + .select('g') + .attr('transform', + 'translate(' + margin.left + ',' + margin.right + ')'); + + svg.append("svg:defs").append("svg:marker").attr("id", "output-arrow").attr("viewBox", "0 0 10 10") + .attr("refX", 20).attr("refY", 5).attr("markerUnits", "strokeWidth").attr("markerWidth", 6) + .attr("markerHeight", 9).attr("orient", "auto").append("svg:path").attr("d", "M 0 0 L 10 5 L 0 10 z"); + + //marker for input type graph + svg.append("svg:defs") + .append("svg:marker") + .attr("id", "input-arrow") + .attr("viewBox", "0 0 10 10") + .attr("refX", -7) + .attr("refY", 5) + .attr("markerUnits", "strokeWidth") + .attr("markerWidth", 6) + .attr("markerHeight", 9) + .attr("orient", "auto") + .append("svg:path") + .attr("d", "M -2 5 L 8 0 L 8 10 z"); + + var root = nodes; + function update(source) { + + // Compute the new tree layout. + var nodes1 = tree.nodes(source).reverse(), + links = tree.links(nodes1); + // Normalize for fixed-depth. + nodes1.forEach(function(d) { + d.y = d.depth * 180; + }); + + // Declare the nodes� + var node = svg.selectAll('g.node') + .data(nodes1, function(d) { + + return d.id || (d.id = ++i); + }); + // Enter the nodes. + var nodeEnter = node.enter().append('g') + .attr('class', 'node') + .attr('transform', function(d) { + return 'translate(' + d.y + ',' + d.x + ')'; }); - } - function postRender() { - svg - .append('svg:defs') - .append('svg:marker') - .attr('id', 'arrowhead') - .attr('viewBox', '0 0 10 10') - .attr('refX', 8) - .attr('refY', 5) - .attr('markerUnits', 'strokewidth') - .attr('markerWidth', 8) - .attr('markerHeight', 5) - .attr('orient', 'auto') - .attr('style', 'fill: #333') - .append('svg:path') - .attr('d', 'M 0 0 L 10 5 L 0 10 z'); - } + nodeEnter.append("image") + .attr("xlink:href", function(d) { + //return d.icon; + return d.type === 'cluster' ? 'css/img/cloud.png' : 'css/img/feed.png'; + }) + .attr("x", "-18px") + .attr("y", "-18px") + .attr("width", "34px") + .attr("height", "34px"); + + nodeEnter.append('text') + .attr('x', function(d) { + return d.children || d._children ? + (5) * -1 : +15; + }) + .attr('dy', '-1.75em') + .attr('text-anchor', function(d) { + return d.children || d._children ? 'middle' : 'middle'; + }) + .text(function(d) { + return d.name; + }) - function plot() { - var g = new dagre.Digraph(); + .style('fill-opacity', 1); - for (var key in nodes) { - var n = nodes[key]; - node_by_id[n.id] = n; - g.addNode(n.id, { "width": NODE_WIDTH, "height": NODE_HEIGHT }); - } + // Declare the links� + var link = svg.selectAll('path.link') + .data(links, function(d) { + return d.target.id; + }); - for (var key in nodes) { - var n = nodes[key]; - for (var i = 0, l = n.dependency.length; i < l; ++i) { - var d = n.dependency[i]; - g.addEdge(null, n.id, d); - } - } + link.enter().insert('path', 'g') + .attr('class', 'link') + //.style('stroke', function(d) { return d.target.level; }) + .style('stroke', 'green') + .attr('d', diagonal); + link.attr("marker-start", function (d) { + if(d.target.arrowDirections==="Input") + return "url(#input-arrow)"; + }); //if input + link.attr("marker-end", function (d) { + if(d.target.arrowDirections==="Output") + return "url(#output-arrow)"; + }); //if outPut - layout = dagre.layout() - .universalSep(UNIVERSAL_SEP).rankSep(SEPARATION) - .run(g); - layout.eachEdge(drawEdge); - layout.eachNode(drawNode); + } - var graph = layout.graph(); + update(root); - svg.attr("width", graph.width); - svg.attr("height", graph.height); - postRender(); - } - plot(); }; var visualizeDependencyGraph = function(type, name) { loadDependencyGraph(type, name, function(nodes) { - plotDependencyGraph(nodes, element[0]); + plotDependencyGraph(nodes, {element:element[0], height:element[0].offsetHeight,width:element[0].offsetWidth}); }); }; http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/directives.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/directives.js b/falcon-ui/app/js/directives/directives.js index 9c388ea..e1366b6 100644 --- a/falcon-ui/app/js/directives/directives.js +++ b/falcon-ui/app/js/directives/directives.js @@ -29,9 +29,21 @@ 'app.directives.validation-message', 'chart-module', 'app.directives.dependencies-graph', - 'app.directives.lineage-graph' + 'app.directives.lineage-graph', + 'tooltip', + 'app.directives.feed-cluster-partitions', + 'app.directives.acl-permissions', + 'app.directives.interface-endpoint' ]); + directivesModule.directive('errorNav', function () { + return { + replace: false, + restrict: 'A', + templateUrl: 'html/error.html' + }; + }); + directivesModule.directive('navHeader', function () { return { replace: false, @@ -74,7 +86,7 @@ template: '{{output}}', link: function (scope) { if (scope.value.quantity) { - scope.output = scope.prefix + ' ' + scope.value.quantity + ' ' + scope.value.unit; + scope.output = (scope.prefix ? scope.prefix + ' ' : '') + scope.value.quantity + ' ' + scope.value.unit; } else { scope.output = 'Not specified'; } @@ -87,16 +99,21 @@ restrict: 'E', replace: false, scope: { - ngModel: '=' + ngModel: '=', + required: '=' }, templateUrl: 'html/directives/timeZoneSelectDv.html' }; }); - directivesModule.directive('simpleDate', ['$filter', function ($filter) { + directivesModule.directive('simpleDate', ['$filter','DateHelper', function ($filter, DateHelper) { return { require: 'ngModel', link: function (scope, element, attrs, ngModelController) { + var dateFormat = DateHelper.getLocaleDateFormat(); + + element.attr('title','Date should be entered in '+ dateFormat.toLowerCase() + ' format.'); + ngModelController.$parsers.push(function (data) { //convert data from view format to model format return data; @@ -104,7 +121,7 @@ ngModelController.$formatters.push(function (date) { //convert data from model format to view format if (date !== "") { - date = $filter('date')(date, 'MM/dd/yyyy'); + date = $filter('date')(date, dateFormat); } return date; }); @@ -135,6 +152,7 @@ resize(); }); var resize = function () { + element[0].style.resize = "vertical"; element[0].style.height = "250px"; return element[0].style.height = "" + element[0].scrollHeight + "px"; }; @@ -179,4 +197,259 @@ }; }]); -}()); \ No newline at end of file + directivesModule.directive('scrollToError', ['$timeout',function ($timeout) { + return { + require : "^form", + restrict : 'A', + link: function (scope, element,attrs,form) { + element.on('mousedown',function(event){ + event.preventDefault(); + }); + element.on('click', function () { + var formElement = angular.element('form[name="' + form.$name + '"]'); + var firstInvalid = formElement[0].querySelector('.ng-invalid'); + $timeout(function() { + if (firstInvalid) { + firstInvalid.blur(); + firstInvalid.focus(); + } + },0) + }); + } + }; + }]); + + directivesModule.directive('feedFormClusterDetails', function () { + return { + replace: false, + restrict: 'EA', + templateUrl: 'html/feed/feedFormClusterDetailsTpl.html', + link: function ($scope, $element) { + $scope.$on('forms.feed.clusters:submit', function() { + $scope.cluster.isAccordionOpened = $element.find('.ng-invalid').length > 0; + }); + } + }; + }); + + directivesModule.directive('feedFormDataSource', function () { + return { + replace: false, + restrict: 'EA', + templateUrl: 'html/feed/feedFormDataSourceTpl.html', + link: function ($scope, $element) { + if($scope.feed.dataTransferType === 'import'){ + $scope.dataSourceType = 'source'; + $scope.dataTransferAction = 'extract'; + if ($scope.feed.import === undefined) { + $scope.feed.import = { 'source' : { + 'extract' : {'type' : 'full', 'mergepolicy' : 'snapshot'}, 'columnsType' : 'all', 'fields' : {} } }; + } else { + if ($scope.feed.import.source.fields && $scope.feed.import.source.fields.exlcudes) { + $scope.feed.import.source.columnsType = 'exclude'; + } else if ($scope.feed.import.source.fields && $scope.feed.import.source.fields.includes) { + $scope.feed.import.source.columnsType = 'include'; + } else { + $scope.feed.import.source.columnsType = 'all'; + } + } + } else { + $scope.dataSourceType = 'target'; + $scope.dataTransferAction = 'load'; + if ($scope.feed.export === undefined) { + $scope.feed.export = { 'target' : { + 'load' : {'type' : 'updateonly' }, 'columnsType' : 'all', 'fields' : {} } }; + } else { + if ($scope.feed.export.target.fields && $scope.feed.export.target.fields.exlcudes) { + $scope.feed.export.target.columnsType = 'exclude'; + } else if ($scope.feed.export.target.fields && $scope.feed.export.target.fields.includes) { + $scope.feed.export.target.columnsType = 'include'; + } else { + $scope.feed.export.target.columnsType = 'all'; + } + } + } + } + }; + }); + + directivesModule.directive('feedFormHiveStorage', ['EntityFactory',function (entityFactory) { + return { + replace: false, + scope: { + storageInfo:"=", + add:"&", + show:'&', + toggleAdvancedOptions:'&', + openDatePicker:'&', + constructDate:'&', + reset:'&', + validations:'=', + required:'=' + }, + restrict: 'EA', + templateUrl: 'html/directives/feedFormHiveStorage.html', + link: function ($scope) { + $scope.valiationMessage= JSON.stringify($scope.validations.messages.number); + if($scope.storageInfo.type ==='target'){ + $scope.buttonText = 'Destination'; + }else{ + $scope.buttonText = 'Source' + } + if(!$scope.storageInfo.clusterStorage){ + $scope.cluster = entityFactory.newCluster($scope.storageInfo.type, 'hive', "", null); + }else{ + $scope.cluster = $scope.storageInfo.clusterStorage; + } + $scope.toggleAdvancedOptions = function(){ + $scope.showingAdvancedOptions = !$scope.showingAdvancedOptions; + }; + $scope.reset = function(){ + $scope.storageInfo.feedClusters.forEach(function (cluster, index) { + if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) { + $scope.storageInfo.feedClusters[index] + = entityFactory.newCluster($scope.storageInfo.type, 'hive', '', null); + } + }); + }; + $scope.checkDuplicateClusterOnTarget = function() { + if ($scope.cluster.type === 'target' + && $scope.cluster.name !== '' + && $scope.storageInfo.feedClusters.filter(function (cluster) { + return cluster.name === $scope.cluster.name && cluster.type === 'source'; + }).length > 0) { + return true; + } + return false; + }; + $scope.findClusterExists = function(newClusterName, newClusterType, clusterList) { + $scope.clusterExists = clusterList.filter(function (cluster) { + return cluster.name === newClusterName && cluster.type === newClusterType; + }).length > 1; + }; + $scope.addCluster = function(clusterDetails){ + var cluster = entityFactory.newCluster(clusterDetails.type, clusterDetails.dataTransferType, "", null); + $scope.storageInfo.feedClusters.unshift(cluster); + //$scope.add({value : clusterDetails}); + //$scope.reset(); + }; + $scope.deleteCluster = function() { + $scope.storageInfo.feedClusters.forEach(function (cluster, index) { + if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) { + $scope.storageInfo.feedClusters.splice(index, 1); + } + }); + }; + } + }; + }]); + + directivesModule.directive('feedFormHdfsStorage', ['EntityFactory',function (entityFactory) { + return { + replace: false, + scope: { + storageInfo:"=", + add:"&", + show:'&', + toggleAdvancedOptions:'&', + openDatePicker:'&', + constructDate:'&', + reset:'&', + validations:'=', + required:'=' + }, + restrict: 'EA', + templateUrl: 'html/directives/feedFormHdfsStorage.html', + require:"^form", + link: function ($scope, $element, $attrs, $form) { + $scope.valiationMessage= JSON.stringify($scope.validations.messages.number); + if($scope.storageInfo.type ==='target'){ + $scope.buttonText = 'Destination'; + }else{ + $scope.buttonText = 'Source' + } + if(!$scope.storageInfo.clusterStorage){ + $scope.cluster = entityFactory.newCluster($scope.storageInfo.type, 'hdfs', "", null); + }else{ + $scope.cluster = $scope.storageInfo.clusterStorage; + if (!$scope.cluster.storage.fileSystem) { + $scope.cluster.storage = { 'fileSystem' : entityFactory.newClusterFileSystem() }; + } + } + $scope.toggleAdvancedOptions = function(){ + $scope.showingAdvancedOptions = !$scope.showingAdvancedOptions; + }; + $scope.reset = function(){ + $scope.storageInfo.feedClusters.forEach(function (cluster, index) { + if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) { + $scope.storageInfo.feedClusters[index] + = entityFactory.newCluster($scope.storageInfo.type, 'hdfs', '', null); + } + }); + }; + $scope.checkDuplicateClusterOnTarget = function() { + if ($scope.cluster.type === 'target' + && $scope.cluster.name !== '' + && $scope.storageInfo.feedClusters.filter(function (cluster) { + return cluster.name === $scope.cluster.name && cluster.type === 'source'; + }).length > 0) { + return true; + } + return false; + }; + $scope.findClusterExists = function(newClusterName, newClusterType, clusterList) { + $scope.clusterExists = clusterList.filter(function (cluster) { + return cluster.name === newClusterName && cluster.type === newClusterType; + }).length > 1; + }; + $scope.addCluster = function(clusterDetails, feedForm){ + var cluster = entityFactory.newCluster(clusterDetails.type, clusterDetails.dataTransferType, "", null); + $scope.storageInfo.feedClusters.unshift(cluster); + //$scope.add({value : clusterDetails}); + //$scope.reset(); + }; + $scope.deleteCluster = function() { + $scope.storageInfo.feedClusters.forEach(function (cluster, index) { + if (cluster.name === $scope.cluster.name && cluster.type === $scope.cluster.type) { + $scope.storageInfo.feedClusters.splice(index, 1); + } + }); + }; + } + }; + }]); + + directivesModule.directive('simpleDatePicker', ['$filter','DateHelper', function ($filter, DateHelper) { + return { + require: 'ngModel', + link: function ($scope, $element, $attrs, ngModelController) { + $element.datepicker(); + + var dateFormat = DateHelper.getLocaleDateFormat(); + + $element.attr('title','Date should be entered in '+ dateFormat.toLowerCase() + ' format.'); + + ngModelController.$parsers.push(function (date) { + //convert data from view format to model format + return new Date(date); + }); + ngModelController.$formatters.push(function (date) { + //convert data from model format to view format + if (date !== "") { + date = $filter('date')(date, dateFormat); + } + return date; + }); + } + }; + }]); + + directivesModule.directive('mandatoryField', function () { + return { + replace: false, + restrict: 'E', + template: '<span>*</span>' + }; + }); + +}()); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/entities-search-list.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/entities-search-list.js b/falcon-ui/app/js/directives/entities-search-list.js index 3e76f63..9171fbd 100644 --- a/falcon-ui/app/js/directives/entities-search-list.js +++ b/falcon-ui/app/js/directives/entities-search-list.js @@ -20,8 +20,16 @@ var entitiesListModule = angular.module('app.directives.entities-search-list', ['app.services' ]); - entitiesListModule.controller('EntitiesSearchListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService', - function($scope, Falcon, X2jsService, $window, encodeService) { + entitiesListModule.controller('EntitiesSearchListCtrl', ['$scope', 'Falcon', 'X2jsService', '$window', 'EncodeService', '$rootScope', + function($scope, Falcon, X2jsService, $window, encodeService, $rootScope) { + + $scope.isSafeMode = function() { + return $rootScope.safeMode; + }; + + $scope.isSuperUser = function() { + return $rootScope.superUser; + }; $scope.downloadEntity = function(type, name) { Falcon.logRequest(); @@ -47,7 +55,7 @@ }; }); - entitiesListModule.directive('entitiesSearchList', ["$timeout", 'Falcon', function($timeout, Falcon) { + entitiesListModule.directive('entitiesSearchList', ["$timeout", 'Falcon', "$state", "$rootScope", function($timeout, Falcon, $state, $rootScope) { return { scope: { input: "=", @@ -78,7 +86,7 @@ }, true); scope.selectedRows = []; - scope.mirrorTag = "_falcon_mirroring_type"; + scope.mirrorTag = "_falcon_extension_name"; scope.checkedRow = function (name) { var isInArray = false; @@ -159,6 +167,17 @@ scope.selectedDisabledButtons = { schedule:true, suspend:true, resume:true }; } + var selectedClusterRows = scope.selectedRows.filter(function(entity) { + return entity.type == 'cluster' || entity.type == 'CLUSTER'; + }); + if (selectedClusterRows && selectedClusterRows.length > 0) { + scope.selectedDisabledButtons = { + schedule:true, + suspend:true, + resume:true + }; + } + if(scope.selectedRows.length === 0) { scope.selectedDisabledButtons = { schedule:true, @@ -166,6 +185,7 @@ resume:true }; } + }, 50); }; @@ -209,16 +229,48 @@ }; scope.scopeEdit = function () { - scope.edit(scope.selectedRows[0].type, scope.selectedRows[0].name); + var selectedRow = scope.selectedRows[0]; + if (selectedRow.type.toLowerCase() === 'cluster' && (!$rootScope.safeMode || !$rootScope.superUser)) { + return; + } + var state = 'forms.' + selectedRow.type.toLowerCase(); + var selectedEntity = scope.input.filter(function(value){ + return value.name === selectedRow.name; + }); + if(selectedRow.type.toLowerCase() === 'process' && selectedEntity[0].tags + && scope.isMirror(selectedEntity[0].tags.tag)){ + var mirrorType = scope.getMirrorType(selectedEntity[0].tags.tag); + if (mirrorType === 'hdfs-mirror' || mirrorType === 'hive-mirror') { + state = 'forms.dataset'; + } else { + state = 'forms.snapshot'; + } + } + $state.go(state, {'name' : selectedRow.name, 'action' : 'edit'}); }; + scope.scopeClone = function () { - scope.clone(scope.selectedRows[0].type, scope.selectedRows[0].name); + var selectedRow = scope.selectedRows[0]; + var state = 'forms.' + selectedRow.type.toLowerCase(); + var selectedEntity = scope.input.filter(function(value){ + return value.name === selectedRow.name; + }); + if(selectedRow.type.toLowerCase() === 'process' && selectedEntity[0].tags + && scope.isMirror(selectedEntity[0].tags.tag)){ + var mirrorType = scope.getMirrorType(selectedEntity[0].tags.tag); + if (mirrorType === 'hdfs-mirror' || mirrorType === 'hive-mirror') { + state = 'forms.dataset'; + } else { + state = 'forms.snapshot'; + } + } + $state.go(state, {'name' : selectedRow.name, 'action' : 'clone'}); }; scope.goEntityDefinition = function(name, type) { scope.entityDefinition(name, type); }; scope.goEntityDetails = function(name, type) { - scope.entityDetails(name, type); + $state.go('entityDetails',{'name' : name, 'type' : type}); }; scope.scopeRemove = function () { @@ -279,19 +331,31 @@ return flag; }; + scope.getMirrorType = function(tags) { + if (tags.indexOf('_falcon_extension_name=HDFS-MIRRORING') !== -1) { + return "hdfs-mirror"; + } else if (tags.indexOf('_falcon_extension_name=HDFS-SNAPSHOT-MIRRORING') !== -1) { + return "snapshot"; + } else if (tags.indexOf('_falcon_extension_name=HIVE-MIRRORING') !== -1) { + return "hive-mirror"; + } + }; + scope.displayIcon = function (type, tags) { if(type === "FEED"){ return "entypo download"; - }else if(type === "PROCESS" && scope.isMirror(tags)){ + } else if(type === "CLUSTER"){ + return "entypo archive"; + } else if(type === "PROCESS" && scope.isMirror(tags)){ return "glyphicon glyphicon-duplicate"; - }else{ + } else{ return "entypo cycle"; } }; scope.displayType = function (tag) { var tagKeyVal = tag.split("="); - if(tagKeyVal[0] === "_falcon_mirroring_type"){ + if(tagKeyVal[0] === "_falcon_extension_name"){ return tagKeyVal[1]; }else{ return ""; @@ -302,4 +366,4 @@ }; }]); -})(); \ No newline at end of file +})(); http://git-wip-us.apache.org/repos/asf/falcon/blob/76dc2e18/falcon-ui/app/js/directives/feed-cluster-partitions.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/feed-cluster-partitions.js b/falcon-ui/app/js/directives/feed-cluster-partitions.js new file mode 100644 index 0000000..8fadb90 --- /dev/null +++ b/falcon-ui/app/js/directives/feed-cluster-partitions.js @@ -0,0 +1,119 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + var module = angular.module('app.directives.feed-cluster-partitions', []); + + module.directive('feedFormClusterPartitions', function () { + return { + replace: false, + restrict: 'EA', + templateUrl: 'html/feed/feedFormClusterPartitionsTpl.html', + link: function ($scope, $element) { + function addFeedPartitionsIfNotExists(partitions, existingPartitions) { + var runningPartition; + for (var i=0;i<partitions.length;i++) { + runningPartition = partitions[i]; + if (runningPartition.trim() != "") { + var matchedPartitions = existingPartitions.filter(function(element) { + return element.partitionName === runningPartition; + }); + if (matchedPartitions.length < 1) { + existingPartitions.splice(i, 0, {"partitionName" : runningPartition, + "partitionText" : "", + "mapping" : "any"}); + } + } + } + } + + function removeClusterPartitionsIfNotExists(existingPartitions, partitions) { + var runningPartition; + var updatedExistingParititons = []; + for (var i=0;i<existingPartitions.length;i++) { + runningPartition = existingPartitions[i]; + var matchedPartitions = partitions.filter(function(element) { + return element === runningPartition.partitionName; + }); + if (matchedPartitions.length > 0) { + updatedExistingParititons.push(runningPartition); + } + } + return updatedExistingParititons; + } + + function createDefaultClusterPartitions() { + var currentPartitions = ($scope.feedPartitions.trim() === "") ? [] : $scope.feedPartitions.split(","); + addFeedPartitionsIfNotExists(currentPartitions, $scope.partitionList); + $scope.partitionList = removeClusterPartitionsIfNotExists($scope.partitionList, currentPartitions); + } + + function createSpecificClusterPartitions() { + var partitionExpressions = $scope.cluster.partition.split("/"); + var currentPartitions = ($scope.feedPartitions.trim() === "") ? [] : $scope.feedPartitions.split(","); + partitionExpressions.forEach(function(partitionExpression, index) { + $scope.partitionList.push({"partitionName" : currentPartitions[index], + "partitionText" : (partitionExpression == "*") ? "" : partitionExpression, + "mapping" : (partitionExpression == "*") ? "any" : "mappedPartition" + }); + }); + } + + $scope.preparePartitionExpression = function(partitionList) { + var partitionExpression = ""; + for(var i=0;i<partitionList.length;i++) { + var partition = partitionList[i]; + if (partition.mapping === 'any') { + partitionExpression = partitionExpression.concat("*"); + } else if (partition.mapping === 'mappedPartition') { + partitionExpression = partitionExpression.concat(partition.partitionText); + } + if (i < partitionList.length-1) { + partitionExpression = partitionExpression.concat("/"); + } + } + $scope.cluster.partition = partitionExpression; + } + + $scope.partitionList = []; + if ($scope.cluster.partition != undefined) { + $scope.selectPartition = true; + createSpecificClusterPartitions() + } else { + $scope.selectPartition = false; + createDefaultClusterPartitions(); + } + + $scope.$watch('selectPartition', function() { + if ($scope.selectPartition) { + $scope.preparePartitionExpression($scope.partitionList); + } else { + delete $scope.cluster.partition; + } + }); + + $scope.$on('feed:createClusterPartitions', function() { + createDefaultClusterPartitions(); + $scope.preparePartitionExpression($scope.partitionList); + }); + } + }; + }); + +})();
