diff --git a/awx/ui/client/src/access/roleList.partial.html b/awx/ui/client/src/access/roleList.partial.html index 1478c9dc37..365a20f061 100644 --- a/awx/ui/client/src/access/roleList.partial.html +++ b/awx/ui/client/src/access/roleList.partial.html @@ -1,13 +1,13 @@
-
{{ entry.name }} diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index b3425609fe..0f925812f6 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -839,6 +839,7 @@ var tower = angular.module('Tower', [ // If browser refresh, set the user_is_superuser value $rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser'); $rootScope.user_is_system_auditor = Authorization.getUserInfo('is_system_auditor'); + // state the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket) if (!_.contains($location.$$url, '/login')) { ConfigService.getConfig().then(function() { diff --git a/awx/ui/client/src/controllers/Credentials.js b/awx/ui/client/src/controllers/Credentials.js index 66b016dc50..aed17aee32 100644 --- a/awx/ui/client/src/controllers/Credentials.js +++ b/awx/ui/client/src/controllers/Credentials.js @@ -138,7 +138,6 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log, ReturnToCaller, ClearScope, GenerateList, SearchInit, PaginateInit, LookUpInit, OrganizationList, GetBasePath, GetChoices, Empty, KindChange, OwnerChange, FormSave, $state, CreateSelect2) { - ClearScope(); // Inject dynamic view @@ -337,6 +336,7 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, } ClearScope(); + var defaultUrl = GetBasePath('credentials'), generator = GenerateForm, form = CredentialForm, @@ -344,6 +344,7 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log, master = {}, id = $stateParams.credential_id, relatedSets = {}; + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.reset(); $scope.id = id; diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index 3f0e3bdcfd..0e2f46f736 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -13,7 +13,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $stateParams, ClearScope, LoadSchedulesScope, - LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait) { + LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait, $state) { ClearScope(); @@ -61,6 +61,11 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa } } jobs_scope = $scope.$new(true); + + jobs_scope.viewJob = function (id) { + $state.transitionTo('jobDetail', {id: id}); + }; + jobs_scope.showJobType = true; LoadJobsScope({ parent_scope: $scope, @@ -153,4 +158,4 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa JobsListController.$inject = ['$rootScope', '$log', '$scope', '$compile', '$stateParams', 'ClearScope', 'LoadSchedulesScope', 'LoadJobsScope', -'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait']; +'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', '$state']; diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 60692d581e..6c882cdd13 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -15,10 +15,16 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, ProjectList, GenerateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, ProjectUpdate, Refresh, Wait, GetChoices, Empty, - Find, GetProjectIcon, GetProjectToolTip, $filter, $state) { - + Find, GetProjectIcon, GetProjectToolTip, $filter, $state, rbacUiControlService) { ClearScope(); + $scope.canAdd = false; + + rbacUiControlService.canAdd('projects') + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + Wait('start'); var list = ProjectList, @@ -369,7 +375,7 @@ ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate', 'Refresh', 'Wait', 'GetChoices', 'Empty', 'Find', - 'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state' + 'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state', 'rbacUiControlService' ]; @@ -379,6 +385,15 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l OrganizationList, CredentialList, GetChoices, DebugForm, Wait, $state, CreateSelect2) { + Rest.setUrl(GetBasePath('projects')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a project.', 'alert-info'); + } + }); + ClearScope(); // Inject dynamic view diff --git a/awx/ui/client/src/controllers/Teams.js b/awx/ui/client/src/controllers/Teams.js index a8eed62bc5..ca30f84699 100644 --- a/awx/ui/client/src/controllers/Teams.js +++ b/awx/ui/client/src/controllers/Teams.js @@ -14,10 +14,16 @@ export function TeamsList($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, TeamList, GenerateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, SetTeamListeners, GetBasePath, - SelectionInit, Wait, $state, Refresh, $filter) { - + SelectionInit, Wait, $state, Refresh, $filter, rbacUiControlService) { ClearScope(); + $scope.canAdd = false; + + rbacUiControlService.canAdd('teams') + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + var list = TeamList, defaultUrl = GetBasePath('teams'), generator = GenerateList, @@ -126,7 +132,7 @@ TeamsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'TeamList', 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'SetTeamListeners', 'GetBasePath', 'SelectionInit', 'Wait', - '$state', 'Refresh', '$filter' + '$state', 'Refresh', '$filter', 'rbacUiControlService' ]; @@ -137,6 +143,15 @@ export function TeamsAdd($scope, $rootScope, $compile, $location, $log, ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //$scope. + Rest.setUrl(GetBasePath('teams')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a team.', 'alert-info'); + } + }); + // Inject dynamic view var defaultUrl = GetBasePath('teams'), form = TeamForm, diff --git a/awx/ui/client/src/controllers/Users.js b/awx/ui/client/src/controllers/Users.js index 103e1d1c84..0a00ed97c3 100644 --- a/awx/ui/client/src/controllers/Users.js +++ b/awx/ui/client/src/controllers/Users.js @@ -34,10 +34,16 @@ function user_type_sync($scope) { export function UsersList($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, UserList, GenerateList, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, - Wait, $state, Refresh, $filter) { - + Wait, $state, Refresh, $filter, rbacUiControlService) { ClearScope(); + $scope.canAdd = false; + + rbacUiControlService.canAdd('users') + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + var list = UserList, defaultUrl = GetBasePath('users'), generator = GenerateList, @@ -136,7 +142,7 @@ UsersList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'UserList', 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'SelectionInit', 'Wait', '$state', - 'Refresh', '$filter' + 'Refresh', '$filter', 'rbacUiControlService' ]; @@ -148,6 +154,15 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log, ReturnToCaller, ClearScope, GetBasePath, LookUpInit, OrganizationList, ResetForm, Wait, CreateSelect2, $state) { + Rest.setUrl(GetBasePath('users')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a user.', 'alert-info'); + } + }); + ClearScope(); // Inject dynamic view diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js index d503f53905..c75f30c8be 100644 --- a/awx/ui/client/src/forms/Credentials.js +++ b/awx/ui/client/src/forms/Credentials.js @@ -32,13 +32,15 @@ export default type: 'text', addRequired: true, editRequired: true, - autocomplete: false + autocomplete: false, + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, description: { label: 'Description', type: 'text', addRequired: false, - editRequired: false + editRequired: false, + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, organization: { addRequired: false, @@ -52,7 +54,8 @@ export default awPopOver: "

If no organization is given, the credential can only be used by the user that creates the credential. Organization admins and system administrators can assign an organization so that roles for the credential can be assigned to users and teams in that organization.

", dataTitle: 'Organization ', dataPlacement: 'bottom', - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, kind: { label: 'Type', @@ -83,7 +86,8 @@ export default dataTitle: 'Type', dataPlacement: 'right', dataContainer: "body", - hasSubForm: true + hasSubForm: true, + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, access_key: { label: 'Access Key', @@ -96,12 +100,13 @@ export default autocomplete: false, apiField: 'username', subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, secret_key: { label: 'Secret Key', type: 'sensitive', ngShow: "kind.value == 'aws'", - ngDisabled: "secret_key_ask", + ngDisabled: "secret_key_ask || !credential_obj.summary_fields.user_capabilities.edit", awRequiredWhen: { reqExpression: "aws_required", init: false @@ -123,7 +128,8 @@ export default dataTitle: 'STS Token', dataPlacement: 'right', dataContainer: "body", - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "host": { labelBind: 'hostLabel', @@ -139,7 +145,8 @@ export default reqExpression: 'host_required', init: false }, - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "subscription": { label: "Subscription ID", @@ -156,7 +163,8 @@ export default dataTitle: 'Subscription ID', dataPlacement: 'right', dataContainer: "body", - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "username": { labelBind: 'usernameLabel', @@ -168,7 +176,8 @@ export default init: false }, autocomplete: false, - subForm: "credentialSubForm" + subForm: "credentialSubForm", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "email_address": { labelBind: 'usernameLabel', @@ -183,7 +192,8 @@ export default dataTitle: 'Email', dataPlacement: 'right', dataContainer: "body", - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "api_key": { label: 'API Key', @@ -196,7 +206,8 @@ export default autocomplete: false, hasShowInputButton: true, clear: false, - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "password": { labelBind: 'passwordLabel', @@ -209,13 +220,14 @@ export default reqExpression: "password_required", init: false }, - subForm: "credentialSubForm" + subForm: "credentialSubForm", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "ssh_password": { label: 'Password', type: 'sensitive', ngShow: "kind.value == 'ssh'", - ngDisabled: "ssh_password_ask", + ngDisabled: "ssh_password_ask || !credential_obj.summary_fields.user_capabilities.edit", addRequired: false, editRequired: false, subCheckbox: { @@ -247,7 +259,8 @@ export default dataTitle: 'Private Key', dataPlacement: 'right', dataContainer: "body", - subForm: "credentialSubForm" + subForm: "credentialSubForm", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "ssh_key_unlock": { label: 'Private Key Passphrase', @@ -255,7 +268,7 @@ export default ngShow: "kind.value == 'ssh' || kind.value == 'scm'", addRequired: false, editRequired: false, - ngDisabled: "keyEntered === false || ssh_key_unlock_ask", + ngDisabled: "keyEntered === false || ssh_key_unlock_ask || !credential_obj.summary_fields.user_capabilities.edit", subCheckbox: { variable: 'ssh_key_unlock_ask', ngShow: "kind.value == 'ssh'", @@ -278,7 +291,8 @@ export default "sudo | su | pbrun | pfexec | runas
(defaults to sudo)

", dataPlacement: 'right', dataContainer: "body", - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "become_username": { labelBind: 'becomeUsernameLabel', @@ -287,13 +301,14 @@ export default addRequired: false, editRequired: false, autocomplete: false, - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "become_password": { labelBind: 'becomePasswordLabel', type: 'sensitive', ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ", - ngDisabled: "become_password_ask", + ngDisabled: "become_password_ask || !credential_obj.summary_fields.user_capabilities.edit", addRequired: false, editRequired: false, subCheckbox: { @@ -309,7 +324,8 @@ export default type: 'text', label: 'Client ID', subForm: 'credentialSubForm', - ngShow: "kind.value === 'azure_rm'" + ngShow: "kind.value === 'azure_rm'", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, secret:{ type: 'sensitive', @@ -317,20 +333,23 @@ export default autocomplete: false, label: 'Client Secret', subForm: 'credentialSubForm', - ngShow: "kind.value === 'azure_rm'" + ngShow: "kind.value === 'azure_rm'", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, tenant: { type: 'text', label: 'Tenant ID', subForm: 'credentialSubForm', - ngShow: "kind.value === 'azure_rm'" + ngShow: "kind.value === 'azure_rm'", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, authorize: { label: 'Authorize', type: 'checkbox', ngChange: "toggleCallback('host_config_key')", subForm: 'credentialSubForm', - ngShow: "kind.value === 'net'" + ngShow: "kind.value === 'net'", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, authorize_password: { label: 'Authorize Password', @@ -339,6 +358,7 @@ export default autocomplete: false, subForm: 'credentialSubForm', ngShow: "authorize && authorize !== 'false'", + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "project": { labelBind: 'projectLabel', @@ -355,7 +375,8 @@ export default reqExpression: 'project_required', init: false }, - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "domain": { labelBind: 'domainLabel', @@ -371,13 +392,14 @@ export default dataContainer: "body", addRequired: false, editRequired: false, - subForm: 'credentialSubForm' + subForm: 'credentialSubForm', + ngDisabled: '!credential_obj.summary_fields.user_capabilities.edit' }, "vault_password": { label: "Vault Password", type: 'sensitive', ngShow: "kind.value == 'ssh'", - ngDisabled: "vault_password_ask", + ngDisabled: "vault_password_ask || !credential_obj.summary_fields.user_capabilities.edit", addRequired: false, editRequired: false, subCheckbox: { @@ -394,11 +416,17 @@ export default buttons: { cancel: { ngClick: 'formCancel()', + ngShow: 'credential_obj.summary_fields.user_capabilities.edit' + }, + close: { + ngClick: 'formCancel()', + ngShow: '!credential_obj.summary_fields.user_capabilities.edit' }, save: { label: 'Save', ngClick: 'formSave()', //$scope.function to call on click, optional - ngDisabled: true //Disable when $pristine or $invalid, optional + ngDisabled: true, + ngShow: 'credential_obj.summary_fields.user_capabilities.edit' //Disable when $pristine or $invalid, optional } }, @@ -421,7 +449,8 @@ export default label: 'Add', awToolTip: 'Add a permission', actionClass: 'btn List-buttonSubmit', - buttonContent: '+ ADD' + buttonContent: '+ ADD', + ngShow: 'credential_obj.summary_fields.user_capabilities.edit' } }, diff --git a/awx/ui/client/src/forms/Groups.js b/awx/ui/client/src/forms/Groups.js index e3327d442e..40a5cd3301 100644 --- a/awx/ui/client/src/forms/Groups.js +++ b/awx/ui/client/src/forms/Groups.js @@ -26,14 +26,16 @@ export default type: 'text', addRequired: true, editRequired: true, - tab: 'properties' + tab: 'properties', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, description: { label: 'Description', type: 'text', addRequired: false, editRequired: false, - tab: 'properties' + tab: 'properties', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, variables: { label: 'Variables', @@ -65,7 +67,8 @@ export default ngChange: 'sourceChange(source)', addRequired: false, editRequired: false, - ngModel: 'source' + ngModel: 'source', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, credential: { label: 'Cloud Credential', @@ -77,7 +80,8 @@ export default awRequiredWhen: { reqExpression: "cloudCredentialRequired", init: "false" - } + }, + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, source_regions: { label: 'Regions', @@ -92,7 +96,8 @@ export default awPopOver: "

Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " + "or choose All to include all regions. Tower will only be updated with Hosts associated with the selected regions." + "

", - dataContainer: 'body' + dataContainer: 'body', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, instance_filters: { label: 'Instance Filters', @@ -112,7 +117,8 @@ export default "
tag:Name=test*
\n" + "

View the Describe Instances documentation " + "for a complete list of supported filters.

", - dataContainer: 'body' + dataContainer: 'body', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, group_by: { label: 'Only Group By', @@ -137,7 +143,8 @@ export default "
  • VPC ID: vpcs » vpc-5ca1ab1e
  • " + "
  • Tag None: tags » tag_none
  • " + "

    If blank, all groups above are created except Instance ID.

    ", - dataContainer: 'body' + dataContainer: 'body', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, inventory_script: { label : "Custom Inventory Script", @@ -149,6 +156,7 @@ export default addRequired: true, editRequired: true, ngRequired: "source && source.value === 'custom'", + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit', }, custom_variables: { id: 'custom_variables', @@ -269,7 +277,8 @@ export default dataTitle: 'Overwrite', dataContainer: 'body', dataPlacement: 'right', - labelClass: 'checkbox-options' + labelClass: 'checkbox-options', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, { name: 'overwrite_vars', label: 'Overwrite Variables', @@ -283,7 +292,8 @@ export default dataTitle: 'Overwrite Variables', dataContainer: 'body', dataPlacement: 'right', - labelClass: 'checkbox-options' + labelClass: 'checkbox-options', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }, { name: 'update_on_launch', label: 'Update on Launch', @@ -296,7 +306,8 @@ export default dataTitle: 'Update on Launch', dataContainer: 'body', dataPlacement: 'right', - labelClass: 'checkbox-options' + labelClass: 'checkbox-options', + ngDisabled: '!group_obj.summary_fields.user_capabilities.edit' }] }, update_cache_timeout: { @@ -321,11 +332,17 @@ export default buttons: { cancel: { - ngClick: 'formCancel()' + ngClick: 'formCancel()', + ngShow: 'group_obj.summary_fields.user_capabilities.edit' + }, + close: { + ngClick: 'formCancel()', + ngShow: '!group_obj.summary_fields.user_capabilities.edit' }, save: { ngClick: 'formSave()', - ngDisabled: true + ngDisabled: true, + ngShow: 'group_obj.summary_fields.user_capabilities.edit' } }, diff --git a/awx/ui/client/src/forms/Hosts.js b/awx/ui/client/src/forms/Hosts.js index 0da34d3e2e..3f01a6cf5c 100644 --- a/awx/ui/client/src/forms/Hosts.js +++ b/awx/ui/client/src/forms/Hosts.js @@ -46,13 +46,15 @@ export default "", dataTitle: 'Host Name', dataPlacement: 'right', - dataContainer: 'body' + dataContainer: 'body', + ngDisabled: '!host.summary_fields.user_capabilities.edit' }, description: { label: 'Description', type: 'text', addRequired: false, - editRequired: false + editRequired: false, + ngDisabled: '!host.summary_fields.user_capabilities.edit' }, variables: { label: 'Variables', @@ -83,10 +85,16 @@ export default buttons: { cancel: { ngClick: 'formCancel()', + ngShow: 'host.summary_fields.user_capabilities.edit' + }, + close: { + ngClick: 'formCancel()', + ngShow: '!host.summary_fields.user_capabilities.edit' }, save: { ngClick: 'formSave()', - ngDisabled: true + ngDisabled: true, + ngShow: 'host.summary_fields.user_capabilities.edit' } }, diff --git a/awx/ui/client/src/forms/Inventories.js b/awx/ui/client/src/forms/Inventories.js index 6467bf28d3..425c4d342d 100644 --- a/awx/ui/client/src/forms/Inventories.js +++ b/awx/ui/client/src/forms/Inventories.js @@ -26,14 +26,16 @@ export default type: 'text', addRequired: true, editRequired: true, - capitalize: false + capitalize: false, + ngDisabled: '!inventory_obj.summary_fields.user_capabilities.edit' }, inventory_description: { realName: 'description', label: 'Description', type: 'text', addRequired: false, - editRequired: false + editRequired: false, + ngDisabled: '!inventory_obj.summary_fields.user_capabilities.edit' }, organization: { label: 'Organization', @@ -44,7 +46,8 @@ export default awRequiredWhen: { reqExpression: "organizationrequired", init: "true" - } + }, + ngDisabled: '!inventory_obj.summary_fields.user_capabilities.edit' }, variables: { label: 'Variables', @@ -63,17 +66,24 @@ export default '

    View YAML examples at docs.ansible.com

    ', dataTitle: 'Inventory Variables', dataPlacement: 'right', - dataContainer: 'body' + dataContainer: 'body', + ngDisabled: '!inventory_obj.summary_fields.user_capabilities.edit' // TODO: get working } }, buttons: { cancel: { - ngClick: 'formCancel()' + ngClick: 'formCancel()', + ngShow: 'inventory_obj.summary_fields.user_capabilities.edit' + }, + close: { + ngClick: 'formCancel()', + ngHide: 'inventory_obj.summary_fields.user_capabilities.edit' }, save: { ngClick: 'formSave()', - ngDisabled: true + ngDisabled: true, + ngShow: 'inventory_obj.summary_fields.user_capabilities.edit' } }, @@ -94,7 +104,8 @@ export default label: 'Add', awToolTip: 'Add a permission', actionClass: 'btn List-buttonSubmit', - buttonContent: '+ ADD' + buttonContent: '+ ADD', + ngShow: 'inventory_obj.summary_fields.user_capabilities.edit' } }, diff --git a/awx/ui/client/src/forms/JobTemplates.js b/awx/ui/client/src/forms/JobTemplates.js index 5d56907c88..13e826492d 100644 --- a/awx/ui/client/src/forms/JobTemplates.js +++ b/awx/ui/client/src/forms/JobTemplates.js @@ -27,14 +27,16 @@ export default type: 'text', addRequired: true, editRequired: true, - column: 1 + column: 1, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, description: { label: 'Description', type: 'text', addRequired: false, editRequired: false, - column: 1 + column: 1, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, job_type: { label: 'Job Type', @@ -56,7 +58,8 @@ export default variable: 'ask_job_type_on_launch', ngShow: "!job_type.value || job_type.value !== 'scan'", text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, inventory: { label: 'Inventory', @@ -78,7 +81,8 @@ export default variable: 'ask_inventory_on_launch', ngShow: "!job_type.value || job_type.value !== 'scan'", text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, project: { label: 'Project', @@ -100,12 +104,13 @@ export default dataTitle: 'Project', dataPlacement: 'right', dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, playbook: { label: 'Playbook', type:'select', ngOptions: 'book for book in playbook_options track by book', - ngDisabled: "job_type.value === 'scan' && project_name === 'Default'", + ngDisabled: "(job_type.value === 'scan' && project_name === 'Default') || !job_template_obj.summary_fields.user_capabilities.edit", id: 'playbook-select', awRequiredWhen: { reqExpression: "playbookrequired", @@ -138,7 +143,8 @@ export default subCheckbox: { variable: 'ask_credential_on_launch', text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, cloud_credential: { label: 'Cloud Credential', @@ -153,7 +159,8 @@ export default "running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.

    ", dataTitle: 'Cloud Credential', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, network_credential: { label: 'Network Credential', @@ -167,7 +174,8 @@ export default awPopOver: "

    Network credentials are used by Ansible networking modules to connect to and manage networking devices.

    ", dataTitle: 'Network Credential', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, forks: { label: 'Forks', @@ -186,7 +194,8 @@ export default ' target=\"_blank\">ansible configuration file.

    ', dataTitle: 'Forks', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' // TODO: get working }, limit: { label: 'Limit', @@ -203,7 +212,8 @@ export default subCheckbox: { variable: 'ask_limit_on_launch', text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, verbosity: { label: 'Verbosity', @@ -216,7 +226,8 @@ export default awPopOver: "

    Control the level of output ansible will produce as the playbook executes.

    ", dataTitle: 'Verbosity', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, job_tags: { label: 'Job Tags', @@ -235,7 +246,8 @@ export default subCheckbox: { variable: 'ask_tags_on_launch', text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, skip_tags: { label: 'Skip Tags', @@ -254,7 +266,8 @@ export default subCheckbox: { variable: 'ask_skip_tags_on_launch', text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, checkbox_group: { label: 'Options', @@ -270,7 +283,8 @@ export default dataPlacement: 'right', dataTitle: 'Become Privilege Escalation', dataContainer: "body", - labelClass: 'stack-inline' + labelClass: 'stack-inline', + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, { name: 'allow_callbacks', label: 'Allow Provisioning Callbacks', @@ -284,7 +298,8 @@ export default dataPlacement: 'right', dataTitle: 'Allow Provisioning Callbacks', dataContainer: "body", - labelClass: 'stack-inline' + labelClass: 'stack-inline', + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }] }, callback_url: { @@ -299,7 +314,8 @@ export default awPopOverWatch: "callback_help", dataPlacement: 'top', dataTitle: 'Provisioning Callback URL', - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, host_config_key: { label: 'Host Config Key', @@ -312,7 +328,8 @@ export default awPopOverWatch: "callback_help", dataPlacement: 'right', dataTitle: "Host Config Key", - dataContainer: "body" + dataContainer: "body", + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, labels: { label: 'Labels', @@ -325,7 +342,8 @@ export default dataTitle: 'Labels', dataPlacement: 'right', awPopOver: "

    Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs in the Tower display.

    ", - dataContainer: 'body' + dataContainer: 'body', + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' }, variables: { label: 'Extra Variables', @@ -348,14 +366,15 @@ export default subCheckbox: { variable: 'ask_variables_on_launch', text: 'Prompt on launch' - } + }, + ngDisabled: '!job_template_obj.summary_fields.user_capabilities.edit' // TODO: get working } }, buttons: { //for now always generates @@ -27,6 +28,7 @@ diff --git a/awx/ui/client/src/notifications/add/add.controller.js b/awx/ui/client/src/notifications/add/add.controller.js index 0b58f2e257..122d822cf4 100644 --- a/awx/ui/client/src/notifications/add/add.controller.js +++ b/awx/ui/client/src/notifications/add/add.controller.js @@ -9,14 +9,23 @@ export default 'NotificationsFormObject', 'ProcessErrors', 'GetBasePath', 'Empty', 'GenerateForm', 'SearchInit' , 'PaginateInit', 'LookUpInit', 'OrganizationList', '$scope', '$state', 'CreateSelect2', 'GetChoices', - 'NotificationsTypeChange', 'ParseTypeChange', + 'NotificationsTypeChange', 'ParseTypeChange', 'Alert', function( $rootScope, pagination, $compile, SchedulerInit, Rest, Wait, NotificationsFormObject, ProcessErrors, GetBasePath, Empty, GenerateForm, SearchInit, PaginateInit, LookUpInit, OrganizationList, $scope, $state, CreateSelect2, GetChoices, - NotificationsTypeChange, ParseTypeChange + NotificationsTypeChange, ParseTypeChange, Alert ) { + Rest.setUrl(GetBasePath('projects')); + Rest.options() + .success(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a notification template.', 'alert-info'); + } + }); + var generator = GenerateForm, form = NotificationsFormObject, url = GetBasePath('notification_templates'); diff --git a/awx/ui/client/src/notifications/notification-templates-list/list.controller.js b/awx/ui/client/src/notifications/notification-templates-list/list.controller.js index 04265f1181..7fc279534b 100644 --- a/awx/ui/client/src/notifications/notification-templates-list/list.controller.js +++ b/awx/ui/client/src/notifications/notification-templates-list/list.controller.js @@ -8,12 +8,12 @@ export default [ '$rootScope','Wait', 'generateList', 'NotificationTemplatesList', 'GetBasePath' , 'SearchInit' , 'PaginateInit', 'Rest' , 'ProcessErrors', 'Prompt', '$state', 'GetChoices', 'Empty', 'Find', - 'ngToast', '$compile', '$filter', + 'ngToast', '$compile', '$filter', 'rbacUiControlService', function( $rootScope,Wait, GenerateList, NotificationTemplatesList, GetBasePath, SearchInit, PaginateInit, Rest, ProcessErrors, Prompt, $state, GetChoices, Empty, Find, ngToast, - $compile, $filter) { + $compile, $filter, rbacUiControlService) { var scope = $rootScope.$new(), defaultUrl = GetBasePath('notification_templates'), list = NotificationTemplatesList, @@ -24,6 +24,13 @@ export default scope: scope }); + scope.canAdd = false; + + rbacUiControlService.canAdd("notification_templates") + .then(function(canAdd) { + scope.canAdd = canAdd; + }); + scope.removePostRefresh = scope.$on('PostRefresh', function () { Wait('stop'); if (scope.notification_templates) { diff --git a/awx/ui/client/src/notifications/notificationTemplates.form.js b/awx/ui/client/src/notifications/notificationTemplates.form.js index cd0ff9d945..69128bbb18 100644 --- a/awx/ui/client/src/notifications/notificationTemplates.form.js +++ b/awx/ui/client/src/notifications/notificationTemplates.form.js @@ -27,13 +27,15 @@ export default function() { type: 'text', addRequired: true, editRequired: true, - capitalize: false + capitalize: false, + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, description: { label: 'Description', type: 'text', addRequired: false, - editRequired: false + editRequired: false, + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, organization: { label: 'Organization', @@ -44,7 +46,8 @@ export default function() { awRequiredWhen: { reqExpression: "organizationrequired", init: "true" - } + }, + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, notification_type: { label: 'Type', @@ -54,13 +57,15 @@ export default function() { class: 'NotificationsForm-typeSelect', ngOptions: 'type.label for type in notification_type_options track by type.value', ngChange: 'typeChange()', - hasSubForm: true + hasSubForm: true, + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, username: { label: 'Username', type: 'text', ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, host: { @@ -71,7 +76,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, sender: { label: 'Sender Email', @@ -81,7 +87,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, recipients: { label: 'Recipient List', @@ -97,7 +104,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, password: { labelBind: 'passwordLabel', @@ -108,7 +116,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, port: { labelBind: 'portLabel', @@ -122,7 +131,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'email' || notification_type.value == 'irc'", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, channels: { label: 'Destination Channels', @@ -138,7 +148,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'slack'", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, rooms: { label: 'Destination Channels', @@ -154,7 +165,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'hipchat'", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, token: { labelBind: 'tokenLabel', @@ -165,7 +177,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'slack' || notification_type.value == 'pagerduty' || notification_type.value == 'hipchat'", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, account_token: { label: 'Account Token', @@ -176,7 +189,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, from_number: { label: 'Source Phone Number', @@ -188,7 +202,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, to_numbers: { label: 'Destination SMS Number', @@ -204,7 +219,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, account_sid: { label: 'Account SID', @@ -214,7 +230,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, subdomain: { label: 'Pagerduty subdomain', @@ -224,7 +241,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'pagerduty' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, service_key: { label: 'API Service/Integration Key', @@ -234,7 +252,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'pagerduty' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, client_name: { label: 'Client Identifier', @@ -244,7 +263,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'pagerduty' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, message_from: { label: 'Label to be shown with notification', @@ -254,7 +274,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, api_url: { label: 'API URL', @@ -265,7 +286,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, color: { label: 'Notification Color', @@ -277,13 +299,15 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, notify: { label: 'Notify Channel', type: 'checkbox', ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, url: { label: 'Target URL', @@ -293,7 +317,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'webhook' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, headers: { label: 'HTTP Headers', @@ -313,7 +338,8 @@ export default function() { '

    ', dataPlacement: 'right', ngShow: "notification_type.value == 'webhook' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, server: { label: 'IRC Server Address', @@ -323,7 +349,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'irc' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, nickname: { label: 'IRC Nick', @@ -333,7 +360,8 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'irc' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, targets: { label: 'Destination Channels or Users', @@ -349,13 +377,15 @@ export default function() { init: "false" }, ngShow: "notification_type.value == 'irc' ", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, use_ssl: { label: 'SSL Connection', type: 'checkbox', ngShow: "notification_type.value == 'irc'", - subForm: 'typeSubForm' + subForm: 'typeSubForm', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, checkbox_group: { label: 'Options', @@ -367,13 +397,15 @@ export default function() { label: 'Use TLS', type: 'checkbox', ngShow: "notification_type.value == 'email' ", - labelClass: 'checkbox-options stack-inline' + labelClass: 'checkbox-options stack-inline', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }, { name: 'use_ssl', label: 'Use SSL', type: 'checkbox', ngShow: "notification_type.value == 'email'", - labelClass: 'checkbox-options stack-inline' + labelClass: 'checkbox-options stack-inline', + ngDisabled: '!notification_template.summary_fields.user_capabilities.edit' }] } }, @@ -381,9 +413,15 @@ export default function() { buttons: { //for now always generates @@ -31,13 +32,23 @@
    +
    -
    +
    -
    +
    -
    +
    PREVIEW
    @@ -56,13 +56,13 @@ {{question.question_description}}
    - +   -
    +
    @@ -80,9 +80,10 @@
    - - - + + + +
    diff --git a/awx/ui/client/src/scheduler/scheduler.controller.js b/awx/ui/client/src/scheduler/scheduler.controller.js index dffa82098e..db8eaa7b29 100644 --- a/awx/ui/client/src/scheduler/scheduler.controller.js +++ b/awx/ui/client/src/scheduler/scheduler.controller.js @@ -14,11 +14,11 @@ export default [ '$scope', '$compile', '$location', '$stateParams', 'SchedulesList', 'Rest', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'Wait', - 'Find', 'LoadSchedulesScope', 'GetChoices', '$q', '$state', + 'Find', 'LoadSchedulesScope', 'GetChoices', '$q', '$state', 'rbacUiControlService', function ($scope, $compile, $location, $stateParams, SchedulesList, Rest, ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices, - $q, $state) { + $q, $state, rbacUiControlService) { var schedList = _.cloneDeep(SchedulesList); ClearScope(); @@ -48,6 +48,14 @@ export default [ } $scope.removeParentLoaded = $scope.$on('ParentLoaded', function() { url += "schedules/"; + + $scope.canAdd = false; + + rbacUiControlService.canAdd(url) + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + schedList.well = true; // include name of item in listTitle diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index 48dcbb764b..e1ffc75589 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -9,7 +9,6 @@ export default ['$compile', '$filter', '$state', '$stateParams', 'AddSchedule', 'Rest', 'ParamPass', function($compile, $filter, $state, $stateParams, AddSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParamPass) { - $scope.processSchedulerEndDt = function(){ // set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight var dt = new Date($scope.schedulerUTCTime); diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 66c4f750b8..821857e3f6 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -865,6 +865,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += ">"; html += " -1) { + Rest.setUrl(apiPath); + } else { + Rest.setUrl(GetBasePath(apiPath)); + } + + Wait("start"); + Rest.options() + .success(function(data) { + if (data.actions.POST) { + canAddVal.resolve(true); + } else { + canAddVal.reject(false); + } + Wait("stop"); + }); + + return canAddVal.promise; + }; + }]);