mirror of
https://github.com/ansible/awx.git
synced 2026-02-20 12:40:06 -03:30
update adhoc feature, add tests and modularize
This commit is contained in:
317
awx/ui/client/src/adhoc/adhoc.controller.js
Normal file
317
awx/ui/client/src/adhoc/adhoc.controller.js
Normal file
@@ -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 = '<p>These arguments are used with the ' +
|
||||||
|
'specified module. You can find information about the ' +
|
||||||
|
val.value + ' module <a ' +
|
||||||
|
'id=\"adhoc_module_arguments_docs_link_for_module_' +
|
||||||
|
val.value + '\" href=\"http://docs.ansible.com/' +
|
||||||
|
val.value + '_module.html\" ' +
|
||||||
|
'target=\"_blank\">here</a>.</p>';
|
||||||
|
} else {
|
||||||
|
// no module selected
|
||||||
|
$scope.argsPopOver = "<p>These arguments are used with the" +
|
||||||
|
" specified module.</p>";
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// initially set to the same as no module selected
|
||||||
|
$scope.argsPopOver = "<p>These arguments are used with the " +
|
||||||
|
"specified module.</p>";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 = '<form class="ng-valid ng-valid-required" ' +
|
||||||
|
'name="job_launch_form" id="job_launch_form" autocomplete="off" ' +
|
||||||
|
'nonvalidate>';
|
||||||
|
|
||||||
|
// stub the payload with defaults from DRF
|
||||||
|
data = {
|
||||||
|
"job_type": "run",
|
||||||
|
"limit": "",
|
||||||
|
"credential": null,
|
||||||
|
"module_name": "command",
|
||||||
|
"module_args": "",
|
||||||
|
"forks": 0,
|
||||||
|
"verbosity": 0,
|
||||||
|
"privilege_escalation": ""
|
||||||
|
};
|
||||||
|
|
||||||
|
GenerateForm.clearApiErrors();
|
||||||
|
|
||||||
|
// populate data with the relevant form values
|
||||||
|
for (fld in adhocForm.fields) {
|
||||||
|
if (adhocForm.fields[fld].type === 'select') {
|
||||||
|
data[fld] = $scope[fld].value;
|
||||||
|
} else {
|
||||||
|
data[fld] = $scope[fld];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Wait('start');
|
||||||
|
|
||||||
|
if ($scope.removeStartAdhocRun) {
|
||||||
|
$scope.removeStartAdhocRun();
|
||||||
|
}
|
||||||
|
$scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() {
|
||||||
|
var password;
|
||||||
|
for (password in $scope.passwords) {
|
||||||
|
data[$scope.passwords[password]] = $scope[
|
||||||
|
$scope.passwords[password]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Launch the adhoc job
|
||||||
|
Rest.setUrl(GetBasePath('inventory') +
|
||||||
|
$routeParams.inventory_id + '/ad_hoc_commands/');
|
||||||
|
Rest.post(data)
|
||||||
|
.success(function (data) {
|
||||||
|
Wait('stop');
|
||||||
|
$location.path("/ad_hoc_commands/" + data.id);
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($scope, data, status, adhocForm, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to launch adhoc command. POST ' +
|
||||||
|
'returned status: ' + status });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeCreateLaunchDialog) {
|
||||||
|
$scope.removeCreateLaunchDialog();
|
||||||
|
}
|
||||||
|
$scope.removeCreateLaunchDialog = $scope.$on('CreateLaunchDialog',
|
||||||
|
function(e, html, url) {
|
||||||
|
CreateLaunchDialog({
|
||||||
|
scope: $scope,
|
||||||
|
html: html,
|
||||||
|
url: url,
|
||||||
|
callback: 'StartAdhocRun'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removePromptForPasswords) {
|
||||||
|
$scope.removePromptForPasswords();
|
||||||
|
}
|
||||||
|
$scope.removePromptForPasswords = $scope.$on('PromptForPasswords',
|
||||||
|
function(e, passwords_needed_to_start,html, url) {
|
||||||
|
PromptForPasswords({ scope: $scope,
|
||||||
|
passwords: passwords_needed_to_start,
|
||||||
|
callback: 'CreateLaunchDialog',
|
||||||
|
html: html,
|
||||||
|
url: url
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeContinueCred) {
|
||||||
|
$scope.removeContinueCred();
|
||||||
|
}
|
||||||
|
$scope.removeContinueCred = $scope.$on('ContinueCred', function(e,
|
||||||
|
passwords) {
|
||||||
|
if(passwords.length>0){
|
||||||
|
$scope.passwords_needed_to_start = passwords;
|
||||||
|
// only go through the password prompting steps if there are
|
||||||
|
// passwords to prompt for
|
||||||
|
$scope.$emit('PromptForPasswords', passwords, html, adhocUrl);
|
||||||
|
} else {
|
||||||
|
// if not, go straight to trying to run the job.
|
||||||
|
$scope.$emit('StartAdhocRun', adhocUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// start adhoc launching routine
|
||||||
|
CheckPasswords({
|
||||||
|
scope: $scope,
|
||||||
|
credential: $scope.credential,
|
||||||
|
callback: 'ContinueCred'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ['$q', '$scope', '$rootScope', '$location', '$routeParams',
|
||||||
|
'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'adhocForm',
|
||||||
|
'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
|
||||||
|
'GetChoices', 'KindChange', 'LookUpInit', 'CredentialList', 'Empty', 'Wait',
|
||||||
|
adhocController];
|
||||||
143
awx/ui/client/src/adhoc/adhoc.form.js
Normal file
143
awx/ui/client/src/adhoc/adhoc.form.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name forms.function:Adhoc
|
||||||
|
* @description This form is for executing an adhoc command
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function() {
|
||||||
|
return {
|
||||||
|
editTitle: 'Execute Command',
|
||||||
|
name: 'adhoc',
|
||||||
|
well: true,
|
||||||
|
forceListeners: true,
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
module_name: {
|
||||||
|
label: 'Module',
|
||||||
|
excludeModal: true,
|
||||||
|
type: 'select',
|
||||||
|
ngOptions: 'module.label for module in adhoc_module_options' +
|
||||||
|
' track by module.value',
|
||||||
|
ngChange: 'moduleChange()',
|
||||||
|
editRequired: true,
|
||||||
|
awPopOver:'<p>These are the modules that Tower supports ' +
|
||||||
|
'running commands against.',
|
||||||
|
dataTitle: 'Module',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body'
|
||||||
|
},
|
||||||
|
module_args: {
|
||||||
|
label: 'Arguments',
|
||||||
|
type: 'text',
|
||||||
|
awPopOverWatch: 'argsPopOver',
|
||||||
|
awPopOver: '{{ argsPopOver }}',
|
||||||
|
dataTitle: 'Arguments',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body',
|
||||||
|
editRequired: false,
|
||||||
|
autocomplete: false
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
label: 'Host Pattern',
|
||||||
|
type: 'text',
|
||||||
|
addRequired: false,
|
||||||
|
editRequired: false,
|
||||||
|
awPopOver: '<p>The pattern used to target hosts in the ' +
|
||||||
|
'inventory. Leaving the field blank, all, and * will ' +
|
||||||
|
'all target all hosts in the inventory. You can find ' +
|
||||||
|
'more information about Ansible\'s host patterns ' +
|
||||||
|
'<a id=\"adhoc_form_hostpatterns_doc_link\"' +
|
||||||
|
'href=\"http://docs.ansible.com/intro_patterns.html\" ' +
|
||||||
|
'target=\"_blank\">here</a>.</p>',
|
||||||
|
dataTitle: 'Host Pattern',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body'
|
||||||
|
},
|
||||||
|
credential: {
|
||||||
|
label: 'Machine Credential',
|
||||||
|
type: 'lookup',
|
||||||
|
sourceModel: 'credential',
|
||||||
|
sourceField: 'name',
|
||||||
|
ngClick: 'lookUpCredential()',
|
||||||
|
class: 'squeeze',
|
||||||
|
awPopOver: '<p>Select the credential you want to use when ' +
|
||||||
|
'accessing the remote hosts to run the command. ' +
|
||||||
|
'Choose the credential containing ' +
|
||||||
|
'the username and SSH key or password that Ansbile ' +
|
||||||
|
'will need to log into the remote hosts.</p>',
|
||||||
|
dataTitle: 'Credential',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body',
|
||||||
|
awRequiredWhen: {
|
||||||
|
variable: 'credRequired',
|
||||||
|
init: 'false'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
become_enabled: {
|
||||||
|
label: 'Enable Privilege Escalation',
|
||||||
|
type: 'checkbox',
|
||||||
|
addRequired: false,
|
||||||
|
editRequired: false,
|
||||||
|
column: 2,
|
||||||
|
awPopOver: "<p>If enabled, run this playbook as an administrator. This is the equivalent of passing the<code> --become</code> option to the <code> ansible</code> command. </p>",
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataTitle: 'Become Privilege Escalation',
|
||||||
|
dataContainer: "body"
|
||||||
|
},
|
||||||
|
verbosity: {
|
||||||
|
label: 'Verbosity',
|
||||||
|
excludeModal: true,
|
||||||
|
type: 'select',
|
||||||
|
ngOptions: 'verbosity.label for verbosity in ' +
|
||||||
|
'adhoc_verbosity_options ' +
|
||||||
|
'track by verbosity.value',
|
||||||
|
editRequired: true,
|
||||||
|
awPopOver:'<p>These are the verbosity levels for standard ' +
|
||||||
|
'out of the command run that are supported.',
|
||||||
|
dataTitle: 'Module',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body',
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
forks: {
|
||||||
|
label: 'Forks',
|
||||||
|
id: 'forks-number',
|
||||||
|
type: 'number',
|
||||||
|
integer: true,
|
||||||
|
min: 0,
|
||||||
|
spinner: true,
|
||||||
|
"default": 0,
|
||||||
|
addRequired: false,
|
||||||
|
editRequired: true,
|
||||||
|
'class': "input-small",
|
||||||
|
column: 1,
|
||||||
|
awPopOver: '<p>The number of parallel or simultaneous processes to use while executing the command. 0 signifies ' +
|
||||||
|
'the default value from the <a id="ansible_forks_docs" href=\"http://docs.ansible.com/intro_configuration.html#the-ansible-configuration-file\" ' +
|
||||||
|
' target=\"_blank\">ansible configuration file</a>.</p>',
|
||||||
|
dataTitle: 'Forks',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: "body"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
buttons: {
|
||||||
|
launch: {
|
||||||
|
label: 'Launch',
|
||||||
|
ngClick: 'launchJob()',
|
||||||
|
ngDisabled: true
|
||||||
|
},
|
||||||
|
reset: {
|
||||||
|
ngClick: 'formReset()',
|
||||||
|
ngDisabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
related: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
19
awx/ui/client/src/adhoc/adhoc.route.js
Normal file
19
awx/ui/client/src/adhoc/adhoc.route.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../shared/template-url/template-url.factory';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
route: '/inventories/:inventory_id/adhoc',
|
||||||
|
name: 'inventoryAdhoc',
|
||||||
|
templateUrl: templateUrl('adhoc/adhoc'),
|
||||||
|
controller: 'adhocController',
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
12
awx/ui/client/src/adhoc/main.js
Normal file
12
awx/ui/client/src/adhoc/main.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import route from './adhoc.route';
|
||||||
|
import adhocController from './adhoc.controller';
|
||||||
|
import form from './adhoc.form';
|
||||||
|
|
||||||
|
export default angular.module('adhoc', ["ngRoute"])
|
||||||
|
.controller('adhocController', adhocController)
|
||||||
|
.config(['$routeProvider', function($routeProvider) {
|
||||||
|
var url = route.route;
|
||||||
|
delete route.route;
|
||||||
|
$routeProvider.when(url, route);
|
||||||
|
}])
|
||||||
|
.factory('adhocForm', form);
|
||||||
@@ -43,6 +43,7 @@ import browserData from './browser-data/main';
|
|||||||
import dashboard from './dashboard/main';
|
import dashboard from './dashboard/main';
|
||||||
import moment from './shared/moment/main';
|
import moment from './shared/moment/main';
|
||||||
import templateUrl from './shared/template-url/main';
|
import templateUrl from './shared/template-url/main';
|
||||||
|
import adhoc from './adhoc/main';
|
||||||
|
|
||||||
import {JobDetailController} from './controllers/JobDetail';
|
import {JobDetailController} from './controllers/JobDetail';
|
||||||
import {JobStdoutController} from './controllers/JobStdout';
|
import {JobStdoutController} from './controllers/JobStdout';
|
||||||
@@ -52,7 +53,6 @@ import {ScheduleEditController} from './controllers/Schedules';
|
|||||||
import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
|
import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
|
||||||
import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from './controllers/Organizations';
|
import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from './controllers/Organizations';
|
||||||
import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from './controllers/Inventories';
|
import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from './controllers/Inventories';
|
||||||
import {AdhocCtrl} from './controllers/Adhoc';
|
|
||||||
import {AdminsList} from './controllers/Admins';
|
import {AdminsList} from './controllers/Admins';
|
||||||
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
|
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
|
||||||
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
|
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
|
||||||
@@ -93,6 +93,7 @@ var tower = angular.module('Tower', [
|
|||||||
dashboard.name,
|
dashboard.name,
|
||||||
moment.name,
|
moment.name,
|
||||||
templateUrl.name,
|
templateUrl.name,
|
||||||
|
adhoc.name,
|
||||||
'templates',
|
'templates',
|
||||||
'AuthService',
|
'AuthService',
|
||||||
'Utilities',
|
'Utilities',
|
||||||
@@ -112,7 +113,6 @@ var tower = angular.module('Tower', [
|
|||||||
'RefreshHelper',
|
'RefreshHelper',
|
||||||
'AdminListDefinition',
|
'AdminListDefinition',
|
||||||
'AWDirectives',
|
'AWDirectives',
|
||||||
'AdhocFormDefinition',
|
|
||||||
'InventoriesListDefinition',
|
'InventoriesListDefinition',
|
||||||
'InventoryFormDefinition',
|
'InventoryFormDefinition',
|
||||||
'InventoryHelper',
|
'InventoryHelper',
|
||||||
@@ -182,9 +182,6 @@ var tower = angular.module('Tower', [
|
|||||||
'SocketHelper',
|
'SocketHelper',
|
||||||
'AboutAnsibleHelpModal',
|
'AboutAnsibleHelpModal',
|
||||||
'PortalJobsListDefinition',
|
'PortalJobsListDefinition',
|
||||||
|
|
||||||
|
|
||||||
'AdhocHelper',
|
|
||||||
'features',
|
'features',
|
||||||
'longDateFilter'
|
'longDateFilter'
|
||||||
])
|
])
|
||||||
@@ -468,17 +465,6 @@ var tower = angular.module('Tower', [
|
|||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
when('/inventories/:inventory_id/adhoc', {
|
|
||||||
name: 'inventoryAdhoc',
|
|
||||||
templateUrl: urlPrefix + 'partials/adhoc.html',
|
|
||||||
controller: AdhocCtrl,
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
when('/organizations', {
|
when('/organizations', {
|
||||||
name: 'organizations',
|
name: 'organizations',
|
||||||
templateUrl: urlPrefix + 'partials/organizations.html',
|
templateUrl: urlPrefix + 'partials/organizations.html',
|
||||||
|
|||||||
@@ -1,248 +0,0 @@
|
|||||||
/*************************************************
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
export function AdhocCtrl($scope, $rootScope, $location, $routeParams,
|
|
||||||
CheckPasswords, PromptForPasswords, CreateLaunchDialog, AdhocForm, GenerateForm, Rest, ProcessErrors, ClearScope,
|
|
||||||
GetBasePath, GetChoices, KindChange, LookUpInit, CredentialList, Empty,
|
|
||||||
Wait) {
|
|
||||||
|
|
||||||
ClearScope();
|
|
||||||
|
|
||||||
var url = GetBasePath('inventory') + $routeParams.inventory_id +
|
|
||||||
'/ad_hoc_commands/',
|
|
||||||
generator = GenerateForm,
|
|
||||||
form = AdhocForm,
|
|
||||||
master = {},
|
|
||||||
id = $routeParams.inventory_id,
|
|
||||||
choicesReadyCount = 0,
|
|
||||||
data;
|
|
||||||
|
|
||||||
$scope.inv_id = id;
|
|
||||||
|
|
||||||
Rest.setUrl(GetBasePath('inventory') + $routeParams.inventory_id + '/');
|
|
||||||
Rest.get(data)
|
|
||||||
.success(function (data) {
|
|
||||||
$scope.inv_name = data.name;
|
|
||||||
})
|
|
||||||
.error(function (data, status) {
|
|
||||||
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
|
|
||||||
msg: 'Failed to get inventory name. POST returned ' +
|
|
||||||
'status: ' + status });
|
|
||||||
});
|
|
||||||
|
|
||||||
// inject the adhoc command form
|
|
||||||
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
|
||||||
generator.reset();
|
|
||||||
|
|
||||||
// BEGIN: populate scope with the things needed to make the adhoc form
|
|
||||||
// display
|
|
||||||
Wait('start');
|
|
||||||
$scope.id = id;
|
|
||||||
$scope.argsPopOver = "<p>These arguments are used with the" +
|
|
||||||
" specified module.</p>";
|
|
||||||
|
|
||||||
// fix arguments help popover based on the module selected
|
|
||||||
$scope.moduleChange = function () {
|
|
||||||
// NOTE: for selenium testing link -
|
|
||||||
// link will be displayed with id adhoc_module_arguments_docs_link
|
|
||||||
// only when a module is selected
|
|
||||||
if ($scope.module_name) {
|
|
||||||
// give the docs for the selected module
|
|
||||||
$scope.argsPopOver = "<p>These arguments are used with the" +
|
|
||||||
" specified module. You can find information about the " +
|
|
||||||
$scope.module_name.value + " module " +
|
|
||||||
" <a id=\"adhoc_module_arguments_docs_link_for_module_" +
|
|
||||||
$scope.module_name.value +
|
|
||||||
"\"" +
|
|
||||||
" href=\"http://docs.ansible.com/" + $scope.module_name.value +
|
|
||||||
"_module.html\" target=\"_blank\">here</a>.</p>";
|
|
||||||
} else {
|
|
||||||
// no module selected
|
|
||||||
$scope.argsPopOver = "<p>These arguments are used with the" +
|
|
||||||
" specified module.</p>";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// pre-populate hostPatterns from the inventory page and
|
|
||||||
// delete the value off of rootScope
|
|
||||||
$scope.limit = $rootScope.hostPatterns || "all";
|
|
||||||
$scope.providedHostPatterns = $scope.limit;
|
|
||||||
delete $rootScope.hostPatterns;
|
|
||||||
|
|
||||||
LookUpInit({
|
|
||||||
url: GetBasePath('credentials') + '?kind=ssh',
|
|
||||||
scope: $scope,
|
|
||||||
form: form,
|
|
||||||
current_item: (!Empty($scope.credential_id)) ? $scope.credential_id : null,
|
|
||||||
list: CredentialList,
|
|
||||||
field: 'credential',
|
|
||||||
input_type: 'radio'
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($scope.removeChoicesReady) {
|
|
||||||
$scope.removeChoicesReady();
|
|
||||||
}
|
|
||||||
$scope.removeChoicesReady = $scope.$on('choicesReadyAdhoc', function () {
|
|
||||||
choicesReadyCount++;
|
|
||||||
|
|
||||||
if (choicesReadyCount === 2) {
|
|
||||||
var verbosity;
|
|
||||||
// this sets the default options for the selects as specified by the controller.
|
|
||||||
for (verbosity in $scope.adhoc_verbosity_options) {
|
|
||||||
if ($scope.adhoc_verbosity_options[verbosity].isDefault) {
|
|
||||||
$scope.verbosity = $scope.adhoc_verbosity_options[verbosity];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$("#forks-number").spinner("value", $scope.forks_field.default);
|
|
||||||
$scope.forks = $scope.forks_field.default;
|
|
||||||
Wait('stop'); // END: form population
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// setup Machine Credential lookup
|
|
||||||
GetChoices({
|
|
||||||
scope: $scope,
|
|
||||||
url: url,
|
|
||||||
field: 'module_name',
|
|
||||||
variable: 'adhoc_module_options',
|
|
||||||
callback: 'choicesReadyAdhoc'
|
|
||||||
});
|
|
||||||
|
|
||||||
// setup verbosity options lookup
|
|
||||||
GetChoices({
|
|
||||||
scope: $scope,
|
|
||||||
url: url,
|
|
||||||
field: 'verbosity',
|
|
||||||
variable: 'adhoc_verbosity_options',
|
|
||||||
callback: 'choicesReadyAdhoc'
|
|
||||||
});
|
|
||||||
|
|
||||||
// launch the job with the provided form data
|
|
||||||
$scope.launchJob = function () {
|
|
||||||
var fld, data={}, html;
|
|
||||||
|
|
||||||
html = '<form class="ng-valid ng-valid-required" name="job_launch_form"' +
|
|
||||||
'id="job_launch_form" autocomplete="off" nonvalidate>';
|
|
||||||
|
|
||||||
// stub the payload with defaults from DRF
|
|
||||||
data = {
|
|
||||||
"job_type": "run",
|
|
||||||
"limit": "",
|
|
||||||
"credential": null,
|
|
||||||
"module_name": "command",
|
|
||||||
"module_args": "",
|
|
||||||
"forks": 0,
|
|
||||||
"verbosity": 0,
|
|
||||||
"privilege_escalation": ""
|
|
||||||
};
|
|
||||||
|
|
||||||
generator.clearApiErrors();
|
|
||||||
|
|
||||||
// populate data with the relevant form values
|
|
||||||
for (fld in form.fields) {
|
|
||||||
if (form.fields[fld].type === 'select') {
|
|
||||||
data[fld] = $scope[fld].value;
|
|
||||||
} else {
|
|
||||||
data[fld] = $scope[fld];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wait('start');
|
|
||||||
|
|
||||||
if ($scope.removeStartAdhocRun) {
|
|
||||||
$scope.removeStartAdhocRun();
|
|
||||||
}
|
|
||||||
$scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() {
|
|
||||||
var password;
|
|
||||||
for (password in $scope.passwords) {
|
|
||||||
data[$scope.passwords[password]] = $scope[$scope.passwords[password]];
|
|
||||||
}
|
|
||||||
// Launch the adhoc job
|
|
||||||
Rest.setUrl(GetBasePath('inventory') +
|
|
||||||
$routeParams.inventory_id + '/ad_hoc_commands/');
|
|
||||||
Rest.post(data)
|
|
||||||
.success(function (data) {
|
|
||||||
Wait('stop');
|
|
||||||
$location.path("/ad_hoc_commands/" + data.id);
|
|
||||||
})
|
|
||||||
.error(function (data, status) {
|
|
||||||
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
|
|
||||||
msg: 'Failed to launch adhoc command. POST returned ' +
|
|
||||||
'status: ' + status });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($scope.removeCreateLaunchDialog) {
|
|
||||||
$scope.removeCreateLaunchDialog();
|
|
||||||
}
|
|
||||||
$scope.removeCreateLaunchDialog = $scope.$on('CreateLaunchDialog',
|
|
||||||
function(e, html, url) {
|
|
||||||
CreateLaunchDialog({
|
|
||||||
scope: $scope,
|
|
||||||
html: html,
|
|
||||||
url: url,
|
|
||||||
callback: 'StartAdhocRun'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($scope.removePromptForPasswords) {
|
|
||||||
$scope.removePromptForPasswords();
|
|
||||||
}
|
|
||||||
$scope.removePromptForPasswords = $scope.$on('PromptForPasswords', function(e, passwords_needed_to_start,html, url) {
|
|
||||||
PromptForPasswords({ scope: $scope,
|
|
||||||
passwords: passwords_needed_to_start,
|
|
||||||
callback: 'CreateLaunchDialog',
|
|
||||||
html: html,
|
|
||||||
url: url
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($scope.removeContinueCred) {
|
|
||||||
$scope.removeContinueCred();
|
|
||||||
}
|
|
||||||
$scope.removeContinueCred = $scope.$on('ContinueCred', function(e, passwords) {
|
|
||||||
if(passwords.length>0){
|
|
||||||
$scope.passwords_needed_to_start = passwords;
|
|
||||||
// only go through the password prompting steps if there are
|
|
||||||
// passwords to prompt for
|
|
||||||
$scope.$emit('PromptForPasswords', passwords, html, url);
|
|
||||||
} else {
|
|
||||||
// if not, go straight to trying to run the job.
|
|
||||||
$scope.$emit('StartAdhocRun', url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// start adhoc launching routine
|
|
||||||
CheckPasswords({
|
|
||||||
scope: $scope,
|
|
||||||
credential: $scope.credential,
|
|
||||||
callback: 'ContinueCred'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove all data input into the form
|
|
||||||
$scope.formReset = function () {
|
|
||||||
generator.reset();
|
|
||||||
for (var fld in master) {
|
|
||||||
$scope[fld] = master[fld];
|
|
||||||
}
|
|
||||||
$scope.limit = $scope.providedHostPatterns;
|
|
||||||
KindChange({ scope: $scope, form: form, reset: false });
|
|
||||||
$scope.verbosity = $scope.adhoc_verbosity_options[$scope.verbosity_field.default];
|
|
||||||
$("#forks-number").spinner("value", $scope.forks_field.default);
|
|
||||||
$scope.forks = $scope.forks_field.default;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AdhocCtrl.$inject = ['$scope', '$rootScope', '$location', '$routeParams',
|
|
||||||
'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'AdhocForm',
|
|
||||||
'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
|
|
||||||
'GetChoices', 'KindChange', 'LookUpInit', 'CredentialList', 'Empty', 'Wait'];
|
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
import ActivityDetail from "./forms/ActivityDetail";
|
import ActivityDetail from "./forms/ActivityDetail";
|
||||||
import Credentials from "./forms/Credentials";
|
import Credentials from "./forms/Credentials";
|
||||||
import Adhoc from "./forms/Adhoc";
|
|
||||||
import EventsViewer from "./forms/EventsViewer";
|
import EventsViewer from "./forms/EventsViewer";
|
||||||
import Groups from "./forms/Groups";
|
import Groups from "./forms/Groups";
|
||||||
import HostGroups from "./forms/HostGroups";
|
import HostGroups from "./forms/HostGroups";
|
||||||
@@ -33,7 +32,6 @@ import Users from "./forms/Users";
|
|||||||
export
|
export
|
||||||
{ ActivityDetail,
|
{ ActivityDetail,
|
||||||
Credentials,
|
Credentials,
|
||||||
Adhoc,
|
|
||||||
EventsViewer,
|
EventsViewer,
|
||||||
Groups,
|
Groups,
|
||||||
HostGroups,
|
HostGroups,
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
/*************************************************
|
|
||||||
* Copyright (c) 2015 Ansible, Inc.
|
|
||||||
*
|
|
||||||
* All Rights Reserved
|
|
||||||
*************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc function
|
|
||||||
* @name forms.function:Adhoc
|
|
||||||
* @description This form is for executing an adhoc command
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default
|
|
||||||
angular.module('AdhocFormDefinition', [])
|
|
||||||
.value('AdhocForm', {
|
|
||||||
editTitle: 'Execute Command',
|
|
||||||
name: 'adhoc',
|
|
||||||
well: true,
|
|
||||||
forceListeners: true,
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
module_name: {
|
|
||||||
label: 'Module',
|
|
||||||
excludeModal: true,
|
|
||||||
type: 'select',
|
|
||||||
ngOptions: 'module.label for module in adhoc_module_options' +
|
|
||||||
' track by module.value',
|
|
||||||
ngChange: 'moduleChange()',
|
|
||||||
editRequired: true,
|
|
||||||
awPopOver:'<p>These are the modules that Tower supports ' +
|
|
||||||
'running commands against.',
|
|
||||||
dataTitle: 'Module',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: 'body'
|
|
||||||
},
|
|
||||||
module_args: {
|
|
||||||
label: 'Arguments',
|
|
||||||
type: 'text',
|
|
||||||
awPopOverWatch: 'argsPopOver',
|
|
||||||
awPopOver: '{{ argsPopOver }}',
|
|
||||||
dataTitle: 'Arguments',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: 'body',
|
|
||||||
editRequired: false,
|
|
||||||
autocomplete: false
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
label: 'Host Pattern',
|
|
||||||
type: 'text',
|
|
||||||
addRequired: false,
|
|
||||||
editRequired: false,
|
|
||||||
awPopOver: '<p>The pattern used to target hosts in the ' +
|
|
||||||
'inventory. Leaving the field blank, all, and * will ' +
|
|
||||||
'all target all hosts in the inventory. You can find ' +
|
|
||||||
'more information about Ansible\'s host patterns ' +
|
|
||||||
'<a id=\"adhoc_form_hostpatterns_doc_link\"' +
|
|
||||||
'href=\"http://docs.ansible.com/intro_patterns.html\" ' +
|
|
||||||
'target=\"_blank\">here</a>.</p>',
|
|
||||||
dataTitle: 'Host Pattern',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: 'body'
|
|
||||||
},
|
|
||||||
credential: {
|
|
||||||
label: 'Machine Credential',
|
|
||||||
type: 'lookup',
|
|
||||||
sourceModel: 'credential',
|
|
||||||
sourceField: 'name',
|
|
||||||
ngClick: 'lookUpCredential()',
|
|
||||||
class: 'squeeze',
|
|
||||||
awPopOver: '<p>Select the credential you want to use when ' +
|
|
||||||
'accessing the remote hosts to run the command. ' +
|
|
||||||
'Choose the credential containing ' +
|
|
||||||
'the username and SSH key or password that Ansbile ' +
|
|
||||||
'will need to log into the remote hosts.</p>',
|
|
||||||
dataTitle: 'Credential',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: 'body',
|
|
||||||
awRequiredWhen: {
|
|
||||||
variable: 'credRequired',
|
|
||||||
init: 'false'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
become_enabled: {
|
|
||||||
label: 'Enable Privilege Escalation',
|
|
||||||
type: 'checkbox',
|
|
||||||
addRequired: false,
|
|
||||||
editRequird: false,
|
|
||||||
column: 2,
|
|
||||||
awPopOver: "<p>If enabled, run this playbook as an administrator. This is the equivalent of passing the<code> --become</code> option to the <code> ansible</code> command. </p>",
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataTitle: 'Become Privilege Escalation',
|
|
||||||
dataContainer: "body"
|
|
||||||
},
|
|
||||||
verbosity: {
|
|
||||||
label: 'Verbosity',
|
|
||||||
excludeModal: true,
|
|
||||||
type: 'select',
|
|
||||||
ngOptions: 'verbosity.label for verbosity in ' +
|
|
||||||
'adhoc_verbosity_options ' +
|
|
||||||
'track by verbosity.value',
|
|
||||||
editRequired: true,
|
|
||||||
awPopOver:'<p>These are the verbosity levels for standard ' +
|
|
||||||
'out of the command run that are supported.',
|
|
||||||
dataTitle: 'Module',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: 'body',
|
|
||||||
"default": 1
|
|
||||||
},
|
|
||||||
forks: {
|
|
||||||
label: 'Forks',
|
|
||||||
id: 'forks-number',
|
|
||||||
type: 'number',
|
|
||||||
integer: true,
|
|
||||||
min: 0,
|
|
||||||
spinner: true,
|
|
||||||
"default": 0,
|
|
||||||
addRequired: false,
|
|
||||||
editRequired: false,
|
|
||||||
'class': "input-small",
|
|
||||||
column: 1,
|
|
||||||
awPopOver: '<p>The number of parallel or simultaneous processes to use while executing the command. 0 signifies ' +
|
|
||||||
'the default value from the <a id="ansible_forks_docs" href=\"http://docs.ansible.com/intro_configuration.html#the-ansible-configuration-file\" ' +
|
|
||||||
' target=\"_blank\">ansible configuration file</a>.</p>',
|
|
||||||
dataTitle: 'Forks',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: "body"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
buttons: {
|
|
||||||
launch: {
|
|
||||||
label: 'Launch',
|
|
||||||
ngClick: 'launchJob()',
|
|
||||||
ngDisabled: true
|
|
||||||
},
|
|
||||||
reset: {
|
|
||||||
ngClick: 'formReset()',
|
|
||||||
ngDisabled: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
related: {}
|
|
||||||
});
|
|
||||||
220
awx/ui/client/tests/adhoc/adhoc.controller-test.js
Normal file
220
awx/ui/client/tests/adhoc/adhoc.controller-test.js
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import '../support/node';
|
||||||
|
|
||||||
|
import adhocModule from 'adhoc/main';
|
||||||
|
import RestStub from '../support/rest-stub';
|
||||||
|
|
||||||
|
describe("adhoc.controller", function() {
|
||||||
|
var $scope, $rootScope, $location, $routeParams,
|
||||||
|
CheckPasswords, PromptForPasswords, CreateLaunchDialog, AdhocForm,
|
||||||
|
GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices,
|
||||||
|
KindChange, LookUpInit, CredentialList, Empty, Wait;
|
||||||
|
|
||||||
|
var $controller, ctrl, generateFormCallback, waitCallback, locationCallback,
|
||||||
|
getBasePath, processErrorsCallback, restCallback;
|
||||||
|
|
||||||
|
beforeEach("instantiate the adhoc module", function() {
|
||||||
|
angular.mock.module(adhocModule.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
before("create spies", function() {
|
||||||
|
getBasePath = function(path) {
|
||||||
|
return '/' + path + '/';
|
||||||
|
};
|
||||||
|
generateFormCallback = {
|
||||||
|
inject: angular.noop
|
||||||
|
};
|
||||||
|
waitCallback = sinon.spy();
|
||||||
|
locationCallback = {
|
||||||
|
path: sinon.spy()
|
||||||
|
};
|
||||||
|
processErrorsCallback = sinon.spy();
|
||||||
|
restCallback = new RestStub();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach("mock dependencies", angular.mock.module(['$provide', function(_provide_) {
|
||||||
|
var $provide = _provide_;
|
||||||
|
|
||||||
|
$provide.value('$location', locationCallback);
|
||||||
|
$provide.value('CheckPasswords', angular.noop);
|
||||||
|
$provide.value('PromptForPasswords', angular.noop);
|
||||||
|
$provide.value('CreateLaunchDialog', angular.noop);
|
||||||
|
$provide.value('AdhocForm', angular.noop);
|
||||||
|
$provide.value('GenerateForm', generateFormCallback);
|
||||||
|
$provide.value('Rest', restCallback);
|
||||||
|
$provide.value('ProcessErrors', processErrorsCallback);
|
||||||
|
$provide.value('ClearScope', angular.noop);
|
||||||
|
$provide.value('GetBasePath', getBasePath);
|
||||||
|
$provide.value('GetChoices', angular.noop);
|
||||||
|
$provide.value('KindChange', angular.noop);
|
||||||
|
$provide.value('LookUpInit', angular.noop);
|
||||||
|
$provide.value('CredentialList', angular.noop);
|
||||||
|
$provide.value('Empty', angular.noop);
|
||||||
|
$provide.value('Wait', waitCallback);
|
||||||
|
}]));
|
||||||
|
|
||||||
|
beforeEach("put $q in scope", window.inject(['$q', function($q) {
|
||||||
|
restCallback.$q = $q;
|
||||||
|
}]));
|
||||||
|
|
||||||
|
beforeEach("put the controller in scope", inject(function($injector) {
|
||||||
|
$rootScope = $injector.get('$rootScope');
|
||||||
|
$controller = $injector.get('$controller');
|
||||||
|
$scope = $rootScope.$new();
|
||||||
|
ctrl = $controller('adhocController', {$scope: $scope});
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("setAvailableUrls", function() {
|
||||||
|
it('should only have the specified urls ' +
|
||||||
|
'available for adhoc commands', function() {
|
||||||
|
var urls = ctrl.privateFn.setAvailableUrls();
|
||||||
|
expect(urls).to.have.keys('adhocUrl', 'inventoryUrl',
|
||||||
|
'machineCredentialUrl');
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i in urls) {
|
||||||
|
if (urls.hasOwnProperty(i)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(count).to.equal(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setFieldDefaults", function() {
|
||||||
|
it('should set the select form field defaults' +
|
||||||
|
'based on user settings', function() {
|
||||||
|
var verbosity_options = [
|
||||||
|
{label: "0 (Foo)", value: 0, name: "0 (Foo)",
|
||||||
|
isDefault: false},
|
||||||
|
{label: "1 (Bar)", value: 1, name: "1 (Bar)",
|
||||||
|
isDefault: true},
|
||||||
|
],
|
||||||
|
forks_field = {};
|
||||||
|
|
||||||
|
forks_field.default = 3;
|
||||||
|
|
||||||
|
$scope.$apply(function() {
|
||||||
|
ctrl.privateFn.setFieldDefaults(verbosity_options,
|
||||||
|
forks_field.default);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($scope.forks).to.equal(forks_field.default);
|
||||||
|
expect($scope.verbosity.value).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setLoadingStartStop", function() {
|
||||||
|
it('should start the controller working state when the form is ' +
|
||||||
|
'loading', function() {
|
||||||
|
waitCallback.reset();
|
||||||
|
ctrl.privateFn.setLoadingStartStop();
|
||||||
|
expect(waitCallback).to.have.been.calledWith("start");
|
||||||
|
});
|
||||||
|
it('should stop the indicator after all REST calls in the form load have ' +
|
||||||
|
'completed', function() {
|
||||||
|
var forks_field = {},
|
||||||
|
adhoc_verbosity_options = {};
|
||||||
|
forks_field.default = "1";
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.forks_field = forks_field;
|
||||||
|
$scope.adhoc_verbosity_options = adhoc_verbosity_options;
|
||||||
|
});
|
||||||
|
waitCallback.reset();
|
||||||
|
$scope.$emit('adhocFormReady');
|
||||||
|
$scope.$emit('adhocFormReady');
|
||||||
|
expect(waitCallback).to.have.been.calledWith("stop");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getInventoryNameForBreadcrumbs", function() {
|
||||||
|
it('should set the inventory name on scope', function() {
|
||||||
|
var req = ctrl.privateFn.getInventoryNameForBreadcrumbs("foo");
|
||||||
|
var response = { data: { name: "foo" } };
|
||||||
|
|
||||||
|
restCallback.succeed(response);
|
||||||
|
restCallback.flush();
|
||||||
|
|
||||||
|
return req.then(function() {
|
||||||
|
expect($scope.inv_name).to.equal('foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to the inventory manage page when the inventory ' +
|
||||||
|
'can not be found', function() {
|
||||||
|
var req = ctrl.privateFn.getInventoryNameForBreadcrumbs("foo");
|
||||||
|
var response = { "detail": "Not found", status: "bad" };
|
||||||
|
|
||||||
|
restCallback.fail(response);
|
||||||
|
restCallback.flush();
|
||||||
|
|
||||||
|
return req.catch(function() {
|
||||||
|
expect(processErrorsCallback).to.have.been.called;
|
||||||
|
expect(locationCallback.path).to.have.been.calledWith("/inventories/");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("instantiateArgumentHelp", function() {
|
||||||
|
it("should initially provide a canned argument help response", function() {
|
||||||
|
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
|
||||||
|
'with the specified module.</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should change the help response when the module changes", function() {
|
||||||
|
$scope.$apply(function () {
|
||||||
|
$scope.module_name = {value: 'foo'};
|
||||||
|
});
|
||||||
|
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
|
||||||
|
'with the specified module. You can find information about ' +
|
||||||
|
'the foo module <a ' +
|
||||||
|
'id=\"adhoc_module_arguments_docs_link_for_module_foo\" ' +
|
||||||
|
'href=\"http://docs.ansible.com/foo_module.html\" ' +
|
||||||
|
'target=\"_blank\">here</a>.</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should change the help response when the module changes again", function() {
|
||||||
|
$scope.$apply(function () {
|
||||||
|
$scope.module_name = {value: 'bar'};
|
||||||
|
});
|
||||||
|
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
|
||||||
|
'with the specified module. You can find information about ' +
|
||||||
|
'the bar module <a ' +
|
||||||
|
'id=\"adhoc_module_arguments_docs_link_for_module_bar\" ' +
|
||||||
|
'href=\"http://docs.ansible.com/bar_module.html\" ' +
|
||||||
|
'target=\"_blank\">here</a>.</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should change the help response back to the canned response " +
|
||||||
|
"when no module is selected", function() {
|
||||||
|
$scope.$apply(function () {
|
||||||
|
$scope.module_name = null;
|
||||||
|
});
|
||||||
|
expect($scope.argsPopOver).to.equal('<p>These arguments are used ' +
|
||||||
|
'with the specified module.</p>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("instantiateHostPatterns", function() {
|
||||||
|
it("should initialize the limit object based on the provided host " +
|
||||||
|
"pattern", function() {
|
||||||
|
ctrl.privateFn.instantiateHostPatterns("foo:bar");
|
||||||
|
expect($scope.limit).to.equal("foo:bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the providedHostPatterns variable to the provided host " +
|
||||||
|
"pattern so it is accesible on form reset", function() {
|
||||||
|
ctrl.privateFn.instantiateHostPatterns("foo:bar");
|
||||||
|
expect($scope.providedHostPatterns).to.equal("foo:bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove the hostPattern from rootScope after it has been " +
|
||||||
|
"utilized", function() {
|
||||||
|
$rootScope.hostPatterns = "foo";
|
||||||
|
expect($rootScope.hostPatterns).to.exist;
|
||||||
|
ctrl.privateFn.instantiateHostPatterns("foo");
|
||||||
|
expect($rootScope.hostPatterns).to.not.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,8 +11,10 @@
|
|||||||
require('./setup/jsdom');
|
require('./setup/jsdom');
|
||||||
require('./setup/mocha');
|
require('./setup/mocha');
|
||||||
require('./setup/jquery');
|
require('./setup/jquery');
|
||||||
|
require('./setup/jquery-ui');
|
||||||
require('./setup/angular');
|
require('./setup/angular');
|
||||||
require('./setup/angular-mocks');
|
require('./setup/angular-mocks');
|
||||||
|
require('./setup/angular-route');
|
||||||
require('./setup/angular-templates');
|
require('./setup/angular-templates');
|
||||||
require('./setup/sinon');
|
require('./setup/sinon');
|
||||||
require('./setup/chai');
|
require('./setup/chai');
|
||||||
|
|||||||
1
awx/ui/client/tests/support/node/setup/angular-route.js
vendored
Normal file
1
awx/ui/client/tests/support/node/setup/angular-route.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require('angular-route/angular-route');
|
||||||
4
awx/ui/client/tests/support/node/setup/jquery-ui.js
vendored
Normal file
4
awx/ui/client/tests/support/node/setup/jquery-ui.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
var exportGlobal = require('../export-global');
|
||||||
|
global.document = window.document;
|
||||||
|
global.navigator = window.navigator;
|
||||||
|
require('jquery-ui/jquery-ui');
|
||||||
Reference in New Issue
Block a user