diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less
index 86db1b3fff..4f007ea2f4 100644
--- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less
+++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less
@@ -51,6 +51,10 @@
padding-left: 15px;
}
+.MultiCredential-tag--disabled {
+ background-color: @default-icon;
+}
+
.MultiCredential-tag--deletable {
margin-right: 0px;
border-top-left-radius: 0px;
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html
index 6d51ae9ba1..912dc078a5 100644
--- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html
+++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html
@@ -23,12 +23,12 @@
ng-repeat="tag in credentialsToPost track by $index">
+ ng-hide="fieldIsDisabled || tag.readOnly">
+ ng-class="{'MultiCredential-tag--deletable': !fieldIsDisabled && !tag.readOnly, 'MultiCredential-tag--disabled': tag.readOnly}">
{{ tag.kind }}
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js
index 644097b451..a22937fb61 100644
--- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js
+++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js
@@ -138,6 +138,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
name: cred.name,
id: cred.id,
postType: cred.postType,
+ readOnly: cred.readOnly ? true : false,
kind: typeOpts
.filter(type => {
return parseInt(cred.credential_type) === type.value;
@@ -178,6 +179,8 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
let credDefers = [];
let job_template_obj = data;
+ let credentialGetPermissionDenied = false;
+
// get machine credential
if (data.related.credential) {
Rest.setUrl(data.related.credential);
@@ -188,8 +191,10 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
.catch(({data, status}) => {
if (status === 403) {
/* User doesn't have read access to the machine credential, so use summary_fields */
+ credentialGetPermissionDenied = true;
selectedCredentials.machine = job_template_obj.summary_fields.credential;
selectedCredentials.machine.credential_type = job_template_obj.summary_fields.credential.credential_type_id;
+ selectedCredentials.machine.readOnly = true;
} else {
ProcessErrors(
null, data, status, null,
@@ -212,8 +217,10 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
.catch(({data, status}) => {
if (status === 403) {
/* User doesn't have read access to the vault credential, so use summary_fields */
+ credentialGetPermissionDenied = true;
selectedCredentials.vault = job_template_obj.summary_fields.vault_credential;
selectedCredentials.vault.credential_type = job_template_obj.summary_fields.vault_credential.credential_type_id;
+ selectedCredentials.vault.readOnly = true;
} else {
ProcessErrors(
null, data, status, null,
@@ -237,9 +244,11 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
.catch(({data, status}) => {
if (status === 403) {
/* User doesn't have read access to the extra credentials, so use summary_fields */
+ credentialGetPermissionDenied = true;
selectedCredentials.extra = job_template_obj.summary_fields.extra_credentials;
_.map(selectedCredentials.extra, (cred) => {
cred.credential_type = cred.credential_type_id;
+ cred.readOnly = true;
return cred;
});
} else {
@@ -267,7 +276,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
.updateCredentialTags(selectedCredentials, credTypeOptions);
return [selectedCredentials, credTypes, credTypeOptions,
- credTags];
+ credTags, credentialGetPermissionDenied];
});
};
diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js
index dbaf88d3e6..564309fffe 100644
--- a/awx/ui/client/src/templates/main.js
+++ b/awx/ui/client/src/templates/main.js
@@ -137,6 +137,61 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.
},
resolve: {
edit: {
+ jobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors',
+ function($stateParams, TemplatesService, ProcessErrors) {
+ return TemplatesService.getJobTemplate($stateParams.job_template_id)
+ .then(function(res) {
+ return res.data;
+ }).catch(function(response){
+ ProcessErrors(null, response.data, response.status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to get job template. GET returned status: ' +
+ response.status
+ });
+ });
+ }],
+ canGetProject: ['Rest', 'ProcessErrors', 'jobTemplateData',
+ function(Rest, ProcessErrors, jobTemplateData) {
+ Rest.setUrl(jobTemplateData.related.project);
+ return Rest.get()
+ .then(() => {
+ return true;
+ })
+ .catch(({data, status}) => {
+ if (status === 403) {
+ /* User doesn't have read access to the project, no problem. */
+ } else {
+ ProcessErrors(null, data, status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to get project. GET returned ' +
+ 'status: ' + status
+ });
+ }
+
+ return false;
+ });
+ }],
+ canGetInventory: ['Rest', 'ProcessErrors', 'jobTemplateData',
+ function(Rest, ProcessErrors, jobTemplateData) {
+ Rest.setUrl(jobTemplateData.related.inventory);
+ return Rest.get()
+ .then(() => {
+ return true;
+ })
+ .catch(({data, status}) => {
+ if (status === 403) {
+ /* User doesn't have read access to the project, no problem. */
+ } else {
+ ProcessErrors(null, data, status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to get project. GET returned ' +
+ 'status: ' + status
+ });
+ }
+
+ return false;
+ });
+ }],
InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors',
function($stateParams, Rest, GetBasePath, ProcessErrors){
let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}/instance_groups/`;
@@ -155,32 +210,32 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.
});
});
}],
- availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService',
- function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) {
- return TemplatesService.getAllLabelOptions()
- .then(function(labels){
- return labels;
- }).catch(function(response){
- ProcessErrors(null, response.data, response.status, null, {
- hdr: 'Error!',
- msg: 'Failed to get labels. GET returned status: ' +
- response.status
- });
+ availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService',
+ function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) {
+ return TemplatesService.getAllLabelOptions()
+ .then(function(labels){
+ return labels;
+ }).catch(function(response){
+ ProcessErrors(null, response.data, response.status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to get labels. GET returned status: ' +
+ response.status
});
- }],
- selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors',
- function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) {
- return TemplatesService.getAllJobTemplateLabels($stateParams.job_template_id)
- .then(function(labels){
- return labels;
- }).catch(function(response){
- ProcessErrors(null, response.data, response.status, null, {
- hdr: 'Error!',
- msg: 'Failed to get workflow job template labels. GET returned status: ' +
- response.status
- });
+ });
+ }],
+ selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors',
+ function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) {
+ return TemplatesService.getAllJobTemplateLabels($stateParams.job_template_id)
+ .then(function(labels){
+ return labels;
+ }).catch(function(response){
+ ProcessErrors(null, response.data, response.status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to get workflow job template labels. GET returned status: ' +
+ response.status
});
- }]
+ });
+ }]
}
}
});
diff --git a/awx/ui/tests/spec/multi-credential/multi-credential.service-test.js b/awx/ui/tests/spec/multi-credential/multi-credential.service-test.js
index 9f47abd231..98eeb48070 100644
--- a/awx/ui/tests/spec/multi-credential/multi-credential.service-test.js
+++ b/awx/ui/tests/spec/multi-credential/multi-credential.service-test.js
@@ -1,4 +1,4 @@
-'use strict'
+'use strict';
describe('MultiCredentialService', () => {
let MultiCredentialService;
@@ -79,7 +79,7 @@ describe('MultiCredentialService', () => {
expect(equal).toBe(true);
});
- it('should return array of selected credentials (populated)', () => {
+ it('should return array of selected credentials (populated, not read only)', () => {
let creds = {
machine: {
credential_type: 1,
@@ -120,18 +120,92 @@ describe('MultiCredentialService', () => {
name: 'ssh',
id: 3,
postType: 'machine',
+ readOnly: false,
kind: 'SSH:'
},
{
name: 'aws',
id: 4,
postType: 'extra',
+ readOnly: false,
kind: 'Amazon Web Services:'
},
{
name: 'gce',
id: 5,
postType: 'extra',
+ readOnly: false,
+ kind: 'Google Compute Engine:'
+ }
+ ];
+
+ let actual = MultiCredentialService
+ .updateCredentialTags(creds, typeOpts);
+
+ let equal = _.isEqual(expected.sort(), actual.sort());
+
+ expect(equal).toBe(true);
+ });
+
+ it('should return array of selected credentials (populated, read only)', () => {
+ let creds = {
+ machine: {
+ credential_type: 1,
+ id: 3,
+ name: 'ssh',
+ readOnly: true
+ },
+ extra: [
+ {
+ credential_type: 2,
+ id: 4,
+ name: 'aws',
+ readOnly: true
+ },
+ {
+ credential_type: 3,
+ id: 5,
+ name: 'gce',
+ readOnly: true
+ }
+ ]
+ };
+
+ let typeOpts = [
+ {
+ name: 'SSH',
+ value: 1
+ },
+ {
+ name: 'Amazon Web Services',
+ value: 2
+ },
+ {
+ name: 'Google Compute Engine',
+ value: 3
+ }
+ ];
+
+ let expected = [
+ {
+ name: 'ssh',
+ id: 3,
+ postType: 'machine',
+ readOnly: true,
+ kind: 'SSH:'
+ },
+ {
+ name: 'aws',
+ id: 4,
+ postType: 'extra',
+ readOnly: true,
+ kind: 'Amazon Web Services:'
+ },
+ {
+ name: 'gce',
+ id: 5,
+ postType: 'extra',
+ readOnly: true,
kind: 'Google Compute Engine:'
}
];