diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js index 0b18e937c8..ac1a315882 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js @@ -4,23 +4,22 @@ * All Rights Reserved *************************************************/ -export default ['$state', '$stateParams', '$scope', 'ParseVariableString', - 'rbacUiControlService', 'ToJSON', 'ParseTypeChange', 'GroupsService', +export default ['$state', '$scope', 'ParseVariableString', 'ParseTypeChange', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'SourcesService', 'inventoryData', 'inventorySourcesOptions', 'Empty', 'Wait', 'Rest', 'Alert', '$rootScope', 'i18n', 'InventoryHostsStrings', - 'ProcessErrors', 'inventorySource', - function($state, $stateParams, $scope, ParseVariableString, - rbacUiControlService, ToJSON,ParseTypeChange, GroupsService, + 'ProcessErrors', 'inventorySource', 'isNotificationAdmin', + function($state, $scope, ParseVariableString, ParseTypeChange, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, SourcesService, inventoryData, inventorySourcesOptions, Empty, Wait, Rest, Alert, $rootScope, i18n, InventoryHostsStrings, - ProcessErrors, inventorySource) { + ProcessErrors, inventorySource, isNotificationAdmin) { const inventorySourceData = inventorySource.get(); $scope.projectBasePath = GetBasePath('projects') + '?not__status=never updated'; $scope.canAdd = inventorySourcesOptions.actions.POST; + $scope.isNotificationAdmin = isNotificationAdmin || false; // instantiate expected $scope values from inventorySourceData _.assign($scope, {credential: inventorySourceData.credential}, diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js index dbc2337357..4f3fa52a5c 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js @@ -24,6 +24,20 @@ export default { inventorySourcesOptions: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { return InventoriesService.inventorySourcesOptions($stateParams.inventory_id) .then((response) => response.data); + }], + isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', + function(Rest, ProcessErrors, GetBasePath, i18n) { + Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); + return Rest.get() + .then(({data}) => { + return data.count > 0; + }) + .catch(({data, status}) => { + ProcessErrors(null, data, status, null, { + hdr: i18n._('Error!'), + msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status + }); + }); }] } }; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js index be72be5c19..2a2c01d389 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js @@ -1,443 +1,429 @@ /************************************************* - * Copyright (c) 2015 Ansible, Inc. + * Copyright (c) 2019 Ansible, Inc. * * All Rights Reserved *************************************************/ - /** - * @ngdoc function - * @name forms.function:Groups - * @description This form is for adding/editing a Group on the inventory page -*/ - export default ['NotificationsList', 'i18n', function(NotificationsList, i18n){ var notifications_object = { - name: 'notifications', - index: false, - basePath: "notification_templates", - include: "NotificationsList", - title: i18n._('Notifications'), - iterator: 'notification', - ngIf: "!(inventory_source_obj.source === undefined || inventory_source_obj.source === '')", generateList: true, + include: "NotificationsList", + ngIf: "(current_user.is_superuser || isOrgAdmin || isNotificationAdmin) && !(inventory_source_obj.source === undefined || inventory_source_obj.source === '')", ngClick: "$state.go('inventories.edit.inventory_sources.edit.notifications')" - // search: { - // "or__job__inventory": '' - // } }; let clone = _.clone(NotificationsList); notifications_object = angular.extend(clone, notifications_object); -return { - addTitle: i18n._('CREATE SOURCE'), - editTitle: '{{ name }}', - showTitle: true, - name: 'inventory_source', - basePath: 'inventory_sources', - parent: 'inventories.edit.sources', - // the parent node this generated state definition tree expects to attach to - stateTree: 'inventories', - tabs: true, - // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab - // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit - activeEditState: 'inventories.edit.inventory_sources.edit', - detailsClick: "$state.go('inventories.edit.inventory_sources.edit')", - well: false, - subFormTitles: { - sourceSubForm: i18n._('Source Details'), - }, - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - tab: 'properties' + return { + addTitle: i18n._('CREATE SOURCE'), + editTitle: '{{ name }}', + showTitle: true, + name: 'inventory_source', + basePath: 'inventory_sources', + parent: 'inventories.edit.sources', + // the parent node this generated state definition tree expects to attach to + stateTree: 'inventories', + tabs: true, + // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab + // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit + activeEditState: 'inventories.edit.inventory_sources.edit', + detailsClick: "$state.go('inventories.edit.inventory_sources.edit')", + well: false, + subFormTitles: { + sourceSubForm: i18n._('Source Details'), }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - tab: 'properties' - }, - source: { - label: i18n._('Source'), - type: 'select', - required: true, - ngOptions: 'source.label for source in source_type_options track by source.value', - ngChange: 'sourceChange(source)', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - ngModel: 'source', - hasSubForm: true - }, - credential: { - label: i18n._('Credential'), - type: 'lookup', - list: 'CredentialList', - basePath: 'credentials', - ngShow: "source && source.value !== ''", - sourceModel: 'credential', - sourceField: 'name', - ngClick: 'lookupCredential()', - awRequiredWhen: { - reqExpression: "cloudCredentialRequired", - init: "false" + fields: { + name: { + label: i18n._('Name'), + type: 'text', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + required: true, + tab: 'properties' }, - subForm: 'sourceSubForm', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - watchBasePath: "credentialBasePath" - }, - project: { - // initializes a default value for this search param - // search params with default values set will not generate user-interactable search tags - label: i18n._('Project'), - type: 'lookup', - list: 'ProjectList', - basePath: 'projects', - ngShow: "source && source.value === 'scm'", - sourceModel: 'project', - sourceField: 'name', - ngClick: 'lookupProject()', - awRequiredWhen: { - reqExpression: "source && source.value === 'scm'", - init: "false" + description: { + label: i18n._('Description'), + type: 'text', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + tab: 'properties' }, - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - watchBasePath: "projectBasePath", - subForm: 'sourceSubForm' - }, - inventory_file: { - label: i18n._('Inventory File'), - type:'select', - defaultText: i18n._('Choose an inventory file'), - ngOptions: 'file for file in inventory_files track by file', - ngShow: "source && source.value === 'scm'", - ngDisabled: "!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd) || disableInventoryFileBecausePermissionDenied", - id: 'inventory-file-select', - awRequiredWhen: { - reqExpression: "source && source.value === 'scm'", - init: "true" + source: { + label: i18n._('Source'), + type: 'select', + required: true, + ngOptions: 'source.label for source in source_type_options track by source.value', + ngChange: 'sourceChange(source)', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + ngModel: 'source', + hasSubForm: true }, - column: 1, - awPopOver: "

" + i18n._("Select the inventory file to be synced by this source. " + - "You can select from the dropdown or enter a file within the input.") + "

", - dataTitle: i18n._('Inventory File'), - dataPlacement: 'right', - dataContainer: "body", - includeInventoryFileNotFoundError: true, - subForm: 'sourceSubForm' - }, - source_regions: { - label: i18n._('Regions'), - type: 'select', - ngOptions: 'source.label for source in source_region_choices track by source.value', - multiSelect: true, - ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure_rm')", - dataTitle: i18n._('Source Regions'), - dataPlacement: 'right', - awPopOver: "

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

", - dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - instance_filters: { - label: i18n._("Instance Filters"), - type: 'text', - ngShow: "source && (source.value == 'ec2' || source.value == 'vmware' || source.value == 'tower')", - dataTitle: i18n._('Instance Filters'), - dataPlacement: 'right', - awPopOverWatch: 'instanceFilterPopOver', - awPopOver: '{{ instanceFilterPopOver }}', - dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - group_by: { - label: i18n._('Only Group By'), - type: 'select', - ngShow: "source && (source.value == 'ec2' || source.value == 'vmware')", - ngOptions: 'source.label for source in group_by_choices track by source.value', - multiSelect: true, - dataTitle: i18n._("Only Group By"), - dataPlacement: 'right', - awPopOverWatch: 'groupByPopOver', - awPopOver: '{{ groupByPopOver }}', - dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - inventory_script: { - label : i18n._("Custom Inventory Script"), - type: 'lookup', - basePath: 'inventory_scripts', - list: 'InventoryScriptsList', - ngShow: "source && source.value === 'custom'", - sourceModel: 'inventory_script', - sourceField: 'name', - awRequiredWhen: { - reqExpression: "source && source.value === 'custom'", - init: "false" + credential: { + label: i18n._('Credential'), + type: 'lookup', + list: 'CredentialList', + basePath: 'credentials', + ngShow: "source && source.value !== ''", + sourceModel: 'credential', + sourceField: 'name', + ngClick: 'lookupCredential()', + awRequiredWhen: { + reqExpression: "cloudCredentialRequired", + init: "false" + }, + subForm: 'sourceSubForm', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + watchBasePath: "credentialBasePath" }, - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - custom_variables: { - id: 'custom_variables', - label: i18n._('Environment Variables'), //"{{vars_label}}" , - ngShow: "source && source.value=='custom' || source.value === 'scm'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Environment Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Provide environment variables to pass to the custom inventory script.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - ec2_variables: { - id: 'ec2_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'ec2'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables ") + - "" + - i18n._("view ec2.ini in the Ansible github repo.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - vmware_variables: { - id: 'vmware_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'vmware'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables ") + - "" + - i18n._("view vmware_inventory.ini in the Ansible github repo.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - openstack_variables: { - id: 'openstack_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'openstack'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: i18n._(`Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration - - view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - cloudforms_variables: { - id: 'cloudforms_variables', - label: i18n._('Source Variables'), - ngShow: "source && source.value == 'cloudforms'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: i18n._(`Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration - - view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - satellite6_variables: { - id: 'satellite6_variables', - label: i18n._('Source Variables'), - ngShow: "source && source.value == 'satellite6'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: i18n._(`Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration - - view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - azure_rm_variables: { - id: 'azure_rm_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'azure_rm'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Override variables found in azure_rm.ini and used by the inventory update script. For a detailed description of these variables ") + - "" + - i18n._("view azure_rm.ini in the Ansible github repo.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - verbosity: { - label: i18n._('Verbosity'), - type: 'select', - ngOptions: 'v.label for v in verbosity_options track by v.value', - ngShow: "source && (source.value !== '' && source.value !== null)", - disableChooseOption: true, - column: 1, - awPopOver: "

" + i18n._("Control the level of output ansible will produce for inventory source update jobs.") + "

", - dataTitle: i18n._('Verbosity'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - checkbox_group: { - label: i18n._('Update Options'), - type: 'checkbox_group', - ngShow: "source && (source.value !== '' && source.value !== null)", - subForm: 'sourceSubForm', - fields: [{ - name: 'overwrite', - label: i18n._('Overwrite'), - type: 'checkbox', - ngShow: "source.value !== '' && source.value !== null", - awPopOver: "

" + i18n._("If checked, any hosts and groups that were previously present on the external source but are now removed will be removed from the Tower inventory. Hosts and groups that were not managed by the inventory source will be promoted to the next manually created group or if there is no manually created group to promote them into, they will be left in the \"all\" default group for the inventory.") + '

' + - i18n._("When not checked, local child hosts and groups not found on the external source will remain untouched by the inventory update process.") + "

", - dataTitle: i18n._('Overwrite'), - dataContainer: 'body', + project: { + // initializes a default value for this search param + // search params with default values set will not generate user-interactable search tags + label: i18n._('Project'), + type: 'lookup', + list: 'ProjectList', + basePath: 'projects', + ngShow: "source && source.value === 'scm'", + sourceModel: 'project', + sourceField: 'name', + ngClick: 'lookupProject()', + awRequiredWhen: { + reqExpression: "source && source.value === 'scm'", + init: "false" + }, + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + watchBasePath: "projectBasePath", + subForm: 'sourceSubForm' + }, + inventory_file: { + label: i18n._('Inventory File'), + type:'select', + defaultText: i18n._('Choose an inventory file'), + ngOptions: 'file for file in inventory_files track by file', + ngShow: "source && source.value === 'scm'", + ngDisabled: "!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd) || disableInventoryFileBecausePermissionDenied", + id: 'inventory-file-select', + awRequiredWhen: { + reqExpression: "source && source.value === 'scm'", + init: "true" + }, + column: 1, + awPopOver: "

" + i18n._("Select the inventory file to be synced by this source. " + + "You can select from the dropdown or enter a file within the input.") + "

", + dataTitle: i18n._('Inventory File'), dataPlacement: 'right', - ngDisabled: "(!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd))" - }, { - name: 'overwrite_vars', - label: i18n._('Overwrite Variables'), - type: 'checkbox', - ngShow: "source.value !== '' && source.value !== null", - awPopOver: "

" + i18n._("If checked, all variables for child groups and hosts will be removed and replaced by those found on the external source.") + '

' + - i18n._("When not checked, a merge will be performed, combining local variables with those found on the external source.") + "

", - dataTitle: i18n._('Overwrite Variables'), - dataContainer: 'body', + dataContainer: "body", + includeInventoryFileNotFoundError: true, + subForm: 'sourceSubForm' + }, + source_regions: { + label: i18n._('Regions'), + type: 'select', + ngOptions: 'source.label for source in source_region_choices track by source.value', + multiSelect: true, + ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure_rm')", + dataTitle: i18n._('Source Regions'), dataPlacement: 'right', - ngDisabled: "(!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd))" - }, { - name: 'update_on_launch', - label: i18n._('Update on Launch'), - type: 'checkbox', - ngShow: "source.value !== '' && source.value !== null", - awPopOver: "

" + i18n._("Each time a job runs using this inventory, " + - "refresh the inventory from the selected source before executing job tasks.") + "

", - dataTitle: i18n._('Update on Launch'), + awPopOver: "

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

", dataContainer: 'body', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' + }, + instance_filters: { + label: i18n._("Instance Filters"), + type: 'text', + ngShow: "source && (source.value == 'ec2' || source.value == 'vmware' || source.value == 'tower')", + dataTitle: i18n._('Instance Filters'), dataPlacement: 'right', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }, { - name: 'update_on_project_update', - label: i18n._('Update on Project Change'), - type: 'checkbox', - ngShow: "source.value === 'scm'", - awPopOver: "

" + i18n._("After every project update where the SCM revision changes, " + - "refresh the inventory from the selected source before executing job tasks. " + - "This is intended for static content, like the Ansible inventory .ini file format.") + "

", - dataTitle: i18n._('Update on Project Update'), + awPopOverWatch: 'instanceFilterPopOver', + awPopOver: '{{ instanceFilterPopOver }}', dataContainer: 'body', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' + }, + group_by: { + label: i18n._('Only Group By'), + type: 'select', + ngShow: "source && (source.value == 'ec2' || source.value == 'vmware')", + ngOptions: 'source.label for source in group_by_choices track by source.value', + multiSelect: true, + dataTitle: i18n._("Only Group By"), dataPlacement: 'right', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }] + awPopOverWatch: 'groupByPopOver', + awPopOver: '{{ groupByPopOver }}', + dataContainer: 'body', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' + }, + inventory_script: { + label : i18n._("Custom Inventory Script"), + type: 'lookup', + basePath: 'inventory_scripts', + list: 'InventoryScriptsList', + ngShow: "source && source.value === 'custom'", + sourceModel: 'inventory_script', + sourceField: 'name', + awRequiredWhen: { + reqExpression: "source && source.value === 'custom'", + init: "false" + }, + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' + }, + custom_variables: { + id: 'custom_variables', + label: i18n._('Environment Variables'), //"{{vars_label}}" , + ngShow: "source && source.value=='custom' || source.value === 'scm'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Environment Variables"), + dataPlacement: 'right', + awPopOver: "

" + i18n._("Provide environment variables to pass to the custom inventory script.") + "

" + + "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + + i18n._("JSON:") + "
\n" + + "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + + i18n._("YAML:") + "
\n" + + "
---
somevar: somevalue
password: magic
\n" + + "

" + i18n._("View JSON examples at ") + 'www.json.org

' + + "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + ec2_variables: { + id: 'ec2_variables', + label: i18n._('Source Variables'), //"{{vars_label}}" , + ngShow: "source && source.value == 'ec2'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Source Variables"), + dataPlacement: 'right', + awPopOver: "

" + i18n._("Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables ") + + "" + + i18n._("view ec2.ini in the Ansible github repo.") + "

" + + "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + + i18n._("JSON:") + "
\n" + + "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + + i18n._("YAML:") + "
\n" + + "
---
somevar: somevalue
password: magic
\n" + + "

" + i18n._("View JSON examples at ") + 'www.json.org

' + + "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + vmware_variables: { + id: 'vmware_variables', + label: i18n._('Source Variables'), //"{{vars_label}}" , + ngShow: "source && source.value == 'vmware'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Source Variables"), + dataPlacement: 'right', + awPopOver: "

" + i18n._("Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables ") + + "" + + i18n._("view vmware_inventory.ini in the Ansible github repo.") + "

" + + "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + + i18n._("JSON:") + "
\n" + + "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + + i18n._("YAML:") + "
\n" + + "
---
somevar: somevalue
password: magic
\n" + + "

" + i18n._("View JSON examples at ") + 'www.json.org

' + + "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + openstack_variables: { + id: 'openstack_variables', + label: i18n._('Source Variables'), //"{{vars_label}}" , + ngShow: "source && source.value == 'openstack'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Source Variables"), + dataPlacement: 'right', + awPopOver: i18n._(`Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration + + view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + cloudforms_variables: { + id: 'cloudforms_variables', + label: i18n._('Source Variables'), + ngShow: "source && source.value == 'cloudforms'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Source Variables"), + dataPlacement: 'right', + awPopOver: i18n._(`Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration + + view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + satellite6_variables: { + id: 'satellite6_variables', + label: i18n._('Source Variables'), + ngShow: "source && source.value == 'satellite6'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Source Variables"), + dataPlacement: 'right', + awPopOver: i18n._(`Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration + + view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + azure_rm_variables: { + id: 'azure_rm_variables', + label: i18n._('Source Variables'), //"{{vars_label}}" , + ngShow: "source && source.value == 'azure_rm'", + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + 'default': '---', + parseTypeName: 'envParseType', + dataTitle: i18n._("Source Variables"), + dataPlacement: 'right', + awPopOver: "

" + i18n._("Override variables found in azure_rm.ini and used by the inventory update script. For a detailed description of these variables ") + + "" + + i18n._("view azure_rm.ini in the Ansible github repo.") + "

" + + "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + + i18n._("JSON:") + "
\n" + + "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + + i18n._("YAML:") + "
\n" + + "
---
somevar: somevalue
password: magic
\n" + + "

" + i18n._("View JSON examples at ") + 'www.json.org

' + + "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', + dataContainer: 'body', + subForm: 'sourceSubForm' + }, + verbosity: { + label: i18n._('Verbosity'), + type: 'select', + ngOptions: 'v.label for v in verbosity_options track by v.value', + ngShow: "source && (source.value !== '' && source.value !== null)", + disableChooseOption: true, + column: 1, + awPopOver: "

" + i18n._("Control the level of output ansible will produce for inventory source update jobs.") + "

", + dataTitle: i18n._('Verbosity'), + dataPlacement: 'right', + dataContainer: "body", + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' + }, + checkbox_group: { + label: i18n._('Update Options'), + type: 'checkbox_group', + ngShow: "source && (source.value !== '' && source.value !== null)", + subForm: 'sourceSubForm', + fields: [{ + name: 'overwrite', + label: i18n._('Overwrite'), + type: 'checkbox', + ngShow: "source.value !== '' && source.value !== null", + awPopOver: "

" + i18n._("If checked, any hosts and groups that were previously present on the external source but are now removed will be removed from the Tower inventory. Hosts and groups that were not managed by the inventory source will be promoted to the next manually created group or if there is no manually created group to promote them into, they will be left in the \"all\" default group for the inventory.") + '

' + + i18n._("When not checked, local child hosts and groups not found on the external source will remain untouched by the inventory update process.") + "

", + dataTitle: i18n._('Overwrite'), + dataContainer: 'body', + dataPlacement: 'right', + ngDisabled: "(!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd))" + }, { + name: 'overwrite_vars', + label: i18n._('Overwrite Variables'), + type: 'checkbox', + ngShow: "source.value !== '' && source.value !== null", + awPopOver: "

" + i18n._("If checked, all variables for child groups and hosts will be removed and replaced by those found on the external source.") + '

' + + i18n._("When not checked, a merge will be performed, combining local variables with those found on the external source.") + "

", + dataTitle: i18n._('Overwrite Variables'), + dataContainer: 'body', + dataPlacement: 'right', + ngDisabled: "(!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd))" + }, { + name: 'update_on_launch', + label: i18n._('Update on Launch'), + type: 'checkbox', + ngShow: "source.value !== '' && source.value !== null", + awPopOver: "

" + i18n._("Each time a job runs using this inventory, " + + "refresh the inventory from the selected source before executing job tasks.") + "

", + dataTitle: i18n._('Update on Launch'), + dataContainer: 'body', + dataPlacement: 'right', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + }, { + name: 'update_on_project_update', + label: i18n._('Update on Project Change'), + type: 'checkbox', + ngShow: "source.value === 'scm'", + awPopOver: "

" + i18n._("After every project update where the SCM revision changes, " + + "refresh the inventory from the selected source before executing job tasks. " + + "This is intended for static content, like the Ansible inventory .ini file format.") + "

", + dataTitle: i18n._('Update on Project Update'), + dataContainer: 'body', + dataPlacement: 'right', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + }] + }, + update_cache_timeout: { + label: i18n._("Cache Timeout") + " " + i18n._("(seconds)") + "", + id: 'source-cache-timeout', + type: 'number', + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + integer: true, + min: 0, + ngShow: "source && source.value !== '' && update_on_launch", + spinner: true, + "default": 0, + awPopOver: "

" + i18n._("Time in seconds to consider an inventory sync to be current. " + + "During job runs and callbacks the task system will evaluate the timestamp of the latest sync. " + + "If it is older than Cache Timeout, it is not considered current, and a new inventory sync will be performed.") + "

", + dataTitle: i18n._('Cache Timeout'), + dataPlacement: 'right', + dataContainer: "body", + subForm: 'sourceSubForm' + } }, - update_cache_timeout: { - label: i18n._("Cache Timeout") + " " + i18n._("(seconds)") + "", - id: 'source-cache-timeout', - type: 'number', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - integer: true, - min: 0, - ngShow: "source && source.value !== '' && update_on_launch", - spinner: true, - "default": 0, - awPopOver: "

" + i18n._("Time in seconds to consider an inventory sync to be current. " + - "During job runs and callbacks the task system will evaluate the timestamp of the latest sync. " + - "If it is older than Cache Timeout, it is not considered current, and a new inventory sync will be performed.") + "

", - dataTitle: i18n._('Cache Timeout'), - dataPlacement: 'right', - dataContainer: "body", - subForm: 'sourceSubForm' - } - }, - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + buttons: { + cancel: { + ngClick: 'formCancel()', + ngShow: '(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + }, + close: { + ngClick: 'formCancel()', + ngShow: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + }, + save: { + ngClick: 'formSave()', + ngDisabled: true, + ngShow: '(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + } }, - close: { - ngClick: 'formCancel()', - ngShow: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - related: { - notifications: notifications_object, - schedules: { - title: i18n._('Schedules'), - skipGenerator: true, - ngClick: "$state.go('inventories.edit.inventory_sources.edit.schedules')" + related: { + notifications: notifications_object, + schedules: { + title: i18n._('Schedules'), + skipGenerator: true, + ngClick: "$state.go('inventories.edit.inventory_sources.edit.schedules')" + } } - } -}; + }; }]; diff --git a/awx/ui/client/src/projects/edit/projects-edit.controller.js b/awx/ui/client/src/projects/edit/projects-edit.controller.js index 8169f297d8..54f6493326 100644 --- a/awx/ui/client/src/projects/edit/projects-edit.controller.js +++ b/awx/ui/client/src/projects/edit/projects-edit.controller.js @@ -5,12 +5,12 @@ *************************************************/ export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest', - 'Alert', 'ProcessErrors', 'GenerateForm', 'Prompt', + 'Alert', 'ProcessErrors', 'GenerateForm', 'Prompt', 'isNotificationAdmin', 'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty', 'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification', 'i18n', 'OrgAdminLookup', 'ConfigData', 'scmCredentialType', function($scope, $rootScope, $stateParams, ProjectsForm, Rest, Alert, - ProcessErrors, GenerateForm, Prompt, GetBasePath, + ProcessErrors, GenerateForm, Prompt, isNotificationAdmin, GetBasePath, GetProjectPath, Authorization, GetChoices, Empty, Wait, ProjectUpdate, $state, CreateSelect2, ToggleNotification, i18n, OrgAdminLookup, ConfigData, scmCredentialType) { @@ -27,6 +27,7 @@ export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest', $scope.base_dir = ''; const virtualEnvs = ConfigData.custom_virtualenvs || []; $scope.custom_virtualenvs_options = virtualEnvs; + $scope.isNotificationAdmin = isNotificationAdmin || false; } $scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) { diff --git a/awx/ui/client/src/projects/main.js b/awx/ui/client/src/projects/main.js index 206df6923b..d788f1f064 100644 --- a/awx/ui/client/src/projects/main.js +++ b/awx/ui/client/src/projects/main.js @@ -90,6 +90,24 @@ angular.module('Projects', []) breadcrumbs: { edit: '{{breadcrumb.project_name}}' }, + resolve: { + edit: { + isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', + function(Rest, ProcessErrors, GetBasePath, i18n) { + Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); + return Rest.get() + .then(({data}) => { + return data.count > 0; + }) + .catch(({data, status}) => { + ProcessErrors(null, data, status, null, { + hdr: i18n._('Error!'), + msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status + }); + }); + }] + } + } }) .then(res => { const stateIndex = res.states.findIndex(s => s.name === projectsEditName); diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index a42b9482ec..b63dad2feb 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -11,22 +11,22 @@ */ export default - [ '$filter', '$scope', '$rootScope', - '$location', '$stateParams', 'JobTemplateForm', 'GenerateForm', + [ '$filter', '$scope', + '$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'hashSetup', 'ParseTypeChange', 'Wait', 'selectedLabels', 'i18n', - 'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit', - 'initSurvey', '$state', 'CreateSelect2', + 'Empty', 'ToJSON', 'GetChoices', 'CallbackHelpInit', + 'initSurvey', '$state', 'CreateSelect2', 'isNotificationAdmin', 'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'MultiCredentialService', 'availableLabels', 'projectGetPermissionDenied', 'inventoryGetPermissionDenied', 'jobTemplateData', 'ParseVariableString', 'ConfigData', function( - $filter, $scope, $rootScope, - $location, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, + $filter, $scope, + $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, GetBasePath, hashSetup, ParseTypeChange, Wait, selectedLabels, i18n, - Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, - SurveyControllerInit, $state, CreateSelect2, + Empty, ToJSON, GetChoices, CallbackHelpInit, + SurveyControllerInit, $state, CreateSelect2, isNotificationAdmin, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService, availableLabels, projectGetPermissionDenied, inventoryGetPermissionDenied, jobTemplateData, ParseVariableString, ConfigData @@ -64,6 +64,7 @@ export default $scope.skip_tag_options = []; const virtualEnvs = ConfigData.custom_virtualenvs || []; $scope.custom_virtualenvs_options = virtualEnvs; + $scope.isNotificationAdmin = isNotificationAdmin || false; SurveyControllerInit({ scope: $scope, diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index 60e20aafc8..477ff315a6 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -57,8 +57,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p }, resolve: { add: { - Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ + Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', + function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ if($stateParams.inventory_id){ let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; Rest.setUrl(path); @@ -67,15 +67,15 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return data.data; }).catch(function(response) { ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory info. GET returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get inventory info. GET returned status: ') + response.status }); }); } }], - Project: ['Rest', 'GetBasePath', 'ProcessErrors', '$transition$', - function(Rest, GetBasePath, ProcessErrors, $transition$){ + Project: ['Rest', 'GetBasePath', 'ProcessErrors', '$transition$', 'i18n', + function(Rest, GetBasePath, ProcessErrors, $transition$, i18n){ if($transition$.params().credential_id){ let path = `${GetBasePath('projects')}?credential__id=${Number($transition$.params().credential_id)}`; Rest.setUrl(path); @@ -84,8 +84,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return data.data.results[0]; }).catch(function(response) { ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get project info. GET returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get project info. GET returned status: ') + response.status }); }); @@ -98,50 +98,49 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return data.data; }).catch(function(response) { ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get project info. GET returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get project info. GET returned status: ') + response.status }); }); } }], - availableLabels: ['ProcessErrors', 'TemplatesService', - function(ProcessErrors, TemplatesService) { + availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', + function(ProcessErrors, TemplatesService, i18n) { 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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get labels. GET returned status: ') + response.status }); }); }], - checkPermissions: ['Rest', 'GetBasePath', 'TemplatesService', 'Alert', 'ProcessErrors', '$state', - function(Rest, GetBasePath, TemplatesService, Alert, ProcessErrors, $state) { + checkPermissions: ['TemplatesService', 'Alert', 'ProcessErrors', '$state', 'i18n', + function(TemplatesService, Alert, ProcessErrors, $state, i18n) { return TemplatesService.getJobTemplateOptions() .then(function(data) { if (!data.actions.POST) { $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a job template.', 'alert-info'); + Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a job template.'), 'alert-info'); } }).catch(function(response){ ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get job template options. OPTIONS returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get job template options. OPTIONS returned status: ') + response.status }); }); }], - ConfigData: ['ConfigService', 'ProcessErrors', (ConfigService, ProcessErrors) => { + ConfigData: ['ConfigService', 'ProcessErrors', 'i18n', (ConfigService, ProcessErrors, i18n) => { return ConfigService.getConfig() .then(response => response) .catch(({data, status}) => { ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get config. GET returned status: ' + - 'status: ' + status + hdr: i18n._('Error!'), + msg: i18n._('Failed to get config. GET returned status: ') + status }); }); }] @@ -167,21 +166,21 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p }, resolve: { edit: { - jobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', - function($stateParams, TemplatesService, ProcessErrors) { + jobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', + function($stateParams, TemplatesService, ProcessErrors, i18n) { 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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get job template. GET returned status: ') + response.status }); }); }], - projectGetPermissionDenied: ['Rest', 'ProcessErrors', 'jobTemplateData', - function(Rest, ProcessErrors, jobTemplateData) { + projectGetPermissionDenied: ['Rest', 'ProcessErrors', 'jobTemplateData', 'i18n', + function(Rest, ProcessErrors, jobTemplateData, i18n) { if(jobTemplateData.related.project) { Rest.setUrl(jobTemplateData.related.project); return Rest.get() @@ -191,9 +190,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p .catch(({data, status}) => { if (status !== 403) { ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get project. GET returned ' + - 'status: ' + status + hdr: i18n._('Error!'), + msg: i18n._('Failed to get project. GET returned status: ') + status }); return false; } @@ -206,8 +204,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return false; } }], - inventoryGetPermissionDenied: ['Rest', 'ProcessErrors', 'jobTemplateData', - function(Rest, ProcessErrors, jobTemplateData) { + inventoryGetPermissionDenied: ['Rest', 'ProcessErrors', 'jobTemplateData', 'i18n', + function(Rest, ProcessErrors, jobTemplateData, i18n) { if(jobTemplateData.related.inventory) { Rest.setUrl(jobTemplateData.related.inventory); return Rest.get() @@ -217,9 +215,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p .catch(({data, status}) => { if (status !== 403) { ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get project. GET returned ' + - 'status: ' + status + hdr: i18n._('Error!'), + msg: i18n._('Failed to get project. GET returned status: ') + status }); return false; } @@ -232,8 +229,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return false; } }], - InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ + InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', + function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}/instance_groups/`; Rest.setUrl(path); return Rest.get() @@ -244,48 +241,60 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p }) .catch(({data, status}) => { ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get instance groups. GET returned ' + - 'status: ' + status + hdr: i18n._('Error!'), + msg: i18n._('Failed to get instance groups. GET returned status: ') + status }); }); }], - availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService', - function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) { + availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', + function(ProcessErrors, TemplatesService, i18n) { 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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get labels. GET returned status: ') + response.status }); }); }], - selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors', - function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) { + selectedLabels: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', + function($stateParams, TemplatesService, ProcessErrors, i18n) { 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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get workflow job template labels. GET returned status: ') + response.status }); }); }], - ConfigData: ['ConfigService', 'ProcessErrors', (ConfigService, ProcessErrors) => { + ConfigData: ['ConfigService', 'ProcessErrors', 'i18n', (ConfigService, ProcessErrors, i18n) => { return ConfigService.getConfig() .then(response => response) .catch(({data, status}) => { ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get config. GET returned status: ' + - 'status: ' + status + hdr: i18n._('Error!'), + msg: i18n._('Failed to get config. GET returned status: ') + status }); }); + }], + isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', + function(Rest, ProcessErrors, GetBasePath, i18n) { + Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); + return Rest.get() + .then(({data}) => { + return data.count > 0; + }) + .catch(({data, status}) => { + ProcessErrors(null, data, status, null, { + hdr: i18n._('Error!'), + msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status + }); + }); }] } } @@ -301,8 +310,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p }, resolve: { add: { - Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ + Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', + function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ if($stateParams.inventory_id){ let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; Rest.setUrl(path); @@ -311,38 +320,38 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return data.data; }).catch(function(response) { ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory info. GET returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get inventory info. GET returned status: ') + response.status }); }); } }], - availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService', - function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) { + availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', + function(ProcessErrors, TemplatesService, i18n) { 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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get labels. GET returned status: ') + response.status }); }); }], - checkPermissions: ['Rest', 'GetBasePath', 'TemplatesService', 'Alert', 'ProcessErrors', '$state', - function(Rest, GetBasePath, TemplatesService, Alert, ProcessErrors, $state) { + checkPermissions: ['TemplatesService', 'Alert', 'ProcessErrors', '$state', 'i18n', + function(TemplatesService, Alert, ProcessErrors, $state, i18n) { return TemplatesService.getWorkflowJobTemplateOptions() .then(function(data) { if (!data.actions.POST) { $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a workflow job template.', 'alert-info'); + Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a workflow job template.'), 'alert-info'); } }).catch(function(response){ ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get workflow job template options. OPTIONS returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get workflow job template options. OPTIONS returned status: ') + response.status }); }); @@ -369,8 +378,8 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p }, resolve: { edit: { - Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ + Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', + function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ if($stateParams.inventory_id){ let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; Rest.setUrl(path); @@ -379,48 +388,48 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p return data.data; }).catch(function(response) { ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory info. GET returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get inventory info. GET returned status: ') + response.status }); }); } }], - availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService', - function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) { + availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', + function(ProcessErrors, TemplatesService, i18n) { 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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get labels. GET returned status: ') + response.status }); }); }], - selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors', - function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) { + selectedLabels: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', + function($stateParams, TemplatesService, ProcessErrors, i18n) { return TemplatesService.getAllWorkflowJobTemplateLabels($stateParams.workflow_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: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get workflow job template labels. GET returned status: ') + response.status }); }); }], - workflowJobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', - function($stateParams, TemplatesService, ProcessErrors) { + workflowJobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', + function($stateParams, TemplatesService, ProcessErrors, i18n) { return TemplatesService.getWorkflowJobTemplate($stateParams.workflow_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 workflow job template. GET returned status: ' + + hdr: i18n._('Error!'), + msg: i18n._('Failed to get workflow job template. GET returned status: ') + response.status }); }); @@ -433,6 +442,20 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p .then(({data}) => { return data; }); + }], + isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', + function(Rest, ProcessErrors, GetBasePath, i18n) { + Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); + return Rest.get() + .then(({data}) => { + return data.count > 0; + }) + .catch(({data, status}) => { + ProcessErrors(null, data, status, null, { + hdr: i18n._('Error!'), + msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status + }); + }); }] } } diff --git a/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js b/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js index fc695c8482..174ffffeef 100644 --- a/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js +++ b/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js @@ -10,12 +10,12 @@ export default [ 'Wait', 'Empty', 'ToJSON', 'initSurvey', '$state', 'CreateSelect2', 'ParseVariableString', 'TemplatesService', 'Rest', 'ToggleNotification', 'OrgAdminLookup', 'availableLabels', 'selectedLabels', 'workflowJobTemplateData', 'i18n', - 'workflowLaunch', '$transitions', 'WorkflowJobTemplateModel', 'Inventory', + 'workflowLaunch', '$transitions', 'WorkflowJobTemplateModel', 'Inventory', 'isNotificationAdmin', function($scope, $stateParams, WorkflowForm, GenerateForm, Alert, ProcessErrors, GetBasePath, $q, ParseTypeChange, Wait, Empty, ToJSON, SurveyControllerInit, $state, CreateSelect2, ParseVariableString, TemplatesService, Rest, ToggleNotification, OrgAdminLookup, availableLabels, selectedLabels, workflowJobTemplateData, i18n, - workflowLaunch, $transitions, WorkflowJobTemplate, Inventory + workflowLaunch, $transitions, WorkflowJobTemplate, Inventory, isNotificationAdmin ) { $scope.missingTemplates = _.has(workflowLaunch, 'node_templates_missing') && workflowLaunch.node_templates_missing.length > 0 ? true : false; @@ -26,6 +26,8 @@ export default [ } }); + $scope.isNotificationAdmin = isNotificationAdmin || false; + const criteriaObj = { from: (state) => state.name === 'templates.editWorkflowJobTemplate.workflowMaker', to: (state) => state.name === 'templates.editWorkflowJobTemplate'