From 4cd2f93c31a3bd2eb3738fa04cecdf2b9d85313e Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 8 Nov 2017 16:38:34 -0500 Subject: [PATCH] Updated delete warnings to indicate resources that may be invalidated as a result of deletion --- .../credentials/credentials.strings.js | 5 + awx/ui/client/index.template.ejs | 2 +- awx/ui/client/lib/models/Base.js | 47 +++++- awx/ui/client/lib/models/Credential.js | 60 +++++++- awx/ui/client/lib/models/CredentialType.js | 29 +++- awx/ui/client/lib/models/Inventory.js | 44 ++++++ awx/ui/client/lib/models/InventoryScript.js | 44 ++++++ awx/ui/client/lib/models/InventorySource.js | 44 ++++++ awx/ui/client/lib/models/JobTemplate.js | 44 ++++++ awx/ui/client/lib/models/Organization.js | 13 +- awx/ui/client/lib/models/Project.js | 64 +++++++++ .../lib/models/WorkflowJobTemplateNode.js | 28 ++++ awx/ui/client/lib/models/index.js | 17 ++- awx/ui/client/lib/models/models.strings.js | 22 +++ .../credential-types.strings.js | 15 ++ .../credential-types/list/list.controller.js | 42 ++++-- awx/ui/client/src/credential-types/main.js | 2 + .../list/credentials-list.controller.js | 39 +++-- .../list/inventory-list.controller.js | 44 ++++-- .../sources/list/sources-list.controller.js | 44 ++++-- .../inventory-hosts.strings.js | 10 ++ .../inventory-scripts.strings.js | 15 ++ .../inventory-scripts/list/list.controller.js | 44 ++++-- awx/ui/client/src/inventory-scripts/main.js | 2 + .../list/organizations-list.controller.js | 2 +- .../projects/list/projects-list.controller.js | 47 ++++-- awx/ui/client/src/projects/main.js | 2 + .../client/src/projects/projects.strings.js | 15 ++ awx/ui/client/src/shared/prompt-dialog.js | 1 + .../list/templates-list.controller.js | 134 ++++++++++-------- awx/ui/client/src/templates/main.js | 2 + .../client/src/templates/templates.strings.js | 17 +++ 32 files changed, 807 insertions(+), 133 deletions(-) create mode 100644 awx/ui/client/lib/models/Inventory.js create mode 100644 awx/ui/client/lib/models/InventoryScript.js create mode 100644 awx/ui/client/lib/models/InventorySource.js create mode 100644 awx/ui/client/lib/models/JobTemplate.js create mode 100644 awx/ui/client/lib/models/Project.js create mode 100644 awx/ui/client/lib/models/WorkflowJobTemplateNode.js create mode 100644 awx/ui/client/lib/models/models.strings.js create mode 100644 awx/ui/client/src/credential-types/credential-types.strings.js create mode 100644 awx/ui/client/src/inventory-scripts/inventory-scripts.strings.js create mode 100644 awx/ui/client/src/projects/projects.strings.js create mode 100644 awx/ui/client/src/templates/templates.strings.js diff --git a/awx/ui/client/features/credentials/credentials.strings.js b/awx/ui/client/features/credentials/credentials.strings.js index 958282aa09..64af74726e 100644 --- a/awx/ui/client/features/credentials/credentials.strings.js +++ b/awx/ui/client/features/credentials/credentials.strings.js @@ -27,6 +27,11 @@ function CredentialsStrings (BaseString) { ns.permissions = { TITLE: t.s('CREDENTIALS PERMISSIONS') }; + + ns.deleteCredential = { + CONFIRM: t.s('Are you sure you want to delete this credential?'), + INVALIDATE: t.s('Doing so will invalidate the following:') + }; } CredentialsStrings.$inject = ['BaseStringService']; diff --git a/awx/ui/client/index.template.ejs b/awx/ui/client/index.template.ejs index f222a054d1..5b082eb6e7 100644 --- a/awx/ui/client/index.template.ejs +++ b/awx/ui/client/index.template.ejs @@ -42,7 +42,7 @@ diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 72434e7d8d..854fb72de0 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -77,12 +77,16 @@ function search (params, config) { }); } -function httpGet (resource) { +function httpGet (resource, config) { const req = { method: 'GET', url: this.path }; + if (config && config.params) { + req.params = config.params; + } + if (typeof resource === 'object') { this.model.GET = resource; @@ -143,6 +147,19 @@ function httpOptions (resource) { }); } +function httpDelete (resource) { + const req = { + method: 'DELETE', + url: this.path + }; + + if (resource) { + req.url = `${this.path}${resource}/`; + } + + return $http(req).then(res => res); +} + function options (keys) { return this.find('options', keys); } @@ -349,6 +366,32 @@ function graft (id) { return new this.Constructor('get', item, true); } +function getDependentResourceCounts (id) { + if (this.setDependentResources) { + this.setDependentResources(id); + } else { + return Promise.resolve([]); + } + + const dependentResourcePromises = []; + + this.dependentResources.forEach(dependentResource => { + const config = {}; + + if (dependentResource.params) { + config.params = dependentResource.params; + } + + dependentResourcePromises.push(dependentResource.model.http.get(undefined, config) + .then((val) => ({ + label: dependentResource.model.label, + count: val.data.count + }))); + }); + + return Promise.all(dependentResourcePromises); +} + /** * `create` is called on instantiation of every model. Models can be * instantiated empty or with `GET` and/or `OPTIONS` requests that yield data. @@ -407,12 +450,14 @@ function BaseModel (path, settings) { this.set = set; this.unset = unset; this.extend = extend; + this.getDependentResourceCounts = getDependentResourceCounts; this.http = { get: httpGet.bind(this), options: httpOptions.bind(this), post: httpPost.bind(this), put: httpPut.bind(this), + delete: httpDelete.bind(this) }; this.model = {}; diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 3b0aafcf3d..0bfac7be07 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -1,6 +1,11 @@ const ENCRYPTED_VALUE = '$encrypted$'; let BaseModel; +let ProjectModel; +let JobTemplateModel; +let InventoryModel; +let InventorySourceModel; +let ModelsStrings; function createFormSchema (method, config) { if (!config) { @@ -40,22 +45,73 @@ function assignInputGroupValues (inputs) { }); } +function setDependentResources (id) { + this.dependentResources = [ + { + model: new ProjectModel(), + params: { + credential: id + } + }, + { + model: new JobTemplateModel(), + params: { + credential: id, + ask_credential_on_launch: false + } + }, + { + model: new InventoryModel(), + params: { + insights_credential: id + } + }, + { + model: new InventorySourceModel(), + params: { + credential: id + } + } + ]; +} + function CredentialModel (method, resource, graft) { BaseModel.call(this, 'credentials'); this.Constructor = CredentialModel; this.createFormSchema = createFormSchema.bind(this); this.assignInputGroupValues = assignInputGroupValues.bind(this); + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.CREDENTIAL'); return this.create(method, resource, graft); } -function CredentialModelLoader (_BaseModel_) { +function CredentialModelLoader ( + _BaseModel_, + _ProjectModel_, + _JobTemplateModel_, + _InventoryModel_, + _InventorySourceModel_, + _ModelsStrings_ +) { BaseModel = _BaseModel_; + ProjectModel = _ProjectModel_; + JobTemplateModel = _JobTemplateModel_; + InventoryModel = _InventoryModel_; + InventorySourceModel = _InventorySourceModel_; + ModelsStrings = _ModelsStrings_; return CredentialModel; } -CredentialModelLoader.$inject = ['BaseModel']; +CredentialModelLoader.$inject = [ + 'BaseModel', + 'ProjectModel', + 'JobTemplateModel', + 'InventoryModel', + 'InventorySourceModel', + 'ModelsStrings' +]; export default CredentialModelLoader; diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js index 334ba234ce..936a1d8bdd 100644 --- a/awx/ui/client/lib/models/CredentialType.js +++ b/awx/ui/client/lib/models/CredentialType.js @@ -1,4 +1,6 @@ let BaseModel; +let CredentialModel; +let ModelsStrings; function categorizeByKind () { const group = {}; @@ -30,22 +32,45 @@ function mergeInputProperties () { }); } +function setDependentResources (id) { + this.dependentResources = [ + { + model: new CredentialModel(), + params: { + credential_type: id + } + } + ]; +} + function CredentialTypeModel (method, resource, graft) { BaseModel.call(this, 'credential_types'); this.Constructor = CredentialTypeModel; this.categorizeByKind = categorizeByKind.bind(this); this.mergeInputProperties = mergeInputProperties.bind(this); + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.CREDENTIAL_TYPE'); return this.create(method, resource, graft); } -function CredentialTypeModelLoader (_BaseModel_) { +function CredentialTypeModelLoader ( + _BaseModel_, + _CredentialModel_, + _ModelsStrings_ +) { BaseModel = _BaseModel_; + CredentialModel = _CredentialModel_; + ModelsStrings = _ModelsStrings_; return CredentialTypeModel; } -CredentialTypeModelLoader.$inject = ['BaseModel']; +CredentialTypeModelLoader.$inject = [ + 'BaseModel', + 'CredentialModel', + 'ModelsStrings' +]; export default CredentialTypeModelLoader; diff --git a/awx/ui/client/lib/models/Inventory.js b/awx/ui/client/lib/models/Inventory.js new file mode 100644 index 0000000000..b1d5af3f54 --- /dev/null +++ b/awx/ui/client/lib/models/Inventory.js @@ -0,0 +1,44 @@ +let BaseModel; +let JobTemplateModel; +let ModelsStrings; + +function setDependentResources (id) { + this.dependentResources = [ + { + model: new JobTemplateModel(), + params: { + inventory: id + } + } + ]; +} + +function InventoryModel (method, resource, graft) { + BaseModel.call(this, 'inventories'); + + this.Constructor = InventoryModel; + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.INVENTORY'); + + return this.create(method, resource, graft); +} + +function InventoryModelLoader ( + _BaseModel_, + _JobTemplateModel_, + _ModelsStrings_ +) { + BaseModel = _BaseModel_; + JobTemplateModel = _JobTemplateModel_; + ModelsStrings = _ModelsStrings_; + + return InventoryModel; +} + +InventoryModelLoader.$inject = [ + 'BaseModel', + 'JobTemplateModel', + 'ModelsStrings' +]; + +export default InventoryModelLoader; diff --git a/awx/ui/client/lib/models/InventoryScript.js b/awx/ui/client/lib/models/InventoryScript.js new file mode 100644 index 0000000000..aa37d09efd --- /dev/null +++ b/awx/ui/client/lib/models/InventoryScript.js @@ -0,0 +1,44 @@ +let BaseModel; +let InventorySourceModel; +let ModelsStrings; + +function setDependentResources (id) { + this.dependentResources = [ + { + model: new InventorySourceModel(), + params: { + source_script: id + } + } + ]; +} + +function InventoryScriptModel (method, resource, graft) { + BaseModel.call(this, 'inventory_scripts'); + + this.Constructor = InventoryScriptModel; + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.INVENTORY_SCRIPT'); + + return this.create(method, resource, graft); +} + +function InventoryScriptModelLoader ( + _BaseModel_, + _InventorySourceModel_, + _ModelsStrings_ +) { + BaseModel = _BaseModel_; + InventorySourceModel = _InventorySourceModel_; + ModelsStrings = _ModelsStrings_; + + return InventoryScriptModel; +} + +InventoryScriptModelLoader.$inject = [ + 'BaseModel', + 'InventorySourceModel', + 'ModelsStrings' +]; + +export default InventoryScriptModelLoader; diff --git a/awx/ui/client/lib/models/InventorySource.js b/awx/ui/client/lib/models/InventorySource.js new file mode 100644 index 0000000000..abde6883fb --- /dev/null +++ b/awx/ui/client/lib/models/InventorySource.js @@ -0,0 +1,44 @@ +let BaseModel; +let WorkflowJobTemplateNodeModel; +let ModelsStrings; + +function setDependentResources (id) { + this.dependentResources = [ + { + model: new WorkflowJobTemplateNodeModel(), + params: { + unified_job_template: id + } + } + ]; +} + +function InventorySourceModel (method, resource, graft) { + BaseModel.call(this, 'inventory_sources'); + + this.Constructor = InventorySourceModel; + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.INVENTORY_SOURCE'); + + return this.create(method, resource, graft); +} + +function InventorySourceModelLoader ( + _BaseModel_, + _WorkflowJobTemplateNodeModel_, + _ModelsStrings_ +) { + BaseModel = _BaseModel_; + WorkflowJobTemplateNodeModel = _WorkflowJobTemplateNodeModel_; + ModelsStrings = _ModelsStrings_; + + return InventorySourceModel; +} + +InventorySourceModelLoader.$inject = [ + 'BaseModel', + 'WorkflowJobTemplateNodeModel', + 'ModelsStrings' +]; + +export default InventorySourceModelLoader; diff --git a/awx/ui/client/lib/models/JobTemplate.js b/awx/ui/client/lib/models/JobTemplate.js new file mode 100644 index 0000000000..2b0a7ce021 --- /dev/null +++ b/awx/ui/client/lib/models/JobTemplate.js @@ -0,0 +1,44 @@ +let BaseModel; +let WorkflowJobTemplateNodeModel; +let ModelsStrings; + +function setDependentResources (id) { + this.dependentResources = [ + { + model: new WorkflowJobTemplateNodeModel(), + params: { + unified_job_template: id + } + } + ]; +} + +function JobTemplateModel (method, resource, graft) { + BaseModel.call(this, 'job_templates'); + + this.Constructor = JobTemplateModel; + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.JOB_TEMPLATE'); + + return this.create(method, resource, graft); +} + +function JobTemplateModelLoader ( + _BaseModel_, + _WorkflowJobTemplateNodeModel_, + _ModelsStrings_ +) { + BaseModel = _BaseModel_; + WorkflowJobTemplateNodeModel = _WorkflowJobTemplateNodeModel_; + ModelsStrings = _ModelsStrings_; + + return JobTemplateModel; +} + +JobTemplateModelLoader.$inject = [ + 'BaseModel', + 'WorkflowJobTemplateNodeModel', + 'ModelsStrings' +]; + +export default JobTemplateModelLoader; diff --git a/awx/ui/client/lib/models/Organization.js b/awx/ui/client/lib/models/Organization.js index 53448cd80f..734d547395 100644 --- a/awx/ui/client/lib/models/Organization.js +++ b/awx/ui/client/lib/models/Organization.js @@ -1,19 +1,28 @@ let BaseModel; +let ModelsStrings; function OrganizationModel (method, resource, graft) { BaseModel.call(this, 'organizations'); this.Constructor = OrganizationModel; + this.label = ModelsStrings.get('labels.ORGANIZATION'); return this.create(method, resource, graft); } -function OrganizationModelLoader (_BaseModel_) { +function OrganizationModelLoader ( + _BaseModel_, + _ModelsStrings_ +) { BaseModel = _BaseModel_; + ModelsStrings = _ModelsStrings_; return OrganizationModel; } -OrganizationModelLoader.$inject = ['BaseModel']; +OrganizationModelLoader.$inject = [ + 'BaseModel', + 'ModelsStrings' +]; export default OrganizationModelLoader; diff --git a/awx/ui/client/lib/models/Project.js b/awx/ui/client/lib/models/Project.js new file mode 100644 index 0000000000..63d363bd92 --- /dev/null +++ b/awx/ui/client/lib/models/Project.js @@ -0,0 +1,64 @@ +let BaseModel; +let JobTemplateModel; +let WorkflowJobTemplateNodeModel; +let InventorySourceModel; +let ModelsStrings; + +function setDependentResources (id) { + this.dependentResources = [ + { + model: new JobTemplateModel(), + params: { + project: id + } + }, + { + model: new WorkflowJobTemplateNodeModel(), + params: { + unified_job_template: id + } + }, + { + model: new InventorySourceModel(), + params: { + source_project: id + } + } + ]; +} + +function ProjectModel (method, resource, graft) { + BaseModel.call(this, 'projects'); + + this.Constructor = ProjectModel; + this.setDependentResources = setDependentResources.bind(this); + this.label = ModelsStrings.get('labels.PROJECT'); + + return this.create(method, resource, graft); +} + +function ProjectModelLoader ( + _BaseModel_, + _JobTemplateModel_, + _WorkflowJobTemplateNodeModel_, + _InventorySourceModel_, + _ModelsStrings_ +) { + BaseModel = _BaseModel_; + JobTemplateModel = _JobTemplateModel_; + WorkflowJobTemplateNodeModel = _WorkflowJobTemplateNodeModel_; + InventorySourceModel = _InventorySourceModel_; + ModelsStrings = _ModelsStrings_; + + return ProjectModel; +} + +ProjectModelLoader.$inject = [ + 'BaseModel', + 'JobTemplateModel', + 'WorkflowJobTemplateNodeModel', + 'InventorySourceModel', + 'ModelsStrings' +]; + +export default ProjectModelLoader; diff --git a/awx/ui/client/lib/models/WorkflowJobTemplateNode.js b/awx/ui/client/lib/models/WorkflowJobTemplateNode.js new file mode 100644 index 0000000000..bc5cfdf107 --- /dev/null +++ b/awx/ui/client/lib/models/WorkflowJobTemplateNode.js @@ -0,0 +1,28 @@ +let BaseModel; +let ModelsStrings; + +function WorkflowJobTemplateNodeModel (method, resource, graft) { + BaseModel.call(this, 'workflow_job_template_nodes'); + + this.Constructor = WorkflowJobTemplateNodeModel; + this.label = ModelsStrings.get('labels.WORKFLOW_JOB_TEMPLATE_NODE'); + + return this.create(method, resource, graft); +} + +function WorkflowJobTemplateNodeModelLoader ( + _BaseModel_, + _ModelsStrings_ +) { + BaseModel = _BaseModel_; + ModelsStrings = _ModelsStrings_; + + return WorkflowJobTemplateNodeModel; +} + +WorkflowJobTemplateNodeModelLoader.$inject = [ + 'BaseModel', + 'ModelsStrings' +]; + +export default WorkflowJobTemplateNodeModelLoader; diff --git a/awx/ui/client/lib/models/index.js b/awx/ui/client/lib/models/index.js index 1213463e57..08de2e3786 100644 --- a/awx/ui/client/lib/models/index.js +++ b/awx/ui/client/lib/models/index.js @@ -6,6 +6,14 @@ import Credential from '~models/Credential'; import CredentialType from '~models/CredentialType'; import Me from '~models/Me'; import Organization from '~models/Organization'; +import Project from '~models/Project'; +import JobTemplate from '~models/JobTemplate'; +import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode'; +import InventorySource from '~models/InventorySource'; +import Inventory from '~models/Inventory'; +import InventoryScript from '~models/InventoryScript'; + +import ModelsStrings from '~models/models.strings'; const MODULE_NAME = 'at.lib.models'; @@ -18,6 +26,13 @@ angular .service('CredentialModel', Credential) .service('CredentialTypeModel', CredentialType) .service('MeModel', Me) - .service('OrganizationModel', Organization); + .service('OrganizationModel', Organization) + .service('ProjectModel', Project) + .service('JobTemplateModel', JobTemplate) + .service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode) + .service('InventorySourceModel', InventorySource) + .service('InventoryModel', Inventory) + .service('InventoryScriptModel', InventoryScript) + .service('ModelsStrings', ModelsStrings); export default MODULE_NAME; diff --git a/awx/ui/client/lib/models/models.strings.js b/awx/ui/client/lib/models/models.strings.js new file mode 100644 index 0000000000..55b72be5f0 --- /dev/null +++ b/awx/ui/client/lib/models/models.strings.js @@ -0,0 +1,22 @@ +function ModelsStrings (BaseString) { + BaseString.call(this, 'models'); + + const { t } = this; + const ns = this.models; + + ns.labels = { + CREDENTIAL: t.s('Credentials'), + CREDENTIAL_TYPE: t.s('Credential Types'), + INVENTORY: t.s('Inventories'), + INVENTORY_SCRIPT: t.s('Inventory Scripts'), + INVENTORY_SOURCE: t.s('Inventory Sources'), + JOB_TEMPLATE: t.s('Job Templates'), + ORGANIZATION: t.s('Organizations'), + PROJECT: t.s('Projects'), + WORKFLOW_JOB_TEMPLATE_NODE: t.s('Workflow Job Template Nodes') + }; +} + +ModelsStrings.$inject = ['BaseStringService']; + +export default ModelsStrings; diff --git a/awx/ui/client/src/credential-types/credential-types.strings.js b/awx/ui/client/src/credential-types/credential-types.strings.js new file mode 100644 index 0000000000..ec2e478ef2 --- /dev/null +++ b/awx/ui/client/src/credential-types/credential-types.strings.js @@ -0,0 +1,15 @@ +function CredentialTypesStrings (BaseString) { + BaseString.call(this, 'credential_types'); + + let t = this.t; + let ns = this.credential_types; + + ns.deleteCredentialType = { + CONFIRM: t.s('Are you sure you want to delete this credential type?'), + CREDENTIAL_TYPE_IN_USE: t.s('This credential type is currently being used by one or more credentials. Credentials that use this credential type must be deleted before the credential type can be deleted.') + }; +} + +CredentialTypesStrings.$inject = ['BaseStringService']; + +export default CredentialTypesStrings; diff --git a/awx/ui/client/src/credential-types/list/list.controller.js b/awx/ui/client/src/credential-types/list/list.controller.js index 129d415711..db44d738c6 100644 --- a/awx/ui/client/src/credential-types/list/list.controller.js +++ b/awx/ui/client/src/credential-types/list/list.controller.js @@ -5,11 +5,16 @@ *************************************************/ export default ['$rootScope', '$scope', 'Wait', 'CredentialTypesList', - 'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', '$filter', 'Dataset', 'rbacUiControlService', 'Alert', '$q', + 'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', '$filter', + 'Dataset', 'rbacUiControlService', 'Alert', '$q', 'CredentialTypeModel', + 'CredentialTypesStrings', 'i18n', function( $rootScope, $scope, Wait, CredentialTypesList, - GetBasePath, Rest, ProcessErrors, Prompt, $state, $filter, Dataset, rbacUiControlService, Alert, $q + GetBasePath, Rest, ProcessErrors, Prompt, $state, $filter, + Dataset, rbacUiControlService, Alert, $q, CredentialType, + CredentialTypesStrings, i18n ) { + let credentialType = new CredentialType(); var defaultUrl = GetBasePath('credential_types'), list = CredentialTypesList; @@ -58,8 +63,7 @@ export default ['$rootScope', '$scope', 'Wait', 'CredentialTypesList', $('#prompt-modal').modal('hide'); Wait('start'); var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() + credentialType.request('delete', id) .then(() => { let reloadListStateParams = null; @@ -83,13 +87,29 @@ export default ['$rootScope', '$scope', 'Wait', 'CredentialTypesList', }); }; - var bodyHtml = '
Are you sure you want to delete the credential type below?
' + $filter('sanitize')(name) + '
'; - Prompt({ - hdr: 'Delete', - body: bodyHtml, - action: action, - actionText: 'DELETE' - }); + credentialType.getDependentResourceCounts(id) + .then((counts) => { + let credentialTypeInUse = false; + let deleteModalBody = `
${CredentialTypesStrings.get('deleteCredentialType.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + credentialTypeInUse = true; + } + }); + + if (credentialTypeInUse) { + deleteModalBody = `
${CredentialTypesStrings.get('deleteCredentialType.CREDENTIAL_TYPE_IN_USE')}
`; + } + + Prompt({ + hdr: i18n._('Delete') + ' ' + $filter('sanitize')(name), + body: deleteModalBody, + action: action, + hideActionButton: credentialTypeInUse ? true : false, + actionText: 'DELETE' + }); + }); }; $scope.addCredentialType = function() { diff --git a/awx/ui/client/src/credential-types/main.js b/awx/ui/client/src/credential-types/main.js index be36639928..0f2ce9f186 100644 --- a/awx/ui/client/src/credential-types/main.js +++ b/awx/ui/client/src/credential-types/main.js @@ -10,6 +10,7 @@ import credentialTypesEdit from './edit/main'; import list from './credential-types.list'; import form from './credential-types.form'; import { N_ } from '../i18n'; +import CredentialTypesStrings from './credential-types.strings'; export default angular.module('credentialTypes', [ @@ -19,6 +20,7 @@ angular.module('credentialTypes', [ ]) .factory('CredentialTypesList', list) .factory('CredentialTypesForm', form) + .service('CredentialTypesStrings', CredentialTypesStrings) .config(['$stateProvider', 'stateDefinitionsProvider', function($stateProvider, stateDefinitionsProvider) { let stateDefinitions = stateDefinitionsProvider.$get(); diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js index 798ce5b024..efb3bfb9d1 100644 --- a/awx/ui/client/src/credentials/list/credentials-list.controller.js +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -6,9 +6,12 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'credentialType', 'i18n', + 'CredentialModel', 'CredentialsStrings', function($scope, Rest, CredentialList, Prompt, ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset, - credentialType, i18n) { + credentialType, i18n, Credential, CredentialsStrings) { + + let credential = new Credential(); var list = CredentialList, defaultUrl = GetBasePath('credentials'); @@ -83,8 +86,7 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' $('#prompt-modal').modal('hide'); Wait('start'); var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() + credential.request('delete', id) .then(() => { let reloadListStateParams = null; @@ -109,12 +111,31 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' }); }; - Prompt({ - hdr: i18n._('Delete'), - body: '
' + i18n._('Are you sure you want to delete the credential below?') + '
' + $filter('sanitize')(name) + '
', - action: action, - actionText: i18n._('DELETE') - }); + credential.getDependentResourceCounts(id) + .then((counts) => { + const invalidateRelatedLines = []; + let deleteModalBody = `
${CredentialsStrings.get('deleteCredential.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + invalidateRelatedLines.push(`
${countObj.label} ${countObj.count}
`); + } + }); + + if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { + deleteModalBody = `
${CredentialsStrings.get('deleteCredential.CONFIRM')} ${CredentialsStrings.get('deleteCredential.INVALIDATE')}
`; + invalidateRelatedLines.forEach(invalidateRelatedLine => { + deleteModalBody += invalidateRelatedLine; + }); + } + + Prompt({ + hdr: i18n._('Delete') + ' ' + $filter('sanitize')(name), + body: deleteModalBody, + action: action, + actionText: 'DELETE' + }); + }); }; } ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js index f55bd351e0..1653946a56 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js @@ -13,7 +13,9 @@ function InventoriesList($scope, $filter, Rest, InventoryList, Prompt, ProcessErrors, GetBasePath, Wait, $state, - Dataset, canAdd, i18n) { + Dataset, canAdd, i18n, Inventory, InventoryHostsStrings) { + + let inventory = new Inventory(); let list = InventoryList, defaultUrl = GetBasePath('inventory'); @@ -83,8 +85,7 @@ function InventoriesList($scope, var url = defaultUrl + id + '/'; Wait('start'); $('#prompt-modal').modal('hide'); - Rest.setUrl(url); - Rest.destroy() + inventory.request('delete', id) .then(() => { Wait('stop'); }) @@ -95,13 +96,33 @@ function InventoriesList($scope, }); }; - Prompt({ - hdr: 'Delete', - body: '
' + i18n._('Are you sure you want to delete the inventory below?') + '
' + $filter('sanitize')(name) + '
' + - '
Note: ' + i18n._('The inventory will be in a pending status until the final delete is processed.') + '
', - action: action, - actionText: i18n._('DELETE') - }); + inventory.getDependentResourceCounts(id) + .then((counts) => { + const invalidateRelatedLines = []; + let deleteModalBody = `
${InventoryHostsStrings.get('deleteInventory.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + invalidateRelatedLines.push(`
${countObj.label} ${countObj.count}
`); + } + }); + + if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { + deleteModalBody = `
${InventoryHostsStrings.get('deleteInventory.CONFIRM')} ${InventoryHostsStrings.get('deleteInventory.INVALIDATE')}
`; + invalidateRelatedLines.forEach(invalidateRelatedLine => { + deleteModalBody += invalidateRelatedLine; + }); + } + + deleteModalBody += '
Note: ' + i18n._('The inventory will be in a pending status until the final delete is processed.') + '
'; + + Prompt({ + hdr: i18n._('Delete') + ' ' + $filter('sanitize')(name), + body: deleteModalBody, + action: action, + actionText: 'DELETE' + }); + }); }; $scope.$on(`ws-inventories`, function(e, data){ @@ -129,5 +150,6 @@ function InventoriesList($scope, export default ['$scope', '$filter', 'Rest', 'InventoryList', 'Prompt', 'ProcessErrors', 'GetBasePath', 'Wait', - '$state', 'Dataset', 'canAdd', 'i18n', InventoriesList + '$state', 'Dataset', 'canAdd', 'i18n', 'InventoryModel', + 'InventoryHostsStrings', InventoriesList ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js index 0d806bb4e3..d606a01b8d 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js @@ -9,12 +9,15 @@ 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath', 'GetSyncStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', '$filter', 'Prompt', 'Wait', 'SourcesService', 'inventorySourceOptions', - 'canAdd', 'hasSyncableSources', 'i18n', + 'canAdd', 'hasSyncableSources', 'i18n', 'InventoryHostsStrings', 'InventorySourceModel', function($scope, $rootScope, $state, $stateParams, SourcesListDefinition, InventoryUpdate, CancelSourceUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath, GetSyncStatusMsg, Dataset, Find, qs, inventoryData, $filter, Prompt, - Wait, SourcesService, inventorySourceOptions, canAdd, hasSyncableSources, i18n){ + Wait, SourcesService, inventorySourceOptions, canAdd, hasSyncableSources, i18n, + InventoryHostsStrings, InventorySource){ + + let inventorySource = new InventorySource(); let list = SourcesListDefinition; var inventory_source; @@ -117,7 +120,6 @@ $state.go('inventories.edit.inventory_sources.edit', {inventory_source_id: id}); }; $scope.deleteSource = function(inventory_source){ - var body = '
' + i18n._('Are you sure you want to permanently delete the inventory source below from the inventory?') + '
' + $filter('sanitize')(inventory_source.name) + '
'; var action = function(){ delete $rootScope.promptActionBtnClass; Wait('start'); @@ -137,14 +139,34 @@ Wait('stop'); }); }; - // Prompt depends on having $rootScope.promptActionBtnClass available... - Prompt({ - hdr: i18n._('Delete Source'), - body: body, - action: action, - actionText: i18n._('DELETE'), - }); - $rootScope.promptActionBtnClass = 'Modal-errorButton'; + + inventorySource.getDependentResourceCounts(inventory_source.id) + .then((counts) => { + const invalidateRelatedLines = []; + let deleteModalBody = `
${InventoryHostsStrings.get('deleteSource.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + invalidateRelatedLines.push(`
${countObj.label} ${countObj.count}
`); + } + }); + + if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { + deleteModalBody = `
${InventoryHostsStrings.get('deleteSource.CONFIRM')} ${InventoryHostsStrings.get('deleteSource.INVALIDATE')}
`; + invalidateRelatedLines.forEach(invalidateRelatedLine => { + deleteModalBody += invalidateRelatedLine; + }); + } + + Prompt({ + hdr: i18n._('Delete Source') + ' ' + $filter('sanitize')(inventory_source.name), + body: deleteModalBody, + action: action, + actionText: 'DELETE' + }); + $rootScope.promptActionBtnClass = 'Modal-errorButton'; + }); + }; $scope.updateSource = function(inventory_source) { diff --git a/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js b/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js index c64b73a933..0744be46db 100644 --- a/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js +++ b/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js @@ -4,6 +4,16 @@ function InventoryHostsStrings (BaseString) { let t = this.t; let ns = this['inventory-hosts']; + ns.deleteInventory = { + CONFIRM: t.s('Are you sure you want to delete this inventory?'), + INVALIDATE: t.s('Doing so will invalidate the following:') + }; + + ns.deleteSource = { + CONFIRM: t.s('Are you sure you want to delete this inventory source?'), + INVALIDATE: t.s('Doing so will invalidate the following:') + }; + ns.deletegroup = { GROUP: count => t.p(count, 'group', 'groups'), HOST: count => t.p(count, 'host', 'hosts'), diff --git a/awx/ui/client/src/inventory-scripts/inventory-scripts.strings.js b/awx/ui/client/src/inventory-scripts/inventory-scripts.strings.js new file mode 100644 index 0000000000..9b3fccd4af --- /dev/null +++ b/awx/ui/client/src/inventory-scripts/inventory-scripts.strings.js @@ -0,0 +1,15 @@ +function InventoryScriptsStrings (BaseString) { + BaseString.call(this, 'inventory_scripts'); + + let t = this.t; + let ns = this.inventory_scripts; + + ns.deleteInventoryScript = { + CONFIRM: t.s('Are you sure you want to delete this inventory script?'), + INVALIDATE: t.s('Doing so will invalidate the following:') + }; +} + +InventoryScriptsStrings.$inject = ['BaseStringService']; + +export default InventoryScriptsStrings; diff --git a/awx/ui/client/src/inventory-scripts/list/list.controller.js b/awx/ui/client/src/inventory-scripts/list/list.controller.js index 979d6fc2e0..8f8156951e 100644 --- a/awx/ui/client/src/inventory-scripts/list/list.controller.js +++ b/awx/ui/client/src/inventory-scripts/list/list.controller.js @@ -5,11 +5,16 @@ *************************************************/ export default ['$rootScope', '$scope', 'Wait', 'InventoryScriptsList', - 'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', '$filter', 'Dataset', 'rbacUiControlService', + 'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', '$filter', + 'Dataset', 'rbacUiControlService', 'InventoryScriptModel', 'InventoryScriptsStrings', + 'i18n', function( $rootScope, $scope, Wait, InventoryScriptsList, - GetBasePath, Rest, ProcessErrors, Prompt, $state, $filter, Dataset, rbacUiControlService + GetBasePath, Rest, ProcessErrors, Prompt, $state, $filter, + Dataset, rbacUiControlService, InventoryScript, InventoryScriptsStrings, + i18n ) { + let inventoryScript = new InventoryScript(); var defaultUrl = GetBasePath('inventory_scripts'), list = InventoryScriptsList; @@ -48,8 +53,7 @@ export default ['$rootScope', '$scope', 'Wait', 'InventoryScriptsList', $('#prompt-modal').modal('hide'); Wait('start'); var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() + inventoryScript.request('delete', id) .then(() => { let reloadListStateParams = null; @@ -73,13 +77,31 @@ export default ['$rootScope', '$scope', 'Wait', 'InventoryScriptsList', }); }; - var bodyHtml = '
Are you sure you want to delete the inventory script below?
' + $filter('sanitize')(name) + '
'; - Prompt({ - hdr: 'Delete', - body: bodyHtml, - action: action, - actionText: 'DELETE' - }); + inventoryScript.getDependentResourceCounts(id) + .then((counts) => { + const invalidateRelatedLines = []; + let deleteModalBody = `
${InventoryScriptsStrings.get('deleteInventoryScript.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + invalidateRelatedLines.push(`
${countObj.label} ${countObj.count}
`); + } + }); + + if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { + deleteModalBody = `
${InventoryScriptsStrings.get('deleteInventoryScript.CONFIRM')} ${InventoryScriptsStrings.get('deleteInventoryScript.INVALIDATE')}
`; + invalidateRelatedLines.forEach(invalidateRelatedLine => { + deleteModalBody += invalidateRelatedLine; + }); + } + + Prompt({ + hdr: i18n._('Delete') + ' ' + $filter('sanitize')(name), + body: deleteModalBody, + action: action, + actionText: 'DELETE' + }); + }); }; $scope.addCustomInv = function() { diff --git a/awx/ui/client/src/inventory-scripts/main.js b/awx/ui/client/src/inventory-scripts/main.js index ec7d3e9a94..4854878bca 100644 --- a/awx/ui/client/src/inventory-scripts/main.js +++ b/awx/ui/client/src/inventory-scripts/main.js @@ -10,6 +10,7 @@ import inventoryScriptsEdit from './edit/main'; import list from './inventory-scripts.list'; import form from './inventory-scripts.form'; import { N_ } from '../i18n'; +import InventoryScriptsStrings from './inventory-scripts.strings'; export default angular.module('inventoryScripts', [ @@ -19,6 +20,7 @@ angular.module('inventoryScripts', [ ]) .factory('InventoryScriptsList', list) .factory('InventoryScriptsForm', form) + .service('InventoryScriptsStrings', InventoryScriptsStrings) .config(['$stateProvider', 'stateDefinitionsProvider', function($stateProvider, stateDefinitionsProvider) { let stateDefinitions = stateDefinitionsProvider.$get(); diff --git a/awx/ui/client/src/organizations/list/organizations-list.controller.js b/awx/ui/client/src/organizations/list/organizations-list.controller.js index 9cbe175efc..dd938c5f47 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.controller.js +++ b/awx/ui/client/src/organizations/list/organizations-list.controller.js @@ -169,7 +169,7 @@ export default ['$stateParams', '$scope', '$rootScope', Prompt({ hdr: i18n._('Delete'), - body: '
' + i18n._('Are you sure you want to delete the organization below?') + '
' + $filter('sanitize')(name) + '
', + body: '
' + i18n._('Are you sure you want to delete the organization below? This makes everything in this organization unavailable.') + '
' + $filter('sanitize')(name) + '
', action: action, actionText: i18n._('DELETE') }); diff --git a/awx/ui/client/src/projects/list/projects-list.controller.js b/awx/ui/client/src/projects/list/projects-list.controller.js index 0e7d50f240..32f0c13179 100644 --- a/awx/ui/client/src/projects/list/projects-list.controller.js +++ b/awx/ui/client/src/projects/list/projects-list.controller.js @@ -7,14 +7,16 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert', 'ProjectList', 'Prompt', 'ProcessErrors', 'GetBasePath', 'ProjectUpdate', 'Wait', 'Empty', 'Find', 'GetProjectIcon', 'GetProjectToolTip', '$filter', - '$state', 'rbacUiControlService', 'Dataset', 'i18n', 'QuerySet', + '$state', 'rbacUiControlService', 'Dataset', 'i18n', 'QuerySet', 'ProjectModel', + 'ProjectsStrings', function($scope, $rootScope, $log, Rest, Alert, ProjectList, Prompt, ProcessErrors, GetBasePath, ProjectUpdate, Wait, Empty, Find, GetProjectIcon, GetProjectToolTip, $filter, $state, rbacUiControlService, - Dataset, i18n, qs) { + Dataset, i18n, qs, Project, ProjectsStrings) { - var list = ProjectList, - defaultUrl = GetBasePath('projects'); + let project = new Project(); + + var list = ProjectList; init(); @@ -176,9 +178,7 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert', var action = function() { $('#prompt-modal').modal('hide'); Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() + project.request('delete', id) .then(() => { let reloadListStateParams = null; @@ -196,19 +196,38 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert', }) .catch(({data, status}) => { ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Call to %s failed. DELETE returned status: '), url) + status }); + msg: i18n.sprintf(i18n._('Call to %s failed. DELETE returned status: '), `${project.path}${id}/`) + status }); }) .finally(function() { Wait('stop'); }); }; - Prompt({ - hdr: i18n._('Delete'), - body: '
' + i18n._('Are you sure you want to delete the project below?') + '
' + '
' + $filter('sanitize')(name) + '
', - action: action, - actionText: 'DELETE' - }); + project.getDependentResourceCounts(id) + .then((counts) => { + const invalidateRelatedLines = []; + let deleteModalBody = `
${ProjectsStrings.get('deleteProject.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + invalidateRelatedLines.push(`
${countObj.label} ${countObj.count}
`); + } + }); + + if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { + deleteModalBody = `
${ProjectsStrings.get('deleteProject.CONFIRM')} ${ProjectsStrings.get('deleteProject.INVALIDATE')}
`; + invalidateRelatedLines.forEach(invalidateRelatedLine => { + deleteModalBody += invalidateRelatedLine; + }); + } + + Prompt({ + hdr: i18n._('Delete') + ' ' + $filter('sanitize')(name), + body: deleteModalBody, + action: action, + actionText: 'DELETE' + }); + }); }; if ($scope.removeCancelUpdate) { diff --git a/awx/ui/client/src/projects/main.js b/awx/ui/client/src/projects/main.js index e5534b4a79..25e6eeaea1 100644 --- a/awx/ui/client/src/projects/main.js +++ b/awx/ui/client/src/projects/main.js @@ -14,6 +14,7 @@ import GetProjectPath from './factories/get-project-path.factory'; import GetProjectIcon from './factories/get-project-icon.factory'; import GetProjectToolTip from './factories/get-project-tool-tip.factory'; import ProjectsTemplatesRoute from './projects-templates.route'; +import ProjectsStrings from './projects.strings'; export default angular.module('Projects', []) @@ -25,6 +26,7 @@ angular.module('Projects', []) .factory('GetProjectToolTip', GetProjectToolTip) .factory('ProjectList', ProjectList) .factory('ProjectsForm', ProjectsForm) + .service('ProjectsStrings', ProjectsStrings) .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', function($stateProvider, stateDefinitionsProvider,$stateExtenderProvider) { let stateDefinitions = stateDefinitionsProvider.$get(); diff --git a/awx/ui/client/src/projects/projects.strings.js b/awx/ui/client/src/projects/projects.strings.js new file mode 100644 index 0000000000..84566d12e7 --- /dev/null +++ b/awx/ui/client/src/projects/projects.strings.js @@ -0,0 +1,15 @@ +function ProjectsStrings (BaseString) { + BaseString.call(this, 'projects'); + + let t = this.t; + let ns = this.projects; + + ns.deleteProject = { + CONFIRM: t.s('Are you sure you want to delete this project?'), + INVALIDATE: t.s('Doing so will invalidate the following:') + }; +} + +ProjectsStrings.$inject = ['BaseStringService']; + +export default ProjectsStrings; diff --git a/awx/ui/client/src/shared/prompt-dialog.js b/awx/ui/client/src/shared/prompt-dialog.js index 96152c0b89..dcb1812f78 100644 --- a/awx/ui/client/src/shared/prompt-dialog.js +++ b/awx/ui/client/src/shared/prompt-dialog.js @@ -42,6 +42,7 @@ angular.module('PromptDialog', ['Utilities']) scope.promptBody = params.body; scope.promptAction = params.action; scope.promptActionText = (params.actionText === null || params.actionText === undefined || params.actionText === '') ? 'YES' : params.actionText; + scope.hideActionButton = params.hideActionButton ? true : false; local_backdrop = (params.backdrop === undefined) ? "static" : params.backdrop; diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index b400ec66ed..9ecdecdd9d 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -8,14 +8,16 @@ export default ['$scope', '$rootScope', 'Alert','TemplateList', 'Prompt', 'ProcessErrors', 'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', '$filter', 'Dataset', 'rbacUiControlService', 'TemplatesService','QuerySet', - 'TemplateCopyService', 'i18n', + 'TemplateCopyService', 'i18n', 'JobTemplateModel', 'TemplatesStrings', function( $scope, $rootScope, Alert, TemplateList, Prompt, ProcessErrors, GetBasePath, InitiatePlaybookRun, Wait, $state, $filter, Dataset, rbacUiControlService, TemplatesService, - qs, TemplateCopyService, i18n + qs, TemplateCopyService, i18n, JobTemplate, TemplatesStrings ) { + let jobTemplate = new JobTemplate(); + var list = TemplateList; init(); @@ -98,65 +100,85 @@ export default ['$scope', '$rootScope', $scope.deleteJobTemplate = function(template) { if(template) { - Prompt({ - hdr: i18n._('Delete'), - body: `
${i18n._("Are you sure you want to delete the template below?")}
${$filter('sanitize')(template.name)}
`, - action: function() { + var action = function() { + function handleSuccessfulDelete(isWorkflow) { + let stateParamId = isWorkflow ? $state.params.workflow_job_template_id : $state.params.job_template_id; - function handleSuccessfulDelete(isWorkflow) { - let stateParamId = isWorkflow ? $state.params.workflow_job_template_id : $state.params.job_template_id; + let reloadListStateParams = null; - let reloadListStateParams = null; + if($scope.templates.length === 1 && $state.params.template_search && !_.isEmpty($state.params.template_search.page) && $state.params.template_search.page !== '1') { + reloadListStateParams = _.cloneDeep($state.params); + reloadListStateParams.template_search.page = (parseInt(reloadListStateParams.template_search.page)-1).toString(); + } - if($scope.templates.length === 1 && $state.params.template_search && !_.isEmpty($state.params.template_search.page) && $state.params.template_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.template_search.page = (parseInt(reloadListStateParams.template_search.page)-1).toString(); - } + if (parseInt(stateParamId) === template.id) { + // Move the user back to the templates list + $state.go("templates", reloadListStateParams, {reload: true}); + } else { + $state.go(".", reloadListStateParams, {reload: true}); + } + Wait('stop'); + } - if (parseInt(stateParamId) === template.id) { - // Move the user back to the templates list - $state.go("templates", reloadListStateParams, {reload: true}); - } else { - $state.go(".", reloadListStateParams, {reload: true}); - } - Wait('stop'); - } + $('#prompt-modal').modal('hide'); + Wait('start'); + if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) { + TemplatesService.deleteWorkflowJobTemplate(template.id) + .then(function () { + handleSuccessfulDelete(true); + }) + .catch(function (response) { + Wait('stop'); + ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!', + msg: 'Call to delete workflow job template failed. DELETE returned status: ' + response.status + '.'}); + }); + } + else if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { + TemplatesService.deleteJobTemplate(template.id) + .then(function () { + handleSuccessfulDelete(); + }) + .catch(function (response) { + Wait('stop'); + ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!', + msg: 'Call to delete job template failed. DELETE returned status: ' + response.status + '.'}); + }); + } + else { + Wait('stop'); + Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while deleting.'); + } + }; - $('#prompt-modal').modal('hide'); - Wait('start'); - if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) { - TemplatesService.deleteWorkflowJobTemplate(template.id) - .then(function () { - handleSuccessfulDelete(true); - }) - .catch(function (response) { - Wait('stop'); - ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!', - msg: 'Call to delete workflow job template failed. DELETE returned status: ' + response.status + '.'}); - }); - } - else if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { - TemplatesService.deleteJobTemplate(template.id) - .then(function () { - handleSuccessfulDelete(); - }) - .catch(function (response) { - Wait('stop'); - ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!', - msg: 'Call to delete job template failed. DELETE returned status: ' + response.status + '.'}); - }); - } - else { - Wait('stop'); - Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while deleting.'); - } - }, - actionText: i18n._('DELETE') - }); - } - else { - Alert('Error: Unable to delete template', 'Template parameter is missing'); - } + jobTemplate.getDependentResourceCounts(template.id) + .then((counts) => { + const invalidateRelatedLines = []; + let deleteModalBody = `
${TemplatesStrings.get('jobTemplates.deleteJobTemplate.CONFIRM')}
`; + + counts.forEach(countObj => { + if(countObj.count && countObj.count > 0) { + invalidateRelatedLines.push(`
${countObj.label} ${countObj.count}
`); + } + }); + + if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { + deleteModalBody = `
${TemplatesStrings.get('jobTemplates.deleteJobTemplate.CONFIRM')} ${TemplatesStrings.get('jobTemplates.deleteJobTemplate.INVALIDATE')}
`; + invalidateRelatedLines.forEach(invalidateRelatedLine => { + deleteModalBody += invalidateRelatedLine; + }); + } + + Prompt({ + hdr: i18n._('Delete') + ' ' + $filter('sanitize')(template.name), + body: deleteModalBody, + action: action, + actionText: 'DELETE' + }); + }); + } + else { + Alert('Error: Unable to delete template', 'Template parameter is missing'); + } }; $scope.submitJob = function(template) { diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index 2712437a23..dfda8d26de 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -21,6 +21,7 @@ import WorkflowForm from './workflows.form'; import CompletedJobsList from './completed-jobs.list'; import InventorySourcesList from './inventory-sources.list'; import TemplateList from './templates.list'; +import TemplatesStrings from './templates.strings'; export default angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.name, labels.name, workflowAdd.name, workflowEdit.name, @@ -33,6 +34,7 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. .factory('CompletedJobsList', CompletedJobsList) .factory('TemplateList', TemplateList) .value('InventorySourcesList', InventorySourcesList) + .service('TemplatesStrings', TemplatesStrings) .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow, diff --git a/awx/ui/client/src/templates/templates.strings.js b/awx/ui/client/src/templates/templates.strings.js new file mode 100644 index 0000000000..d8150b0d73 --- /dev/null +++ b/awx/ui/client/src/templates/templates.strings.js @@ -0,0 +1,17 @@ +function TemplatesStrings (BaseString) { + BaseString.call(this, 'templates'); + + let t = this.t; + let ns = this.templates; + + ns.jobTemplates = { + deleteJobTemplate: { + CONFIRM: t.s('Are you sure you want to delete this job template?'), + INVALIDATE: t.s('Doing so will invalidate the following:') + } + }; +} + +TemplatesStrings.$inject = ['BaseStringService']; + +export default TemplatesStrings;