diff --git a/awx/ui/.eslintrc.js b/awx/ui/.eslintrc.js
index c8b8c73bba..217601f5f9 100644
--- a/awx/ui/.eslintrc.js
+++ b/awx/ui/.eslintrc.js
@@ -45,6 +45,7 @@ module.exports = {
ignoreTemplateLiterals: true,
}],
'no-continue': 'off',
+ 'no-debugger': 'off',
'no-mixed-operators': 'off',
'no-param-reassign': 'off',
'no-plusplus': 'off',
diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js
index 560968c918..f0e4445669 100644
--- a/awx/ui/client/features/credentials/add-credentials.controller.js
+++ b/awx/ui/client/features/credentials/add-credentials.controller.js
@@ -42,7 +42,7 @@ function AddCredentialsController (models, $state, strings) {
vm.form.save = data => {
data.user = me.get('id');
- return credential.request('post', data);
+ return credential.request('post', { data });
};
vm.form.onSaveSuccess = res => {
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/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js
index 6fe3845266..e0a36c106f 100644
--- a/awx/ui/client/features/credentials/edit-credentials.controller.js
+++ b/awx/ui/client/features/credentials/edit-credentials.controller.js
@@ -88,7 +88,7 @@ function EditCredentialsController (models, $state, $scope, strings) {
data.user = me.get('id');
credential.unset('inputs');
- return credential.request('put', data);
+ return credential.request('put', { data });
};
vm.form.onSaveSuccess = () => {
diff --git a/awx/ui/client/index.template.ejs b/awx/ui/client/index.template.ejs
index f222a054d1..0301d42da0 100644
--- a/awx/ui/client/index.template.ejs
+++ b/awx/ui/client/index.template.ejs
@@ -33,7 +33,10 @@
diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js
index 72434e7d8d..de53c7d938 100644
--- a/awx/ui/client/lib/models/Base.js
+++ b/awx/ui/client/lib/models/Base.js
@@ -1,34 +1,46 @@
let $http;
let $q;
let cache;
+let strings;
-function request (method, resource) {
- if (Array.isArray(method)) {
- const promises = method.map((_method_, i) =>
- this.request(_method_, Array.isArray(resource) ? resource[i] : resource));
+function request (method, resource, config) {
+ let req = this.parseRequestConfig(method, resource, config);
+
+ if (Array.isArray(req.method)) {
+ const promises = req.method.map((_method, i) => {
+ const _resource = Array.isArray(req.resource) ? req.resource[i] : req.resource;
+
+ req = this.parseRequestConfig(_method, _resource, config);
+
+ if (this.isCacheable(req)) {
+ return this.requestWithCache(req);
+ }
+
+ return this.request(req);
+ });
return $q.all(promises);
}
- if (this.isCacheable(method, resource)) {
- return this.requestWithCache(method, resource);
+ if (this.isCacheable(req)) {
+ return this.requestWithCache(req);
}
- return this.http[method](resource);
+ return this.http[req.method](req);
}
-function requestWithCache (method, resource) {
- const key = cache.createKey(method, this.path, resource);
+function requestWithCache (config) {
+ const key = cache.createKey(config.method, this.path, config.resource);
return cache.get(key)
.then(data => {
if (data) {
- this.model[method.toUpperCase()] = data;
+ this.model[config.method.toUpperCase()] = data;
return data;
}
- return this.http[method](resource)
+ return this.http[config.method](config)
.then(res => {
cache.put(key, res.data);
@@ -77,18 +89,22 @@ function search (params, config) {
});
}
-function httpGet (resource) {
+function httpGet (config = {}) {
const req = {
method: 'GET',
url: this.path
};
- if (typeof resource === 'object') {
- this.model.GET = resource;
+ if (config.params) {
+ req.params = config.params;
+ }
+
+ if (typeof config.resource === 'object') {
+ this.model.GET = config.resource;
return $q.resolve();
- } else if (resource) {
- req.url = `${this.path}${resource}/`;
+ } else if (config.resource) {
+ req.url = `${this.path}${config.resource}/`;
}
return $http(req)
@@ -99,40 +115,51 @@ function httpGet (resource) {
});
}
-function httpPost (data) {
+function httpPost (config = {}) {
const req = {
method: 'POST',
url: this.path,
- data
+ data: config.data
};
- return $http(req).then(res => {
- this.model.GET = res.data;
+ return $http(req)
+ .then(res => {
+ this.model.GET = res.data;
- return res;
- });
+ return res;
+ });
}
-function httpPut (changes) {
- const model = Object.assign(this.get(), changes);
+function httpPatch (config = {}) {
+ const req = {
+ method: 'PUT',
+ url: `${this.path}${this.get('id')}/`,
+ data: config.changes
+ };
+
+ return $http(req);
+}
+
+function httpPut (config = {}) {
+ const model = _.merge(this.get(), config.data);
const req = {
method: 'PUT',
- url: `${this.path}${model.id}/`,
+ url: `${this.path}${this.get('id')}/`,
data: model
};
- return $http(req).then(res => res);
+ return $http(req);
}
-function httpOptions (resource) {
+function httpOptions (config = {}) {
const req = {
method: 'OPTIONS',
url: this.path
};
- if (resource) {
- req.url = `${this.path}${resource}/`;
+ if (config.resource) {
+ req.url = `${this.path}${config.resource}/`;
}
return $http(req)
@@ -143,6 +170,19 @@ function httpOptions (resource) {
});
}
+function httpDelete (config = {}) {
+ const req = {
+ method: 'DELETE',
+ url: this.path
+ };
+
+ if (config.resource) {
+ req.url = `${this.path}${config.resource}/`;
+ }
+
+ return $http(req);
+}
+
function options (keys) {
return this.find('options', keys);
}
@@ -349,6 +389,22 @@ function graft (id) {
return new this.Constructor('get', item, true);
}
+function getDependentResourceCounts (id) {
+ this.setDependentResources(id);
+
+ const promises = [];
+
+ this.dependentResources.forEach(resource => {
+ promises.push(resource.model.request('get', { params: resource.params })
+ .then(res => ({
+ label: resource.model.label,
+ count: res.data.count
+ })));
+ });
+
+ return Promise.all(promises);
+}
+
/**
* `create` is called on instantiation of every model. Models can be
* instantiated empty or with `GET` and/or `OPTIONS` requests that yield data.
@@ -358,20 +414,22 @@ function graft (id) {
* @arg {string=} method - Populate the model with `GET` or `OPTIONS` data.
* @arg {(string|Object)=} resource - An `id` reference to a particular
* resource or an existing model's data.
- * @arg {boolean=} isGraft - Create a new instance from existing model data.
+ * @arg {config=} config - Create a new instance from existing model data.
*
* @returns {(Object|Promise)} - Returns a reference to the model instance
* if an empty instance or graft is created. Otherwise, a promise yielding
* a model instance is returned.
*/
-function create (method, resource, isGraft, config) {
- if (!method) {
+function create (method, resource, config) {
+ const req = this.parseRequestConfig(method, resource, config);
+
+ if (!req || !req.method) {
return this;
}
- this.promise = this.request(method, resource, config);
+ this.promise = this.request(req);
- if (isGraft) {
+ if (req.graft) {
return this;
}
@@ -379,17 +437,55 @@ function create (method, resource, isGraft, config) {
.then(() => this);
}
+function parseRequestConfig (method, resource, config) {
+ if (!method) {
+ return null;
+ }
+
+ let req = {};
+
+ if (Array.isArray(method)) {
+ if (Array.isArray(resource)) {
+ req.resource = resource;
+ } else if (resource === null) {
+ req.resource = undefined;
+ } else if (typeof resource === 'object') {
+ req = resource;
+ }
+
+ req.method = method;
+ } else if (typeof method === 'string') {
+ if (resource === null) {
+ req.resource = undefined;
+ } else if (typeof resource === 'object') {
+ req = resource;
+ } else {
+ req.resource = resource;
+ }
+
+ req.method = method;
+ } else if (typeof method === 'object') {
+ req = method;
+ } else {
+ req = config;
+ req.method = method;
+ req.resource = resource === null ? undefined : resource;
+ }
+
+ return req;
+}
+
/**
* Base functionality for API interaction.
*
- * @arg {string} path - The API resource for the model extending BaseModel to
+ * @arg {string} resource - The API resource for the model extending BaseModel to
* use.
* @arg {Object=} settings - Configuration applied to all instances of the
* extending model.
* @arg {boolean=} settings.cache - Cache the model data.
*
*/
-function BaseModel (path, settings) {
+function BaseModel (resource, settings) {
this.create = create;
this.find = find;
this.get = get;
@@ -401,33 +497,39 @@ function BaseModel (path, settings) {
this.match = match;
this.normalizePath = normalizePath;
this.options = options;
+ this.parseRequestConfig = parseRequestConfig;
this.request = request;
this.requestWithCache = requestWithCache;
this.search = search;
this.set = set;
this.unset = unset;
this.extend = extend;
+ this.getDependentResourceCounts = getDependentResourceCounts;
this.http = {
get: httpGet.bind(this),
options: httpOptions.bind(this),
+ patch: httpPatch.bind(this),
post: httpPost.bind(this),
put: httpPut.bind(this),
+ delete: httpDelete.bind(this)
};
this.model = {};
- this.path = this.normalizePath(path);
+ this.path = this.normalizePath(resource);
+ this.label = strings.get(`${resource}.LABEL`);
this.settings = settings || {};
}
-function BaseModelLoader (_$http_, _$q_, _cache_) {
+function BaseModelLoader (_$http_, _$q_, _cache_, ModelsStrings) {
$http = _$http_;
$q = _$q_;
cache = _cache_;
+ strings = ModelsStrings;
return BaseModel;
}
-BaseModelLoader.$inject = ['$http', '$q', 'CacheService'];
+BaseModelLoader.$inject = ['$http', '$q', 'CacheService', 'ModelsStrings'];
export default BaseModelLoader;
diff --git a/awx/ui/client/lib/models/Config.js b/awx/ui/client/lib/models/Config.js
index 85be6ef147..9f2e3ab87a 100644
--- a/awx/ui/client/lib/models/Config.js
+++ b/awx/ui/client/lib/models/Config.js
@@ -1,5 +1,5 @@
let $log;
-let BaseModel;
+let Base;
function getTruncatedVersion () {
let version;
@@ -17,18 +17,18 @@ function isOpen () {
return this.get('license_info.license_type') === 'open';
}
-function ConfigModel (method, resource, graft) {
- BaseModel.call(this, 'config', { cache: true });
+function ConfigModel (method, resource, config) {
+ Base.call(this, 'config', { cache: true });
this.Constructor = ConfigModel;
this.getTruncatedVersion = getTruncatedVersion;
this.isOpen = isOpen;
- return this.create(method, resource, graft);
+ return this.create(method, resource, config);
}
-function ConfigModelLoader (_BaseModel_, _$log_) {
- BaseModel = _BaseModel_;
+function ConfigModelLoader (BaseModel, _$log_) {
+ Base = BaseModel;
$log = _$log_;
return ConfigModel;
diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js
index 3b0aafcf3d..93d29bb002 100644
--- a/awx/ui/client/lib/models/Credential.js
+++ b/awx/ui/client/lib/models/Credential.js
@@ -1,6 +1,10 @@
const ENCRYPTED_VALUE = '$encrypted$';
-let BaseModel;
+let Base;
+let Project;
+let JobTemplate;
+let Inventory;
+let InventorySource;
function createFormSchema (method, config) {
if (!config) {
@@ -40,22 +44,69 @@ function assignInputGroupValues (inputs) {
});
}
-function CredentialModel (method, resource, graft) {
- BaseModel.call(this, 'credentials');
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new Project(),
+ params: {
+ credential: id
+ }
+ },
+ {
+ model: new JobTemplate(),
+ params: {
+ credential: id,
+ ask_credential_on_launch: false
+ }
+ },
+ {
+ model: new Inventory(),
+ params: {
+ insights_credential: id
+ }
+ },
+ {
+ model: new InventorySource(),
+ params: {
+ credential: id
+ }
+ }
+ ];
+}
+
+function CredentialModel (method, resource, config) {
+ Base.call(this, 'credentials');
this.Constructor = CredentialModel;
this.createFormSchema = createFormSchema.bind(this);
this.assignInputGroupValues = assignInputGroupValues.bind(this);
+ this.setDependentResources = setDependentResources.bind(this);
- return this.create(method, resource, graft);
+ return this.create(method, resource, config);
}
-function CredentialModelLoader (_BaseModel_) {
- BaseModel = _BaseModel_;
+function CredentialModelLoader (
+ BaseModel,
+ ProjectModel,
+ JobTemplateModel,
+ InventoryModel,
+ InventorySourceModel
+) {
+ Base = BaseModel;
+ Project = ProjectModel;
+ JobTemplate = JobTemplateModel;
+ Inventory = InventoryModel;
+ InventorySource = InventorySourceModel;
return CredentialModel;
}
-CredentialModelLoader.$inject = ['BaseModel'];
+CredentialModelLoader.$inject = [
+ 'BaseModel',
+ 'ProjectModel',
+ 'JobTemplateModel',
+ 'InventoryModel',
+ 'InventorySourceModel'
+];
export default CredentialModelLoader;
diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js
index 334ba234ce..0607350ad8 100644
--- a/awx/ui/client/lib/models/CredentialType.js
+++ b/awx/ui/client/lib/models/CredentialType.js
@@ -1,4 +1,5 @@
-let BaseModel;
+let Base;
+let Credential;
function categorizeByKind () {
const group = {};
@@ -30,22 +31,38 @@ function mergeInputProperties () {
});
}
-function CredentialTypeModel (method, resource, graft) {
- BaseModel.call(this, 'credential_types');
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new Credential(),
+ params: {
+ credential_type: id
+ }
+ }
+ ];
+}
+
+function CredentialTypeModel (method, resource, config) {
+ Base.call(this, 'credential_types');
this.Constructor = CredentialTypeModel;
this.categorizeByKind = categorizeByKind.bind(this);
this.mergeInputProperties = mergeInputProperties.bind(this);
+ this.setDependentResources = setDependentResources.bind(this);
- return this.create(method, resource, graft);
+ return this.create(method, resource, config);
}
-function CredentialTypeModelLoader (_BaseModel_) {
- BaseModel = _BaseModel_;
+function CredentialTypeModelLoader (BaseModel, CredentialModel) {
+ Base = BaseModel;
+ Credential = CredentialModel;
return CredentialTypeModel;
}
-CredentialTypeModelLoader.$inject = ['BaseModel'];
+CredentialTypeModelLoader.$inject = [
+ 'BaseModel',
+ 'CredentialModel'
+];
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..3d270dfc79
--- /dev/null
+++ b/awx/ui/client/lib/models/Inventory.js
@@ -0,0 +1,36 @@
+let Base;
+let JobTemplate;
+
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new JobTemplate(),
+ params: {
+ inventory: id
+ }
+ }
+ ];
+}
+
+function InventoryModel (method, resource, config) {
+ Base.call(this, 'inventories');
+
+ this.Constructor = InventoryModel;
+ this.setDependentResources = setDependentResources.bind(this);
+
+ return this.create(method, resource, config);
+}
+
+function InventoryModelLoader (BaseModel, JobTemplateModel) {
+ Base = BaseModel;
+ JobTemplate = JobTemplateModel;
+
+ return InventoryModel;
+}
+
+InventoryModelLoader.$inject = [
+ 'BaseModel',
+ 'JobTemplateModel'
+];
+
+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..1b759410ab
--- /dev/null
+++ b/awx/ui/client/lib/models/InventoryScript.js
@@ -0,0 +1,36 @@
+let Base;
+let InventorySource;
+
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new InventorySource(),
+ params: {
+ source_script: id
+ }
+ }
+ ];
+}
+
+function InventoryScriptModel (method, resource, config) {
+ Base.call(this, 'inventory_scripts');
+
+ this.Constructor = InventoryScriptModel;
+ this.setDependentResources = setDependentResources.bind(this);
+
+ return this.create(method, resource, config);
+}
+
+function InventoryScriptModelLoader (BaseModel, InventorySourceModel) {
+ Base = BaseModel;
+ InventorySource = InventorySourceModel;
+
+ return InventoryScriptModel;
+}
+
+InventoryScriptModelLoader.$inject = [
+ 'BaseModel',
+ 'InventorySourceModel'
+];
+
+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..27ec05b6e9
--- /dev/null
+++ b/awx/ui/client/lib/models/InventorySource.js
@@ -0,0 +1,39 @@
+let Base;
+let WorkflowJobTemplateNode;
+
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new WorkflowJobTemplateNode(),
+ params: {
+ unified_job_template: id
+ }
+ }
+ ];
+}
+
+function InventorySourceModel (method, resource, config) {
+ Base.call(this, 'inventory_sources');
+
+ this.Constructor = InventorySourceModel;
+ this.setDependentResources = setDependentResources.bind(this);
+
+ return this.create(method, resource, config);
+}
+
+function InventorySourceModelLoader (
+ BaseModel,
+ WorkflowJobTemplateNodeModel
+) {
+ Base = BaseModel;
+ WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
+
+ return InventorySourceModel;
+}
+
+InventorySourceModelLoader.$inject = [
+ 'BaseModel',
+ 'WorkflowJobTemplateNodeModel'
+];
+
+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..885f1338a9
--- /dev/null
+++ b/awx/ui/client/lib/models/JobTemplate.js
@@ -0,0 +1,36 @@
+let Base;
+let WorkflowJobTemplateNode;
+
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new WorkflowJobTemplateNode(),
+ params: {
+ unified_job_template: id
+ }
+ }
+ ];
+}
+
+function JobTemplateModel (method, resource, config) {
+ Base.call(this, 'job_templates');
+
+ this.Constructor = JobTemplateModel;
+ this.setDependentResources = setDependentResources.bind(this);
+
+ return this.create(method, resource, config);
+}
+
+function JobTemplateModelLoader (BaseModel, WorkflowJobTemplateNodeModel) {
+ Base = BaseModel;
+ WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
+
+ return JobTemplateModel;
+}
+
+JobTemplateModelLoader.$inject = [
+ 'BaseModel',
+ 'WorkflowJobTemplateNodeModel'
+];
+
+export default JobTemplateModelLoader;
diff --git a/awx/ui/client/lib/models/Me.js b/awx/ui/client/lib/models/Me.js
index c2ee95d46e..221a29da34 100644
--- a/awx/ui/client/lib/models/Me.js
+++ b/awx/ui/client/lib/models/Me.js
@@ -1,11 +1,11 @@
-let BaseModel;
+let Base;
-function MeModel (method, resource, graft) {
- BaseModel.call(this, 'me');
+function MeModel (method, resource, config) {
+ Base.call(this, 'me');
this.Constructor = MeModel;
- return this.create(method, resource, graft)
+ return this.create(method, resource, config)
.then(() => {
if (this.has('results')) {
_.merge(this.model.GET, this.get('results[0]'));
@@ -16,8 +16,8 @@ function MeModel (method, resource, graft) {
});
}
-function MeModelLoader (_BaseModel_) {
- BaseModel = _BaseModel_;
+function MeModelLoader (BaseModel) {
+ Base = BaseModel;
return MeModel;
}
diff --git a/awx/ui/client/lib/models/Organization.js b/awx/ui/client/lib/models/Organization.js
index 53448cd80f..2e29f72473 100644
--- a/awx/ui/client/lib/models/Organization.js
+++ b/awx/ui/client/lib/models/Organization.js
@@ -1,19 +1,21 @@
-let BaseModel;
+let Base;
-function OrganizationModel (method, resource, graft) {
- BaseModel.call(this, 'organizations');
+function OrganizationModel (method, resource, config) {
+ Base.call(this, 'organizations');
this.Constructor = OrganizationModel;
- return this.create(method, resource, graft);
+ return this.create(method, resource, config);
}
-function OrganizationModelLoader (_BaseModel_) {
- BaseModel = _BaseModel_;
+function OrganizationModelLoader (BaseModel) {
+ Base = BaseModel;
return OrganizationModel;
}
-OrganizationModelLoader.$inject = ['BaseModel'];
+OrganizationModelLoader.$inject = [
+ 'BaseModel'
+];
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..bb08c09179
--- /dev/null
+++ b/awx/ui/client/lib/models/Project.js
@@ -0,0 +1,59 @@
+let Base;
+let JobTemplate;
+let WorkflowJobTemplateNode;
+let InventorySource;
+
+function setDependentResources (id) {
+ this.dependentResources = [
+ {
+ model: new JobTemplate(),
+ params: {
+ project: id
+ }
+ },
+ {
+ model: new WorkflowJobTemplateNode(),
+ params: {
+ unified_job_template: id
+ }
+ },
+ {
+ model: new InventorySource(),
+ params: {
+ source_project: id
+ }
+ }
+ ];
+}
+
+function ProjectModel (method, resource, config) {
+ Base.call(this, 'projects');
+
+ this.Constructor = ProjectModel;
+ this.setDependentResources = setDependentResources.bind(this);
+
+ return this.create(method, resource, config);
+}
+
+function ProjectModelLoader (
+ BaseModel,
+ JobTemplateModel,
+ WorkflowJobTemplateNodeModel,
+ InventorySourceModel,
+) {
+ Base = BaseModel;
+ JobTemplate = JobTemplateModel;
+ WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
+ InventorySource = InventorySourceModel;
+
+ return ProjectModel;
+}
+
+ProjectModelLoader.$inject = [
+ 'BaseModel',
+ 'JobTemplateModel',
+ 'WorkflowJobTemplateNodeModel',
+ 'InventorySourceModel'
+];
+
+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..c08cbd2e8c
--- /dev/null
+++ b/awx/ui/client/lib/models/WorkflowJobTemplateNode.js
@@ -0,0 +1,21 @@
+let Base;
+
+function WorkflowJobTemplateNodeModel (method, resource, config) {
+ Base.call(this, 'workflow_job_template_nodes');
+
+ this.Constructor = WorkflowJobTemplateNodeModel;
+
+ return this.create(method, resource, config);
+}
+
+function WorkflowJobTemplateNodeModelLoader (BaseModel) {
+ Base = BaseModel;
+
+ return WorkflowJobTemplateNodeModel;
+}
+
+WorkflowJobTemplateNodeModelLoader.$inject = [
+ 'BaseModel'
+];
+
+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..acb2dd5bbf
--- /dev/null
+++ b/awx/ui/client/lib/models/models.strings.js
@@ -0,0 +1,52 @@
+function ModelsStrings (BaseString) {
+ BaseString.call(this, 'models');
+
+ const { t } = this;
+ const ns = this.models;
+
+ ns.credentials = {
+ LABEL: t.s('Credentials')
+ };
+
+ ns.credential_types = {
+ LABEL: t.s('Credential Types')
+ };
+
+ ns.inventories = {
+ LABEL: t.s('Inventories')
+ };
+
+ ns.inventory_scripts = {
+ LABEL: t.s('Inventory Scripts')
+
+ };
+
+ ns.inventory_sources = {
+ LABEL: t.s('Inventory Sources')
+
+ };
+
+ ns.job_templates = {
+ LABEL: t.s('Job Templates')
+
+ };
+
+ ns.organizations = {
+ LABEL: t.s('Organizations')
+
+ };
+
+ ns.projects = {
+ LABEL: t.s('Projects')
+
+ };
+
+ ns.workflow_job_template_nodes = {
+ LABEL: t.s('Workflow Job Template Nodes')
+
+ };
+}
+
+ModelsStrings.$inject = ['BaseStringService'];
+
+export default ModelsStrings;
diff --git a/awx/ui/client/lib/services/base-string.service.js b/awx/ui/client/lib/services/base-string.service.js
index 4f0df6b545..74d6611956 100644
--- a/awx/ui/client/lib/services/base-string.service.js
+++ b/awx/ui/client/lib/services/base-string.service.js
@@ -4,7 +4,6 @@ let i18n;
function BaseStringService (namespace) {
const ERROR_NO_NAMESPACE = 'BaseString cannot be extended without providing a namespace';
- const ERROR_NO_STRING = 'No string exists with this name';
if (!namespace) {
throw new Error(ERROR_NO_NAMESPACE);
@@ -67,8 +66,6 @@ function BaseStringService (namespace) {
* the more globally relevant strings defined here. Strings with with dots as delimeters are
* supported to give flexibility to extending classes to nest strings as necessary.
*
- * If no match is found, an error is thrown to alert the developer immediately instead of
- * failing silently.
*
* The `t.s` and `t.p` calls should only be used where strings are defined in
*
.strings.js` files. To use translated strings elsewhere, access them through this
@@ -88,13 +85,13 @@ function BaseStringService (namespace) {
} else {
value = value[key];
}
-
- if (!value) {
- throw new Error(`${ERROR_NO_STRING}: ${name}`);
- }
});
- return typeof value === 'string' ? value : value(...args);
+ if (!value || typeof value === 'string') {
+ return value;
+ }
+
+ return value(...args);
};
}
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..da2b27858b 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,32 @@ 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'),
+ resourceName: $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 7031611f3a..ee1783fec8 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');
@@ -85,8 +87,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');
})
@@ -97,13 +98,34 @@ 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'),
+ resourceName: $filter('sanitize')(name),
+ body: deleteModalBody,
+ action: action,
+ actionText: 'DELETE'
+ });
+ });
};
$scope.$on(`ws-inventories`, function(e, data){
@@ -131,5 +153,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..b65c0433aa 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,35 @@
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'),
+ resourceName: $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..b6630b7c2a 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,32 @@ 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'),
+ resourceName: $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/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js
index 8846620f73..5175aa373e 100644
--- a/awx/ui/client/src/job-results/job-results.service.js
+++ b/awx/ui/client/src/job-results/job-results.service.js
@@ -91,11 +91,9 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo
deleteJob: function(job) {
Prompt({
hdr: i18n._("Delete Job"),
+ resourceName: `#${job.id} ` + $filter('sanitize')(job.name),
body: `
- ${i18n._("Are you sure you want to delete the job below?")}
-
-
- #${job.id} ${$filter('sanitize')(job.name)}
+ ${i18n._("Are you sure you want to delete this job?")}
`,
action: function() {
Wait('start');
@@ -140,11 +138,9 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo
Prompt({
hdr: i18n._('Cancel Job'),
+ resourceName: `#${job.id} ` + $filter('sanitize')(job.name),
body: `
- ${i18n._("Are you sure you want to cancel the job below?")}
-
-
- #${job.id} ${$filter('sanitize')(job.name)}
+ ${i18n._("Are you sure you want to cancel this job?")}
`,
action: function() {
Wait('start');
diff --git a/awx/ui/client/src/jobs/factories/delete-job.factory.js b/awx/ui/client/src/jobs/factories/delete-job.factory.js
index d6c3a0d4f1..3900fc0da1 100644
--- a/awx/ui/client/src/jobs/factories/delete-job.factory.js
+++ b/awx/ui/client/src/jobs/factories/delete-job.factory.js
@@ -106,10 +106,11 @@
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
- var cancelBody = "" + i18n._("Submit the request to cancel?") + "
";
- var deleteBody = "" + i18n._("Are you sure you want to delete the job below?") + "
#" + id + " " + $filter('sanitize')(job.name) + "
";
+ var cancelBody = "" + i18n._("Are you sure you want to submit the request to cancel this job?") + "
";
+ var deleteBody = "" + i18n._("Are you sure you want to delete this job?") + "
";
Prompt({
hdr: hdr,
+ resourceName: `#${job.id} ` + $filter('sanitize')(job.name),
body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody,
action: action,
actionText: (action_label === 'cancel' || job.status === 'new') ? i18n._("OK") : i18n._("DELETE")
diff --git a/awx/ui/client/src/notifications/notification-templates-list/list.controller.js b/awx/ui/client/src/notifications/notification-templates-list/list.controller.js
index 6a6cf071e4..6c3eb4b5bf 100644
--- a/awx/ui/client/src/notifications/notification-templates-list/list.controller.js
+++ b/awx/ui/client/src/notifications/notification-templates-list/list.controller.js
@@ -193,10 +193,11 @@
});
});
};
- var bodyHtml = '' + i18n._('Are you sure you want to delete the notification template below?') + '
' + $filter('sanitize')(name) + '
';
+
Prompt({
hdr: i18n._('Delete'),
- body: bodyHtml,
+ resourceName: $filter('sanitize')(name),
+ body: '' + i18n._('Are you sure you want to delete this notification template?') + '
',
action: action,
actionText: i18n._('DELETE')
});
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..6838b9cbd6 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,8 @@ export default ['$stateParams', '$scope', '$rootScope',
Prompt({
hdr: i18n._('Delete'),
- body: '' + i18n._('Are you sure you want to delete the organization below?') + '
' + $filter('sanitize')(name) + '
',
+ resourceName: $filter('sanitize')(name),
+ body: '' + i18n._('Are you sure you want to delete this organization? This makes everything in this organization unavailable.') + '
',
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..267eebe887 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,39 @@ 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'),
+ resourceName: $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/scheduler/factories/delete-schedule.factory.js b/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js
index e1f9376b48..2321028055 100644
--- a/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js
+++ b/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js
@@ -54,7 +54,8 @@ export default
Prompt({
hdr: hdr,
- body: 'Are you sure you want to delete the schedule below?
' + $filter('sanitize')(schedule.name) + '
',
+ resourceName: $filter('sanitize')(schedule.name),
+ body: 'Are you sure you want to delete this schedule?
',
action: action,
actionText: 'DELETE',
backdrop: false
diff --git a/awx/ui/client/src/shared/modal/modal.less b/awx/ui/client/src/shared/modal/modal.less
index 9c741f4131..46cdd03031 100644
--- a/awx/ui/client/src/shared/modal/modal.less
+++ b/awx/ui/client/src/shared/modal/modal.less
@@ -123,3 +123,7 @@
.Modal-footerButton + .Modal-footerButton {
margin-left: 20px;
}
+
+.Modal-titleResourceName {
+ color: @default-err;
+}
diff --git a/awx/ui/client/src/shared/prompt-dialog.js b/awx/ui/client/src/shared/prompt-dialog.js
index 96152c0b89..2d5cc58ddf 100644
--- a/awx/ui/client/src/shared/prompt-dialog.js
+++ b/awx/ui/client/src/shared/prompt-dialog.js
@@ -39,9 +39,11 @@ angular.module('PromptDialog', ['Utilities'])
scope = dialog.scope(), cls, local_backdrop;
scope.promptHeader = params.hdr;
+ scope.promptResourceName = params.resourceName;
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/shared/prompt/prompt.less b/awx/ui/client/src/shared/prompt/prompt.less
index 18c655de99..b2ad010803 100644
--- a/awx/ui/client/src/shared/prompt/prompt.less
+++ b/awx/ui/client/src/shared/prompt/prompt.less
@@ -23,3 +23,7 @@
font-weight: bold;
text-transform: uppercase;
}
+
+.Prompt-warningResourceTitle {
+ margin-right: 10px;
+}
diff --git a/awx/ui/client/src/teams/list/teams-list.controller.js b/awx/ui/client/src/teams/list/teams-list.controller.js
index b4d0176213..32b90528f8 100644
--- a/awx/ui/client/src/teams/list/teams-list.controller.js
+++ b/awx/ui/client/src/teams/list/teams-list.controller.js
@@ -6,9 +6,9 @@
export default ['$scope', 'Rest', 'TeamList', 'Prompt',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter',
- 'rbacUiControlService', 'Dataset',
+ 'rbacUiControlService', 'Dataset', 'i18n',
function($scope, Rest, TeamList, Prompt, ProcessErrors,
- GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) {
+ GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset, i18n) {
var list = TeamList,
defaultUrl = GetBasePath('teams');
@@ -77,7 +77,8 @@ export default ['$scope', 'Rest', 'TeamList', 'Prompt',
Prompt({
hdr: 'Delete',
- body: 'Are you sure you want to delete the team below?
' + $filter('sanitize')(name) + '
',
+ resourceName: $filter('sanitize')(name),
+ body: '' + i18n._('Are you sure you want to delete this team?') + '
',
action: action,
actionText: 'DELETE'
});
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..bd25143132 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,98 @@ 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');
- }
+ if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
+ Prompt({
+ hdr: i18n._('Delete'),
+ resourceName: $filter('sanitize')(template.name),
+ body: TemplatesStrings.get('workflowJobTemplates.deleteWorkflowJobTemplate.CONFIRM'),
+ action: action,
+ actionText: 'DELETE'
+ });
+ }
+ else if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
+
+ 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'),
+ resourceName: $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..2391ea8134
--- /dev/null
+++ b/awx/ui/client/src/templates/templates.strings.js
@@ -0,0 +1,23 @@
+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:')
+ }
+ };
+
+ ns.workflowJobTemplates = {
+ deleteWorkflowJobTemplate: {
+ CONFIRM: t.s('Are you sure you want to delete this workflow job template?')
+ }
+ };
+}
+
+TemplatesStrings.$inject = ['BaseStringService'];
+
+export default TemplatesStrings;
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html
index 57e1344ac0..a52e878e71 100644
--- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html
@@ -1,8 +1,12 @@
+
-
Are you sure you want to remove the template below?
-
{{nodeToBeDeleted.unifiedJobTemplate.name}}
+
Are you sure you want to delete this workflow node?
+