diff --git a/Makefile b/Makefile index 2ae810c837..f556534aa6 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ OFFICIAL ?= no PACKER ?= packer GRUNT ?= $(shell [ -t 0 ] && echo "grunt" || echo "grunt --no-color") TESTEM ?= ./node_modules/.bin/testem +TESTEM_DEBUG_BROWSER ?= Chrome BROCCOLI_BIN ?= ./node_modules/.bin/broccoli MOCHA_BIN ?= ./node_modules/.bin/mocha NODE ?= node @@ -348,18 +349,29 @@ minjs: awx/ui/static node_modules clean-ui Brocfile.js testjs: UI_FLAGS=--node-tests --no-concat --no-styles $(EXTRA_UI_FLAGS) testjs: awx/ui/build_test node-tests -# Performs minified, compressed build to awx/ui/static and runs browsers tests with testem ci -testjs_ci: UI_FLAGS=--compress --no-docs --no-debug --browser-tests $(EXTRA_UI_FLAGS) -testjs_ci: awx/ui/static testem.yml browser-tests +# Performs nonminified, noncompressed build to awx/ui/static and runs browsers tests with testem ci +testjs_ci: UI_FLAGS=--no-styles --no-compress --browser-tests --no-node-tests --no-sourcemaps $(EXTRA_UI_FLAGS) +testjs_ci: awx/ui/static testem.yml browser-tests-ci + +# Performs nonminified, noncompressed build to awx/ui/static and runs browsers tests with testem ci in Chrome +testjs_debug: UI_FLAGS=--no-styles --no-compress --browser-tests --no-node-tests --no-sourcemaps $(EXTRA_UI_FLAGS) +testjs_debug: awx/ui/static testem.yml browser-tests-debug # Runs node tests via mocha without building node-tests: NODE_PATH=awx/ui/build_test $(MOCHA_BIN) --full-trace $(shell find awx/ui/build_test -name '*-test.js') $(MOCHA_FLAGS) -# Runs browser tests using settings from `testem.yml` -browser-tests: +# Runs browser tests on PhantomJS. Outputs the results in a consumable manner for Jenkins. +browser-tests-ci: PATH=./node_modules/.bin:$(PATH) $(TESTEM) ci --file testem.yml -p 7359 -R xunit +# Runs browser tests using settings from `testem.yml` you can pass in the browser you'd +# like to run the tests on (Defaults to Chrome, other options Safari, Firefox, and PhantomJS). +# If you want to run the tests in Node (which is the quickest, but also more difficult to debug), +# make sure to run the testjs/node-tests targets +browser-tests-debug: + PATH=./node_modules/.bin:$(PATH) $(TESTEM) --file testem.yml -l $(TESTEM_DEBUG_BROWSER) + # Check .js files for errors and lint jshint: node_modules Gruntfile.js $(GRUNT) $@ diff --git a/awx/ui/client/src/adhoc/adhoc.controller.js b/awx/ui/client/src/adhoc/adhoc.controller.js new file mode 100644 index 0000000000..bb78c5564a --- /dev/null +++ b/awx/ui/client/src/adhoc/adhoc.controller.js @@ -0,0 +1,317 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Adhoc + * @description This controller controls the adhoc form creation, command launching and navigating to standard out after command has been succesfully ran. +*/ +function adhocController($q, $scope, $rootScope, $location, $routeParams, + CheckPasswords, PromptForPasswords, CreateLaunchDialog, adhocForm, + GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices, + KindChange, LookUpInit, CredentialList, Empty, Wait) { + + ClearScope(); + + // this is done so that we can access private functions for testing, but + // we don't want to populate the "public" scope with these internal + // functions + var privateFn = {}; + this.privateFn = privateFn; + + // note: put any urls that the controller will use in here!!!! + privateFn.setAvailableUrls = function() { + return { + adhocUrl: GetBasePath('inventory') + id + '/ad_hoc_commands/', + inventoryUrl: GetBasePath('inventory') + id + '/', + machineCredentialUrl: GetBasePath('credentials') + '?kind=ssh' + }; + }; + + var id = $routeParams.inventory_id, + urls = privateFn.setAvailableUrls(), + hostPattern = $rootScope.hostPatterns || "all"; + + // set the default options for the selects of the adhoc form + privateFn.setFieldDefaults = function(verbosity_options, forks_default) { + var verbosity; + for (verbosity in verbosity_options) { + if (verbosity_options[verbosity].isDefault) { + $scope.verbosity = verbosity_options[verbosity]; + } + } + $("#forks-number").spinner("value", forks_default); + $scope.forks = forks_default; + }; + + // set when "working" starts and stops + privateFn.setLoadingStartStop = function() { + var asyncHelper = {}, + formReadyPromise = 0; + + Wait('start'); + + if (asyncHelper.removeChoicesReady) { + asyncHelper.removeChoicesReady(); + } + asyncHelper.removeChoicesReady = $scope.$on('adhocFormReady', + isFormDone); + + // check to see if all requests have completed + function isFormDone() { + formReadyPromise++; + + if (formReadyPromise === 2) { + privateFn.setFieldDefaults($scope.adhoc_verbosity_options, + $scope.forks_field.default); + Wait('stop'); + } + } + }; + + privateFn.getInventoryNameForBreadcrumbs = function(url) { + + Rest.setUrl(url); + var promise = Rest.get(); + promise.then(function (response) { + $scope.inv_name = response.data.name; + }); + promise.catch(function (response) { + ProcessErrors($rootScope, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get inventory name. GET returned status: ' + + response.status }); + $location.path("/inventories/"); + }); + return promise; + }; + + // set the arguments help to watch on change of the module + privateFn.instantiateArgumentHelp = function() { + $scope.$watch('module_name', function(val) { + if (val) { + // give the docs for the selected module in the popover + $scope.argsPopOver = '
These arguments are used with the ' + + 'specified module. You can find information about the ' + + val.value + ' module here.
'; + } else { + // no module selected + $scope.argsPopOver = "These arguments are used with the" + + " specified module.
"; + } + }, true); + + // initially set to the same as no module selected + $scope.argsPopOver = "These arguments are used with the " + + "specified module.
"; + }; + + // pre-populate host patterns from the inventory page and + // delete the value off of rootScope + privateFn.instantiateHostPatterns = function(hostPattern) { + $scope.limit = hostPattern; + $scope.providedHostPatterns = $scope.limit; + delete $rootScope.hostPatterns; + }; + + // call helpers to initialize lookup and select fields through get + // requests + privateFn.initializeFields = function(machineCredentialUrl, adhocUrl) { + // setup machine credential lookup + LookUpInit({ + url: machineCredentialUrl, + scope: $scope, + form: adhocForm, + current_item: (!Empty($scope.credential_id)) ? + $scope.credential_id : null, + list: CredentialList, + field: 'credential', + input_type: 'radio' + }); + + // setup module name select + GetChoices({ + scope: $scope, + url: adhocUrl, + field: 'module_name', + variable: 'adhoc_module_options', + callback: 'adhocFormReady' + }); + + // setup verbosity options select + GetChoices({ + scope: $scope, + url: adhocUrl, + field: 'verbosity', + variable: 'adhoc_verbosity_options', + callback: 'adhocFormReady' + }); + }; + + // instantiate all variables on scope for display in the partial + privateFn.initializeForm = function(id, urls, hostPattern) { + // inject the adhoc command form + GenerateForm.inject(adhocForm, + { mode: 'edit', related: true, scope: $scope }); + + // set when "working" starts and stops + privateFn.setLoadingStartStop(); + + // put the inventory id on scope for the partial to use + $scope.inv_id = id; + + // get the inventory name + privateFn.getInventoryNameForBreadcrumbs(urls.inventoryUrl); + + // set the arguments help to watch on change of the module + privateFn.instantiateArgumentHelp(); + + // pre-populate host patterns from the inventory page and + // delete the value off of rootScope + privateFn.instantiateHostPatterns(hostPattern); + + privateFn.initializeFields(urls.machineCredentialUrl, urls.adhocUrl); + }; + + privateFn.initializeForm(id, urls, hostPattern); + + // remove all data input into the form and reset the form back to defaults + $scope.formReset = function () { + GenerateForm.reset(); + + // pre-populate host patterns from the inventory page and + // delete the value off of rootScope + privateFn.instantiateHostPatterns($scope.providedHostPatterns); + + KindChange({ scope: $scope, form: adhocForm, reset: false }); + + // set the default options for the selects of the adhoc form + privateFn.setFieldDefaults($scope.adhoc_verbosity_options, + $scope.forks_default); + }; + + // launch the job with the provided form data + $scope.launchJob = function () { + var adhocUrl = GetBasePath('inventory') + $routeParams.inventory_id + + '/ad_hoc_commands/', fld, data={}, html; + + html = '