mirror of
https://github.com/ansible/awx.git
synced 2024-11-02 01:21:21 +03:00
Merge pull request #6609 from jlmitch5/newCredOnJT
multi-credential ui selector on job template page
This commit is contained in:
commit
8061667ace
@ -264,7 +264,7 @@ export default
|
||||
$scope.credentialTypeOptions = [];
|
||||
credentialTypeData.results.forEach((credentialType => {
|
||||
credential_types[credentialType.id] = credentialType;
|
||||
if(credentialType.kind.match(/^(machine|cloud|network|ssh)$/)) {
|
||||
if(credentialType.kind.match(/^(machine|cloud|net|ssh)$/)) {
|
||||
$scope.credentialTypeOptions.push({
|
||||
name: credentialType.name,
|
||||
value: credentialType.id
|
||||
|
@ -1423,9 +1423,21 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
||||
html += "</div>\n";
|
||||
}
|
||||
|
||||
//custom fields
|
||||
if (field.type === 'custom') {
|
||||
html += label();
|
||||
let labelOptions = {};
|
||||
|
||||
if (field.subCheckbox) {
|
||||
labelOptions.checkbox = {
|
||||
id: `${this.form.name}_${fld}_ask_chbox`,
|
||||
ngShow: field.subCheckbox.ngShow,
|
||||
ngChange: field.subCheckbox.ngChange,
|
||||
ngModel: field.subCheckbox.variable,
|
||||
ngDisabled: field.ngDisabled,
|
||||
text: field.subCheckbox.text || ''
|
||||
};
|
||||
}
|
||||
|
||||
html += label(labelOptions);
|
||||
html += "<div ";
|
||||
html += (horizontal) ? "class=\"" + getFieldWidth() + "\"" : "";
|
||||
html += ">\n";
|
||||
|
@ -46,6 +46,7 @@
|
||||
$scope.playbook_options = [];
|
||||
$scope.mode = "add";
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.credentialNotPresent = false;
|
||||
|
||||
md5Setup({
|
||||
scope: $scope,
|
||||
@ -261,6 +262,29 @@
|
||||
null, true);
|
||||
}
|
||||
|
||||
$scope.selectedCredentials.extra.map(cred => cred.id)
|
||||
.forEach(function(cred_id) {
|
||||
|
||||
Rest.setUrl(data.related.extra_credentials);
|
||||
Rest.post({'id': cred_id})
|
||||
.success(function () {
|
||||
})
|
||||
.error(function (data,
|
||||
status) {
|
||||
ProcessErrors(
|
||||
$scope,
|
||||
data,
|
||||
status,
|
||||
form,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to add extra credential. Post returned ' +
|
||||
'status: ' +
|
||||
status
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var orgDefer = $q.defer();
|
||||
var associationDefer = $q.defer();
|
||||
@ -399,6 +423,14 @@
|
||||
data.ask_inventory_on_launch = $scope.ask_inventory_on_launch ? $scope.ask_inventory_on_launch : false;
|
||||
data.ask_variables_on_launch = $scope.ask_variables_on_launch ? $scope.ask_variables_on_launch : false;
|
||||
data.ask_credential_on_launch = $scope.ask_credential_on_launch ? $scope.ask_credential_on_launch : false;
|
||||
if ($scope.selectedCredentials && $scope.selectedCredentials
|
||||
.machine && $scope.selectedCredentials
|
||||
.machine) {
|
||||
data.credential = $scope.selectedCredentials
|
||||
.machine.id;
|
||||
} else {
|
||||
data.credential = null;
|
||||
}
|
||||
|
||||
data.extra_vars = ToJSON($scope.parseType,
|
||||
$scope.variables, true);
|
||||
|
@ -55,6 +55,7 @@ export default
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.showJobType = false;
|
||||
$scope.instance_groups = InstanceGroupsData;
|
||||
$scope.credentialNotPresent = false;
|
||||
|
||||
SurveyControllerInit({
|
||||
scope: $scope,
|
||||
@ -200,8 +201,6 @@ export default
|
||||
// watch for changes to 'verbosity', ensure we keep our select2 in sync when it changes.
|
||||
$scope.$watch('verbosity', sync_verbosity_select2);
|
||||
|
||||
|
||||
// Turn off 'Wait' after both cloud credential and playbook list come back
|
||||
if ($scope.removeJobTemplateLoadFinished) {
|
||||
$scope.removeJobTemplateLoadFinished();
|
||||
}
|
||||
@ -218,19 +217,11 @@ export default
|
||||
|
||||
});
|
||||
|
||||
if ($scope.cloudCredentialReadyRemove) {
|
||||
$scope.cloudCredentialReadyRemove();
|
||||
}
|
||||
$scope.cloudCredentialReadyRemove = $scope.$on('cloudCredentialReady', function () {
|
||||
$scope.$emit('jobTemplateLoadFinished');
|
||||
});
|
||||
|
||||
|
||||
// Retrieve each related set and populate the playbook list
|
||||
if ($scope.jobTemplateLoadedRemove) {
|
||||
$scope.jobTemplateLoadedRemove();
|
||||
}
|
||||
$scope.jobTemplateLoadedRemove = $scope.$on('jobTemplateLoaded', function (e, related_cloud_credential, masterObject) {
|
||||
$scope.jobTemplateLoadedRemove = $scope.$on('jobTemplateLoaded', function (e, masterObject) {
|
||||
var dft;
|
||||
|
||||
master = masterObject;
|
||||
@ -247,13 +238,7 @@ export default
|
||||
|
||||
ParseTypeChange({ scope: $scope, field_id: 'job_template_variables', onChange: callback });
|
||||
|
||||
if($scope.job_template_obj.summary_fields.cloud_credential && related_cloud_credential) {
|
||||
$scope.$emit('cloudCredentialReady', $scope.job_template_obj.summary_fields.cloud_credential.name);
|
||||
} else {
|
||||
// No existing cloud credential
|
||||
$scope.$emit('cloudCredentialReady', null);
|
||||
}
|
||||
|
||||
$scope.$emit('jobTemplateLoadFinished');
|
||||
});
|
||||
|
||||
Wait('start');
|
||||
@ -411,6 +396,73 @@ export default
|
||||
null, true);
|
||||
}
|
||||
|
||||
let extraCredUrl = data.related.extra_credentials;
|
||||
|
||||
Rest.setUrl(extraCredUrl);
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
let existingCreds = data.results
|
||||
.map(cred => cred.id);
|
||||
|
||||
let newCreds = $scope.selectedCredentials.extra
|
||||
.map(cred => cred.id);
|
||||
|
||||
let toAdd, toRemove;
|
||||
|
||||
[toAdd, toRemove] = _.partition(_.xor(existingCreds, newCreds), cred => (newCreds.indexOf(cred) > -1));
|
||||
|
||||
let destroyResolve = [];
|
||||
|
||||
toRemove.forEach((cred_id) => {
|
||||
Rest.setUrl(extraCredUrl);
|
||||
destroyResolve.push(
|
||||
Rest.post({'id': cred_id, 'disassociate': true})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(
|
||||
$scope,
|
||||
data,
|
||||
status,
|
||||
form,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to remove extra credential. Post returned ' +
|
||||
'status: ' +
|
||||
status
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
$q.all(destroyResolve)
|
||||
.then(() => {
|
||||
toAdd.forEach((cred_id) => {
|
||||
Rest.setUrl(extraCredUrl);
|
||||
Rest.post({'id': cred_id})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(
|
||||
$scope,
|
||||
data,
|
||||
status,
|
||||
form,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to add extra credential. Post returned ' +
|
||||
'status: ' +
|
||||
status
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, form, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to get existing extra credentials. GET returned ' +
|
||||
'status: ' + status
|
||||
});
|
||||
});
|
||||
|
||||
InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups)
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, form, {
|
||||
@ -546,6 +598,14 @@ export default
|
||||
data.ask_inventory_on_launch = $scope.ask_inventory_on_launch ? $scope.ask_inventory_on_launch : false;
|
||||
data.ask_variables_on_launch = $scope.ask_variables_on_launch ? $scope.ask_variables_on_launch : false;
|
||||
data.ask_credential_on_launch = $scope.ask_credential_on_launch ? $scope.ask_credential_on_launch : false;
|
||||
if ($scope.selectedCredentials && $scope.selectedCredentials
|
||||
.machine && $scope.selectedCredentials
|
||||
.machine) {
|
||||
data.credential = $scope.selectedCredentials
|
||||
.machine.id;
|
||||
} else {
|
||||
data.credential = null;
|
||||
}
|
||||
|
||||
data.extra_vars = ToJSON($scope.parseType,
|
||||
$scope.variables, true);
|
||||
@ -585,8 +645,8 @@ export default
|
||||
});
|
||||
} catch (err) {
|
||||
Wait('stop');
|
||||
Alert("Error", "Error parsing extra variables. " +
|
||||
"Parser returned: " + err);
|
||||
Alert("Error", "Error saving job template. " +
|
||||
"Error: " + err);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default
|
||||
function CallbackHelpInit($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors,
|
||||
ParseVariableString, Empty, CredentialList, Wait) {
|
||||
function CallbackHelpInit($q, $location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors,
|
||||
ParseVariableString, Empty, Wait) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
defaultUrl = GetBasePath('job_templates'),
|
||||
@ -13,8 +13,6 @@ export default
|
||||
// checkSCMStatus, getPlaybooks, callback,
|
||||
// choicesCount = 0;
|
||||
|
||||
CredentialList = _.cloneDeep(CredentialList);
|
||||
|
||||
// The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the
|
||||
// popover is activated, a function checks the value of scope.callback_help before constructing the content.
|
||||
scope.setCallbackHelp = function() {
|
||||
@ -136,7 +134,114 @@ export default
|
||||
|
||||
scope.can_edit = data.summary_fields.user_capabilities.edit;
|
||||
|
||||
scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master);
|
||||
scope.selectedCredentials = {
|
||||
machine: null,
|
||||
extra: []
|
||||
};
|
||||
|
||||
var credDefers = [];
|
||||
|
||||
if (data.related.credential) {
|
||||
Rest.setUrl(data.related.credential);
|
||||
credDefers.push(Rest.get()
|
||||
.then(({data}) => {
|
||||
scope.selectedCredentials.machine = data;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(
|
||||
scope,
|
||||
data,
|
||||
status,
|
||||
null,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to get machine credential. ' +
|
||||
'Get returned status: ' +
|
||||
status
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.related.extra_credentials) {
|
||||
Rest.setUrl(data.related.extra_credentials);
|
||||
credDefers.push(Rest.get()
|
||||
.then(({data}) => {
|
||||
scope.selectedCredentials.extra = data.results;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(
|
||||
scope,
|
||||
data,
|
||||
status,
|
||||
null,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to get extra credentials. ' +
|
||||
'Get returned status: ' +
|
||||
status
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
Rest.setUrl(GetBasePath('credential_types'));
|
||||
credDefers.push(Rest.get()
|
||||
.then(({data}) => {
|
||||
scope.credentialTypeOptions = [];
|
||||
data.results.forEach((credentialType => {
|
||||
if(credentialType.kind.match(/^(machine|cloud|network|ssh)$/)) {
|
||||
scope.credentialTypeOptions.push({
|
||||
name: credentialType.name,
|
||||
value: credentialType.id
|
||||
});
|
||||
}
|
||||
}));
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(
|
||||
scope,
|
||||
data,
|
||||
status,
|
||||
null,
|
||||
{
|
||||
hdr: 'Error!',
|
||||
msg: 'Failed to get credential types. Get returned ' +
|
||||
'status: ' +
|
||||
status
|
||||
});
|
||||
}));
|
||||
|
||||
$q.all(credDefers)
|
||||
.then(() => {
|
||||
let machineCred = [];
|
||||
let extraCreds = [];
|
||||
|
||||
if (scope.selectedCredentials && scope.selectedCredentials.machine) {
|
||||
let mach = scope.selectedCredentials.machine;
|
||||
mach.postType = "machine";
|
||||
machineCred = [scope.selectedCredentials.machine];
|
||||
}
|
||||
|
||||
if (scope.selectedCredentials && scope.selectedCredentials.extra) {
|
||||
extraCreds = scope.selectedCredentials.extra;
|
||||
}
|
||||
|
||||
extraCreds = extraCreds.map(function(cred) {
|
||||
cred.postType = "extra";
|
||||
|
||||
return cred;
|
||||
});
|
||||
|
||||
let credTags = machineCred.concat(extraCreds);
|
||||
|
||||
scope.credentialsToPost = credTags.map(cred => ({
|
||||
name: cred.name,
|
||||
id: cred.id,
|
||||
postType: cred.postType,
|
||||
kind: scope.credentialTypeOptions
|
||||
.filter(type => parseInt(cred.credential_type) === type.value)[0].name + ":"
|
||||
}));
|
||||
scope.$emit('jobTemplateLoaded', master);
|
||||
});
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, form, {
|
||||
@ -149,7 +254,7 @@ export default
|
||||
}
|
||||
|
||||
CallbackHelpInit.$inject =
|
||||
[ '$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm',
|
||||
[ '$q', '$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm',
|
||||
'$stateParams', 'ProcessErrors', 'ParseVariableString',
|
||||
'Empty', 'CredentialList', 'Wait'
|
||||
'Empty', 'Wait'
|
||||
];
|
||||
|
@ -127,68 +127,26 @@ function(NotificationsList, CompletedJobsList, i18n) {
|
||||
includePlaybookNotFoundError: true
|
||||
},
|
||||
credential: {
|
||||
label: i18n._('Machine Credential'),
|
||||
type: 'lookup',
|
||||
list: 'CredentialList',
|
||||
basePath: 'credentials',
|
||||
autopopulateLookup: false,
|
||||
search: {
|
||||
kind: 'ssh'
|
||||
},
|
||||
sourceModel: 'credential',
|
||||
sourceField: 'name',
|
||||
awRequiredWhen: {
|
||||
reqExpression: '!ask_credential_on_launch',
|
||||
alwaysShowAsterisk: true
|
||||
},
|
||||
requiredErrorMsg: i18n._("Please select a Machine Credential or check the Prompt on launch option."),
|
||||
column: 1,
|
||||
awPopOver: "<p>" + i18n._("Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " +
|
||||
" the username and SSH key or password that Ansible will need to log into the remote hosts.") + "</p>",
|
||||
dataTitle: i18n._('Credential'),
|
||||
label: i18n._('Credentials'),
|
||||
type: 'custom',
|
||||
control: `
|
||||
<multi-credential
|
||||
credentials="credentials"
|
||||
prompt="ask_credential_on_launch"
|
||||
selected-credentials="selectedCredentials"
|
||||
credential-not-present="credentialNotPresent"
|
||||
credentials-to-post="credentialsToPost">
|
||||
</multi-credential>`,
|
||||
required: true,
|
||||
awPopOver: "<p>" + i18n._("Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type.<br /><br />You must select either a machine (SSH) credential or \"Prompt on launch\". \"Prompt on launch\" requires you to select a machine credential at run time.<br /><br />If you select credentials AND check the \"Prompt on launch\" box, you make the selected credentials the defaults that can be updated at run time.") + "</p>",
|
||||
dataTitle: i18n._('Credentials'),
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
subCheckbox: {
|
||||
variable: 'ask_credential_on_launch',
|
||||
text: i18n._('Prompt on launch'),
|
||||
ngChange: 'job_template_form.credential_name.$validate()',
|
||||
},
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
||||
},
|
||||
cloud_credential: {
|
||||
label: i18n._('Cloud Credential'),
|
||||
type: 'lookup',
|
||||
list: 'CredentialList',
|
||||
basePath: 'credentials',
|
||||
search: {
|
||||
cloud: 'true'
|
||||
},
|
||||
sourceModel: 'cloud_credential',
|
||||
sourceField: 'name',
|
||||
column: 1,
|
||||
awPopOver:"<p>" + i18n._("Selecting an optional cloud credential in the job template will pass along the access credentials to the " +
|
||||
"running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.") + "</p>",
|
||||
dataTitle: i18n._('Cloud Credential'),
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
||||
},
|
||||
network_credential: {
|
||||
label: i18n._('Network Credential'),
|
||||
type: 'lookup',
|
||||
list: 'CredentialList',
|
||||
basePath: 'credentials',
|
||||
search: {
|
||||
kind: 'net'
|
||||
},
|
||||
sourceModel: 'network_credential',
|
||||
sourceField: 'name',
|
||||
column: 1,
|
||||
awPopOver: "<p>" + i18n._("Network credentials are used by Ansible networking modules to connect to and manage networking devices.") + "</p>",
|
||||
dataTitle: i18n._('Network Credential'),
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body",
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
||||
}
|
||||
},
|
||||
forks: {
|
||||
label: i18n._('Forks'),
|
||||
@ -417,7 +375,7 @@ function(NotificationsList, CompletedJobsList, i18n) {
|
||||
},
|
||||
save: {
|
||||
ngClick: 'formSave()', //$scope.function to call on click, optional
|
||||
ngDisabled: "job_template_form.$invalid",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
|
||||
ngDisabled: "job_template_form.$invalid || credentialNotPresent",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
|
||||
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
||||
}
|
||||
},
|
13
awx/ui/client/src/templates/job_templates/main.js
Normal file
13
awx/ui/client/src/templates/job_templates/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
import jobTemplateAdd from './add-job-template/main';
|
||||
import jobTemplateEdit from './edit-job-template/main';
|
||||
import multiCredential from './multi-credential/main';
|
||||
import md5Setup from './factories/md-5-setup.factory';
|
||||
import CallbackHelpInit from './factories/callback-help-init.factory';
|
||||
import JobTemplateForm from './job-template.form';
|
||||
|
||||
export default
|
||||
angular.module('jobTemplates', [jobTemplateAdd.name, jobTemplateEdit.name,
|
||||
multiCredential.name])
|
||||
.factory('md5Setup', md5Setup)
|
||||
.factory('CallbackHelpInit', CallbackHelpInit)
|
||||
.factory('JobTemplateForm', JobTemplateForm);
|
@ -0,0 +1,7 @@
|
||||
import multiCredential from './multi-credential.directive';
|
||||
import multiCredentialModal from './multi-credential-modal.directive';
|
||||
|
||||
export default
|
||||
angular.module('multiCredential', [])
|
||||
.directive('multiCredential', multiCredential)
|
||||
.directive('multiCredentialModal', multiCredentialModal);
|
@ -0,0 +1,281 @@
|
||||
export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile',
|
||||
function(templateUrl, Rest, GetBasePath, GenerateList, $compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
credentialsToPost: '=',
|
||||
credentials: '=',
|
||||
selectedCredentials: '='
|
||||
},
|
||||
templateUrl: templateUrl('templates/job_templates/multi-credential/multi-credential-modal'),
|
||||
|
||||
link: function(scope, element) {
|
||||
scope.credentialKind = "1";
|
||||
|
||||
$('#multi-credential-modal').on('hidden.bs.modal', function () {
|
||||
$('#multi-credential-modal').off('hidden.bs.modal');
|
||||
$(element).remove();
|
||||
});
|
||||
|
||||
scope.showModal = function() {
|
||||
$('#multi-credential-modal').modal('show');
|
||||
};
|
||||
|
||||
scope.destroyModal = function() {
|
||||
scope.credentialKind = 1;
|
||||
$('#multi-credential-modal').modal('hide');
|
||||
};
|
||||
|
||||
scope.generateCredentialList = function() {
|
||||
let html = GenerateList.build({
|
||||
list: scope.list,
|
||||
input_type: 'radio',
|
||||
mode: 'lookup'
|
||||
});
|
||||
$('#multi-credential-modal-body')
|
||||
.append($compile(html)(scope));
|
||||
};
|
||||
|
||||
// Go out and get the credential types
|
||||
Rest.setUrl(GetBasePath('credential_types'));
|
||||
Rest.get()
|
||||
.success(function (credentialTypeData) {
|
||||
let credential_types = {};
|
||||
scope.credentialTypeOptions = [];
|
||||
credentialTypeData.results.forEach((credentialType => {
|
||||
credential_types[credentialType.id] = credentialType;
|
||||
if(credentialType.kind
|
||||
.match(/^(machine|cloud|net|ssh)$/)) {
|
||||
scope.credentialTypeOptions.push({
|
||||
name: credentialType.name,
|
||||
value: credentialType.id
|
||||
});
|
||||
}
|
||||
}));
|
||||
scope.credential_types = credential_types;
|
||||
scope.$emit('multiCredentialModalLinked');
|
||||
});
|
||||
},
|
||||
|
||||
controller: ['$scope', 'CredentialList', 'i18n', 'QuerySet',
|
||||
'GetBasePath', function($scope, CredentialList, i18n, qs,
|
||||
GetBasePath) {
|
||||
|
||||
let updateCredentialTags = function() {
|
||||
let machineCred = [];
|
||||
let extraCreds = [];
|
||||
|
||||
if ($scope.selectedCredentials &&
|
||||
$scope.selectedCredentials.machine) {
|
||||
let mach = $scope.selectedCredentials.machine;
|
||||
mach.postType = "machine";
|
||||
machineCred = [$scope.selectedCredentials.machine];
|
||||
}
|
||||
|
||||
if ($scope.selectedCredentials &&
|
||||
$scope.selectedCredentials.extra) {
|
||||
extraCreds = $scope.selectedCredentials.extra;
|
||||
}
|
||||
|
||||
extraCreds = extraCreds.map(function(cred) {
|
||||
cred.postType = "extra";
|
||||
|
||||
return cred;
|
||||
});
|
||||
|
||||
let credTags = machineCred.concat(extraCreds);
|
||||
|
||||
$scope.credTags = credTags.map(cred => ({
|
||||
name: cred.name,
|
||||
id: cred.id,
|
||||
postType: cred.postType,
|
||||
kind: $scope.credentialTypeOptions
|
||||
.filter(type => {
|
||||
return parseInt(cred.credential_type) === type.value;
|
||||
})[0].name + ":"
|
||||
}));
|
||||
};
|
||||
|
||||
let updateExtraCredentialsList = function() {
|
||||
let extraCredIds = $scope.selectedCredentials.extra
|
||||
.map(cred => cred.id);
|
||||
$scope.credentials.forEach(cred => {
|
||||
if (cred.credential_type !== 1) {
|
||||
cred.checked = (extraCredIds
|
||||
.indexOf(cred.id) > - 1) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
updateCredentialTags();
|
||||
};
|
||||
|
||||
let updateMachineCredentialList = function() {
|
||||
$scope.credentials.forEach(cred => {
|
||||
if (cred.credential_type === 1) {
|
||||
cred.checked = ($scope.selectedCredentials
|
||||
.machine !== null &&
|
||||
cred.id === $scope.selectedCredentials
|
||||
.machine.id) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
updateCredentialTags();
|
||||
};
|
||||
|
||||
let uncheckAllCredentials = function() {
|
||||
$scope.credentials.forEach(cred => {
|
||||
cred.checked = 0;
|
||||
});
|
||||
updateCredentialTags();
|
||||
};
|
||||
|
||||
let init = function() {
|
||||
$scope.originalSelectedCredentials = _.cloneDeep($scope
|
||||
.selectedCredentials);
|
||||
$scope.credential_dataset = [];
|
||||
$scope.credentials = $scope.credentials || [];
|
||||
$scope.listRendered = false;
|
||||
|
||||
let credList = _.cloneDeep(CredentialList);
|
||||
credList.emptyListText = i18n._('No Credentials Matching This Type Have Been Created');
|
||||
$scope.list = credList;
|
||||
|
||||
$scope.credential_default_params = {
|
||||
order_by: 'name',
|
||||
page_size: 5
|
||||
};
|
||||
|
||||
$scope.credential_queryset = {
|
||||
order_by: 'name',
|
||||
page_size: 5
|
||||
};
|
||||
|
||||
$scope.$watch('credentialKind', function(){
|
||||
$scope.credential_default_params.credential_type = $scope
|
||||
.credential_queryset.credential_type = parseInt($scope
|
||||
.credentialKind);
|
||||
|
||||
qs.search(GetBasePath('credentials'), $scope
|
||||
.credential_default_params)
|
||||
.then(res => {
|
||||
$scope.credential_dataset = res.data;
|
||||
$scope.credentials = $scope.credential_dataset
|
||||
.results;
|
||||
|
||||
if(!$scope.listRendered) {
|
||||
$scope.generateCredentialList();
|
||||
$scope.listRendered = true;
|
||||
$scope.showModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$watchCollection('selectedCredentials.extra', () => {
|
||||
if($scope.credentials && $scope.credentials.length > 0) {
|
||||
if($scope.selectedCredentials.extra &&
|
||||
$scope.selectedCredentials.extra.length > 0 &&
|
||||
parseInt($scope.credentialKind) !== 1) {
|
||||
updateExtraCredentialsList();
|
||||
} else {
|
||||
uncheckAllCredentials();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('selectedCredentials.machine', () => {
|
||||
if($scope.selectedCredentials &&
|
||||
$scope.selectedCredentials.machine &&
|
||||
parseInt($scope.credentialKind) === 1) {
|
||||
updateMachineCredentialList();
|
||||
} else {
|
||||
uncheckAllCredentials();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watchGroup(['credentials',
|
||||
'selectedCredentials.machine'], () => {
|
||||
if($scope.credentials &&
|
||||
$scope.credentials.length > 0) {
|
||||
if($scope.selectedCredentials &&
|
||||
$scope.selectedCredentials.machine &&
|
||||
parseInt($scope.credentialKind) === 1) {
|
||||
updateMachineCredentialList();
|
||||
} else if($scope.selectedCredentials &&
|
||||
$scope.selectedCredentials.extra &&
|
||||
$scope.selectedCredentials.extra.length > 0 &&
|
||||
parseInt($scope.credentialKind) !== 1) {
|
||||
updateExtraCredentialsList();
|
||||
} else {
|
||||
uncheckAllCredentials();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('multiCredentialModalLinked', function() {
|
||||
init();
|
||||
});
|
||||
|
||||
$scope.toggle_row = function(selectedRow) {
|
||||
if(parseInt($scope.credentialKind) === 1) {
|
||||
if($scope.selectedCredentials &&
|
||||
$scope.selectedCredentials.machine &&
|
||||
$scope.selectedCredentials.machine.id === selectedRow.id) {
|
||||
$scope.selectedCredentials.machine = null;
|
||||
} else {
|
||||
$scope.selectedCredentials.machine = _.cloneDeep(selectedRow);
|
||||
}
|
||||
} else {
|
||||
let rowDeselected = false;
|
||||
for (let i = $scope.selectedCredentials.extra.length - 1; i >= 0; i--) {
|
||||
if($scope.selectedCredentials.extra[i].id === selectedRow
|
||||
.id) {
|
||||
$scope.selectedCredentials.extra.splice(i, 1);
|
||||
rowDeselected = true;
|
||||
} else if(selectedRow.credential_type === $scope
|
||||
.selectedCredentials.extra[i].credential_type) {
|
||||
$scope.selectedCredentials.extra.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if(!rowDeselected) {
|
||||
$scope.selectedCredentials.extra
|
||||
.push(_.cloneDeep(selectedRow));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeCredential = function(credToRemove) {
|
||||
$scope.credTags
|
||||
.forEach(function(cred) {
|
||||
if (credToRemove === cred.id) {
|
||||
if (cred.postType === 'machine') {
|
||||
$scope.selectedCredentials[cred.postType] = null;
|
||||
} else {
|
||||
$scope.selectedCredentials[cred.postType] = $scope
|
||||
.selectedCredentials[cred.postType]
|
||||
.filter(cred => cred
|
||||
.id !== credToRemove);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.credTags = $scope.credTags
|
||||
.filter(cred => cred.id !== credToRemove);
|
||||
|
||||
if ($scope.credentials
|
||||
.filter(cred => cred.id === credToRemove).length) {
|
||||
uncheckAllCredentials();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.cancelForm = function() {
|
||||
$scope.selectedCredentials = $scope.originalSelectedCredentials;
|
||||
$scope.credTags = $scope.credentialsToPost;
|
||||
$scope.destroyModal();
|
||||
};
|
||||
|
||||
$scope.saveForm = function() {
|
||||
$scope.credentialsToPost = $scope.credTags;
|
||||
$scope.destroyModal();
|
||||
};
|
||||
}]
|
||||
};
|
||||
}];
|
@ -0,0 +1,75 @@
|
||||
<div id="multi-credential-modal" class="Lookup modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header Form-header">
|
||||
<div class="Form-title Form-title--uppercase"
|
||||
translate>
|
||||
CREDENTIALS
|
||||
</div>
|
||||
<div class="Form-header--fields"></div>
|
||||
<div class="Form-exitHolder">
|
||||
<button type="button" class="Form-exit"
|
||||
ng-click="cancelForm()">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="MultiCredential-selectedBar"
|
||||
ng-show="credTags && credTags.length">
|
||||
<span class="MultiCredential-selectedBarLabel" translate>
|
||||
SELECTED:
|
||||
</span>
|
||||
<div class="MultiCredential-tags">
|
||||
<div class="MultiCredential-tagSection">
|
||||
<div class="MultiCredential-flexContainer">
|
||||
<div
|
||||
class="MultiCredential-tagContainer ng-scope"
|
||||
ng-repeat="tag in credTags track by $index">
|
||||
<div class="MultiCredential-deleteContainer"
|
||||
ng-click="removeCredential(tag.id)">
|
||||
<i class="fa fa-times
|
||||
MultiCredential-tagDelete">
|
||||
</i>
|
||||
</div>
|
||||
<div class="MultiCredential-tag
|
||||
MultiCredential-tag--deletable">
|
||||
<span
|
||||
class="MultiCredential-name--label
|
||||
ng-binding">
|
||||
{{ tag.kind }}
|
||||
</span>
|
||||
<span class="MultiCredential-name
|
||||
ng-binding">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="MultiCredential-credentialSubSection">
|
||||
<select ng-model="credentialKind">
|
||||
<option ng-repeat="option in credentialTypeOptions"
|
||||
value="{{option.value}}">{{option.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="multi-credential-modal-body"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" ng-click="cancelForm()"
|
||||
class="Lookup-cancel btn btn-sm Form-cancelButton"
|
||||
translate>
|
||||
CANCEL
|
||||
</button>
|
||||
<button type="button"
|
||||
ng-click="saveForm()"
|
||||
ng-disabled="!credentials || credentials.length === 0"
|
||||
class="Lookup-cancel btn btn-sm Form-saveButton" translate>
|
||||
SELECT
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,112 @@
|
||||
@import "../../../shared/branding/colors.default.less";
|
||||
|
||||
.MultiCredential-selectedBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
background: @default-no-items-bord;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid @default-icon-hov;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.MultiCredential-selectedBarLabel {
|
||||
margin-right: 20px;
|
||||
font-size: 12px;
|
||||
color: @default-icon;
|
||||
}
|
||||
|
||||
.MultiCredential-tags {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.MultiCredential-bar i {
|
||||
font-size: 16px;
|
||||
color: @default-icon;
|
||||
}
|
||||
|
||||
.MultiCredential-flexContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.MultiCredential-tagContainer {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.MultiCredential-tag {
|
||||
border-radius: 5px;
|
||||
padding: 2px 10px;
|
||||
margin: 4px 0px;
|
||||
font-size: 12px;
|
||||
color: @default-interface-txt;
|
||||
background-color: @default-bg;
|
||||
margin-right: 10px;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.MultiCredential-tag--deletable {
|
||||
margin-right: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-right: 0;
|
||||
max-width: ~"calc(100% - 23px)";
|
||||
background-color: @default-link;
|
||||
color: @default-bg;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.MultiCredential-deleteContainer {
|
||||
background-color: @default-link!important;
|
||||
color: white;
|
||||
background-color: @default-bg;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
padding: 0 5px;
|
||||
margin: 4px 0px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.MultiCredential-tagDelete {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.MultiCredential-name {
|
||||
flex: initial;
|
||||
font-size: 12px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.MultiCredential-name--label {
|
||||
color: @default-list-header-bg;
|
||||
font-size: 10px;
|
||||
margin-left: -7px;
|
||||
margin-right: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.MultiCredential-tag--deletable > .MultiCredential-name {
|
||||
max-width: ~"calc(100% - 23px)";
|
||||
}
|
||||
|
||||
.MultiCredential-deleteContainer:hover {
|
||||
border-color: @default-err;
|
||||
background-color: @default-err!important;
|
||||
}
|
||||
|
||||
.MultiCredential-deleteContainer:hover > .MultiCredential-tagDelete {
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.MultiCredential-credentialSubSection {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 15px;
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['templateUrl', '$compile',
|
||||
function(templateUrl, $compile) {
|
||||
return {
|
||||
scope: {
|
||||
credentials: '=',
|
||||
selectedCredentials: '=',
|
||||
prompt: '=',
|
||||
credentialNotPresent: '=',
|
||||
credentialsToPost: '='
|
||||
},
|
||||
restrict: 'E',
|
||||
templateUrl: templateUrl('templates/job_templates/multi-credential/multi-credential'),
|
||||
controller: ['$scope',
|
||||
function($scope) {
|
||||
if (!$scope.selectedCredentials) {
|
||||
$scope.selectedCredentials = {
|
||||
machine: null,
|
||||
extra: []
|
||||
};
|
||||
}
|
||||
|
||||
if (!$scope.credentialsToPost) {
|
||||
$scope.credentialsToPost = [];
|
||||
}
|
||||
|
||||
$scope.fieldDirty = false;
|
||||
|
||||
$scope.$watchGroup(['prompt', 'credentialsToPost'],
|
||||
function() {
|
||||
if ($scope.prompt ||
|
||||
$scope.credentialsToPost.length) {
|
||||
$scope.fieldDirty = true;
|
||||
}
|
||||
|
||||
$scope.credentialNotPresent = !$scope.prompt &&
|
||||
$scope.selectedCredentials.machine === null;
|
||||
});
|
||||
|
||||
$scope.removeCredential = function(credToRemove) {
|
||||
$scope.credentialsToPost = $scope.credentialsToPost
|
||||
.filter(function(cred) {
|
||||
if (cred.id === credToRemove) {
|
||||
if (cred.postType === 'machine') {
|
||||
$scope.selectedCredentials.machine = null;
|
||||
} else {
|
||||
$scope.selectedCredentials
|
||||
.extra = $scope.selectedCredentials
|
||||
.extra
|
||||
.filter(selectedCred => {
|
||||
return selectedCred
|
||||
.id !== credToRemove;
|
||||
});
|
||||
}
|
||||
}
|
||||
return cred.id !== credToRemove;
|
||||
});
|
||||
};
|
||||
}
|
||||
],
|
||||
link: function(scope) {
|
||||
scope.openMultiCredentialModal = function() {
|
||||
$('#content-container')
|
||||
.append($compile(`
|
||||
<multi-credential-modal
|
||||
credentials="credentials"
|
||||
credentials-to-post="credentialsToPost"
|
||||
selected-credentials="selectedCredentials">
|
||||
</multi-credential-modal>`)(scope));
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
@ -0,0 +1,46 @@
|
||||
<div class="input-group Form-mixedInputGroup">
|
||||
<span class="input-group-btn Form-variableHeightButtonGroup">
|
||||
<button type="button"
|
||||
class="Form-lookupButton
|
||||
Form-lookupButton--variableHeight btn btn-default"
|
||||
ng-click="openMultiCredentialModal()">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</span>
|
||||
<span class="form-control Form-textInput Form-textInput--variableHeight
|
||||
input-medium lookup"
|
||||
ng-class="{
|
||||
'ng-invalid': credentialNotPresent,
|
||||
'ng-dirty': fieldDirty
|
||||
}"
|
||||
style="padding: 4px 6px;">
|
||||
<div class="MultiCredential-tags">
|
||||
<div class="MultiCredential-tagSection">
|
||||
<div class="MultiCredential-flexContainer">
|
||||
<div class="MultiCredential-tagContainer ng-scope"
|
||||
ng-repeat="tag in credentialsToPost track by $index">
|
||||
<div class="MultiCredential-deleteContainer"
|
||||
ng-click="removeCredential(tag.id)">
|
||||
<i class="fa fa-times MultiCredential-tagDelete">
|
||||
</i>
|
||||
</div>
|
||||
<div class="MultiCredential-tag
|
||||
MultiCredential-tag--deletable">
|
||||
<span class="MultiCredential-name--label
|
||||
ng-binding">
|
||||
{{ tag.kind }}
|
||||
</span>
|
||||
<span class="MultiCredential-name ng-binding">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="error" ng-cloak ng-show="credentialNotPresent && fieldDirty"
|
||||
translate>
|
||||
Please select a machine (SSH) credential or check the "Prompt on launch" option.
|
||||
</div>
|
@ -7,8 +7,7 @@
|
||||
import templatesService from './templates.service';
|
||||
import surveyMaker from './survey-maker/main';
|
||||
import templatesList from './list/main';
|
||||
import jobTemplatesAdd from './job_templates/add-job-template/main';
|
||||
import jobTemplatesEdit from './job_templates/edit-job-template/main';
|
||||
import jobTemplates from './job_templates/main';
|
||||
import workflowAdd from './workflows/add-workflow/main';
|
||||
import workflowEdit from './workflows/edit-workflow/main';
|
||||
import labels from './labels/main';
|
||||
@ -18,28 +17,21 @@ import workflowControls from './workflows/workflow-controls/main';
|
||||
import templatesListRoute from './list/templates-list.route';
|
||||
import workflowService from './workflows/workflow.service';
|
||||
import templateCopyService from './copy-template/template-copy.service';
|
||||
import CallbackHelpInit from './job_templates/factories/callback-help-init.factory';
|
||||
import md5Setup from './job_templates/factories/md-5-setup.factory';
|
||||
import WorkflowForm from './workflows.form';
|
||||
import CompletedJobsList from './completed-jobs.list';
|
||||
import InventorySourcesList from './inventory-sources.list';
|
||||
import TemplateList from './templates.list';
|
||||
import JobTemplateForm from './job-template.form';
|
||||
|
||||
export default
|
||||
angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesAdd.name,
|
||||
jobTemplatesEdit.name, labels.name, workflowAdd.name, workflowEdit.name,
|
||||
angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.name, labels.name, workflowAdd.name, workflowEdit.name,
|
||||
workflowChart.name, workflowMaker.name, workflowControls.name
|
||||
])
|
||||
.service('TemplatesService', templatesService)
|
||||
.service('WorkflowService', workflowService)
|
||||
.service('TemplateCopyService', templateCopyService)
|
||||
.factory('CallbackHelpInit', CallbackHelpInit)
|
||||
.factory('md5Setup', md5Setup)
|
||||
.factory('WorkflowForm', WorkflowForm)
|
||||
.factory('CompletedJobsList', CompletedJobsList)
|
||||
.factory('TemplateList', TemplateList)
|
||||
.factory('JobTemplateForm', JobTemplateForm)
|
||||
.value('InventorySourcesList', InventorySourcesList)
|
||||
.config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider',
|
||||
function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) {
|
||||
|
Loading…
Reference in New Issue
Block a user