update adhoc feature, add tests and modularize

This commit is contained in:
John Mitchell 2015-08-26 11:35:26 -04:00
parent 5727d722b6
commit 7fd24987d7
15 changed files with 722 additions and 411 deletions

View 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];

View 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: {}
};
}

View 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();
}]
}
};

View 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);

View File

@ -43,6 +43,7 @@ import browserData from './browser-data/main';
import dashboard from './dashboard/main';
import moment from './shared/moment/main';
import templateUrl from './shared/template-url/main';
import adhoc from './adhoc/main';
import {JobDetailController} from './controllers/JobDetail';
import {JobStdoutController} from './controllers/JobStdout';
@ -52,7 +53,6 @@ import {ScheduleEditController} from './controllers/Schedules';
import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from './controllers/Organizations';
import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from './controllers/Inventories';
import {AdhocCtrl} from './controllers/Adhoc';
import {AdminsList} from './controllers/Admins';
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
@ -93,6 +93,7 @@ var tower = angular.module('Tower', [
dashboard.name,
moment.name,
templateUrl.name,
adhoc.name,
'templates',
'AuthService',
'Utilities',
@ -112,7 +113,6 @@ var tower = angular.module('Tower', [
'RefreshHelper',
'AdminListDefinition',
'AWDirectives',
'AdhocFormDefinition',
'InventoriesListDefinition',
'InventoryFormDefinition',
'InventoryHelper',
@ -182,9 +182,6 @@ var tower = angular.module('Tower', [
'SocketHelper',
'AboutAnsibleHelpModal',
'PortalJobsListDefinition',
'AdhocHelper',
'features',
'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', {
name: 'organizations',
templateUrl: urlPrefix + 'partials/organizations.html',

View File

@ -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'];

View File

@ -6,7 +6,6 @@
import ActivityDetail from "./forms/ActivityDetail";
import Credentials from "./forms/Credentials";
import Adhoc from "./forms/Adhoc";
import EventsViewer from "./forms/EventsViewer";
import Groups from "./forms/Groups";
import HostGroups from "./forms/HostGroups";
@ -33,7 +32,6 @@ import Users from "./forms/Users";
export
{ ActivityDetail,
Credentials,
Adhoc,
EventsViewer,
Groups,
HostGroups,

View File

@ -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: {}
});

View File

@ -3,7 +3,7 @@
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Adhoc

View File

@ -3,7 +3,7 @@
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:Jobs

View 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;
});
});
});

View File

@ -11,8 +11,10 @@
require('./setup/jsdom');
require('./setup/mocha');
require('./setup/jquery');
require('./setup/jquery-ui');
require('./setup/angular');
require('./setup/angular-mocks');
require('./setup/angular-route');
require('./setup/angular-templates');
require('./setup/sinon');
require('./setup/chai');

View File

@ -0,0 +1 @@
require('angular-route/angular-route');

View File

@ -0,0 +1,4 @@
var exportGlobal = require('../export-global');
global.document = window.document;
global.navigator = window.navigator;
require('jquery-ui/jquery-ui');