diff --git a/awx/ui/client/src/access/main.js b/awx/ui/client/src/access/main.js
index eedfe0db8c..1da571b524 100644
--- a/awx/ui/client/src/access/main.js
+++ b/awx/ui/client/src/access/main.js
@@ -7,10 +7,12 @@
import roleList from './rbac-role-column/roleList.directive';
import addRbacResource from './add-rbac-resource/main';
import addRbacUserTeam from './add-rbac-user-team/main';
+import permissionsList from './permissions-list.controller';
export default
angular.module('RbacModule', [
addRbacResource.name,
addRbacUserTeam.name
])
+ .controller('PermissionsList', permissionsList)
.directive('roleList', roleList);
diff --git a/awx/ui/client/src/access/permissions-list.controller.js b/awx/ui/client/src/access/permissions-list.controller.js
new file mode 100644
index 0000000000..2bd40eb59f
--- /dev/null
+++ b/awx/ui/client/src/access/permissions-list.controller.js
@@ -0,0 +1,82 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessErrors', 'Prompt', '$state',
+ function($scope, list, Dataset, Wait, Rest, ProcessErrors, Prompt, $state) {
+
+ init();
+
+ function init() {
+ $scope.list = list;
+ $scope[`${list.iterator}_dataset`] = Dataset.data;
+ $scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results;
+ }
+
+ $scope.deletePermissionFromUser = function(userId, userName, roleName, roleType, url) {
+ var action = function() {
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ Rest.setUrl(url);
+ Rest.post({ "disassociate": true, "id": userId })
+ .success(function() {
+ Wait('stop');
+ $state.go('.', null, {reload: true});
+ })
+ .error(function(data, status) {
+ ProcessErrors($scope, data, status, null, {
+ hdr: 'Error!',
+ msg: 'Could not disassociate user from role. Call to ' + url + ' failed. DELETE returned status: ' + status
+ });
+ });
+ };
+
+ Prompt({
+ hdr: `Remove role`,
+ body: `
+
+ Confirm the removal of the ${roleType}
+ ${roleName}
+ role associated with ${userName}.
+
+ `,
+ action: action,
+ actionText: 'REMOVE'
+ });
+ };
+
+ $scope.deletePermissionFromTeam = function(teamId, teamName, roleName, roleType, url) {
+ var action = function() {
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ Rest.setUrl(url);
+ Rest.post({ "disassociate": true, "id": teamId })
+ .success(function() {
+ Wait('stop');
+ $state.go('.', null, {reload: true});
+ })
+ .error(function(data, status) {
+ ProcessErrors($scope, data, status, null, {
+ hdr: 'Error!',
+ msg: 'Could not disassociate team from role. Call to ' + url + ' failed. DELETE returned status: ' + status
+ });
+ });
+ };
+
+ Prompt({
+ hdr: `Remove role`,
+ body: `
+
+ Confirm the removal of the ${roleType}
+ ${roleName}
+ role associated with the ${teamName} team.
+
+ `,
+ action: action,
+ actionText: 'REMOVE'
+ });
+ };
+ }
+];
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index 4cf399699d..70efcfa1c8 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -44,7 +44,6 @@ import './filters';
import { Home } from './controllers/Home';
import { SocketsController } from './controllers/Sockets';
import { CredentialsAdd, CredentialsEdit, CredentialsList } from './controllers/Credentials';
-import { JobsListController } from './controllers/Jobs';
import portalMode from './portal-mode/main';
import systemTracking from './system-tracking/main';
import inventories from './inventories/main';
@@ -70,6 +69,7 @@ import activityStream from './activity-stream/main';
import standardOut from './standard-out/main';
import Templates from './templates/main';
import credentials from './credentials/main';
+import jobs from './jobs/main';
import { ProjectsList, ProjectsAdd, ProjectsEdit } from './controllers/Projects';
import { UsersList, UsersAdd, UsersEdit } from './controllers/Users';
import { TeamsList, TeamsAdd, TeamsEdit } from './controllers/Teams';
@@ -134,6 +134,7 @@ var tower = angular.module('Tower', [
portalMode.name,
config.name,
credentials.name,
+ jobs.name,
//'templates',
'Utilities',
'OrganizationFormDefinition',
@@ -422,53 +423,6 @@ var tower = angular.module('Tower', [
}
});
- $stateExtender.addState({
- searchPrefix: 'job',
- name: 'jobs',
- url: '/jobs',
- ncyBreadcrumb: {
- label: N_("JOBS")
- },
- params: {
- job_search: {
- value: {
- not__launch_type: 'sync',
- order_by: '-finished'
- },
- squash: ''
- }
- },
- data: {
- socket: {
- "groups": {
- "jobs": ["status_changed"],
- "schedules": ["changed"]
- }
- }
- },
- resolve: {
- Dataset: ['AllJobsList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => {
- let path = GetBasePath(list.basePath) || GetBasePath(list.name);
- return qs.search(path, $stateParams[`${list.iterator}_search`]);
- }]
- },
- views: {
- '@': {
- templateUrl: urlPrefix + 'partials/jobs.html',
- },
- 'list@jobs': {
- templateProvider: function(AllJobsList, generateList) {
- let html = generateList.build({
- list: AllJobsList,
- mode: 'edit'
- });
- return html;
- },
- controller: JobsListController
- }
- }
- });
-
$stateExtender.addState({
name: 'userCredentials',
url: '/users/:user_id/credentials',
@@ -500,70 +454,6 @@ var tower = angular.module('Tower', [
}
});
- $rootScope.deletePermissionFromUser = function(userId, userName, roleName, roleType, url) {
- var action = function() {
- $('#prompt-modal').modal('hide');
- Wait('start');
- Rest.setUrl(url);
- Rest.post({ "disassociate": true, "id": userId })
- .success(function() {
- Wait('stop');
- $rootScope.$broadcast("refreshList", "permission");
- })
- .error(function(data, status) {
- ProcessErrors($rootScope, data, status, null, {
- hdr: 'Error!',
- msg: 'Could not disassociate user from role. Call to ' + url + ' failed. DELETE returned status: ' + status
- });
- });
- };
-
- Prompt({
- hdr: `Remove role`,
- body: `
-
- Confirm the removal of the ${roleType}
- ${roleName}
- role associated with ${userName}.
-
- `,
- action: action,
- actionText: 'REMOVE'
- });
- };
-
- $rootScope.deletePermissionFromTeam = function(teamId, teamName, roleName, roleType, url) {
- var action = function() {
- $('#prompt-modal').modal('hide');
- Wait('start');
- Rest.setUrl(url);
- Rest.post({ "disassociate": true, "id": teamId })
- .success(function() {
- Wait('stop');
- $rootScope.$broadcast("refreshList", "role");
- })
- .error(function(data, status) {
- ProcessErrors($rootScope, data, status, null, {
- hdr: 'Error!',
- msg: 'Could not disassociate team from role. Call to ' + url + ' failed. DELETE returned status: ' + status
- });
- });
- };
-
- Prompt({
- hdr: `Remove role`,
- body: `
-
- Confirm the removal of the ${roleType}
- ${roleName}
- role associated with the ${teamName} team.
-
- `,
- action: action,
- actionText: 'REMOVE'
- });
- };
-
function activateTab() {
// Make the correct tab active
var base = $location.path().replace(/^\//, '').split('/')[0];
diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/jobs/jobs-list.controller.js
similarity index 55%
rename from awx/ui/client/src/controllers/Jobs.js
rename to awx/ui/client/src/jobs/jobs-list.controller.js
index cc6a2842a0..d590cf7e17 100644
--- a/awx/ui/client/src/controllers/Jobs.js
+++ b/awx/ui/client/src/jobs/jobs-list.controller.js
@@ -10,14 +10,16 @@
* @description This controller's for the jobs page
*/
-
-
-export function JobsListController($state, $rootScope, $log, $scope, $compile, $stateParams,
- ClearScope, Find, DeleteJob, RelaunchJob, AllJobsList, ScheduledJobsList, GetBasePath, Dataset, qs) {
+ export default ['$state', '$rootScope', '$log', '$scope', '$compile', '$stateParams',
+ 'ClearScope', 'Find', 'DeleteJob', 'RelaunchJob', 'AllJobsList', 'ScheduledJobsList',
+ 'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate',
+ function($state, $rootScope, $log, $scope, $compile, $stateParams,
+ ClearScope, Find, DeleteJob, RelaunchJob, AllJobsList, ScheduledJobsList,
+ GetBasePath, Dataset, qs, ListDefinition, $interpolate) {
ClearScope();
- var list = AllJobsList;
+ var list = ListDefinition;
init();
@@ -44,27 +46,29 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
- $scope[list.name].forEach(function(item, item_idx) {
- var itm = $scope[list.name][item_idx];
+ if($scope[list.name] && $scope[list.name].length > 0) {
+ $scope[list.name].forEach(function(item, item_idx) {
+ var itm = $scope[list.name][item_idx];
- if(item.summary_fields && item.summary_fields.source_workflow_job &&
- item.summary_fields.source_workflow_job.id){
- item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`;
- }
-
- // Set the item type label
- if (list.fields.type && $scope.options &&
- $scope.options.hasOwnProperty('type')) {
- $scope.options.type.choices.every(function(choice) {
- if (choice[0] === item.type) {
- itm.type_label = choice[1];
- return false;
- }
- return true;
- });
+ if(item.summary_fields && item.summary_fields.source_workflow_job &&
+ item.summary_fields.source_workflow_job.id){
+ item.workflow_result_link = `/#/workflows/${item.summary_fields.source_workflow_job.id}`;
}
- buildTooltips(itm);
- });
+
+ // Set the item type label
+ if (list.fields.type && $scope.options &&
+ $scope.options.hasOwnProperty('type')) {
+ $scope.options.type.choices.every(function(choice) {
+ if (choice[0] === item.type) {
+ itm.type_label = choice[1];
+ return false;
+ }
+ return true;
+ });
+ }
+ buildTooltips(itm);
+ });
+ }
}
function buildTooltips(job) {
job.status_tip = 'Job ' + job.status + ". Click for details.";
@@ -75,14 +79,31 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $
};
$scope.relaunchJob = function(event, id) {
- var job, typeId;
+ let job, typeId, jobs;
try {
$(event.target).tooltip('hide');
} catch (e) {
//ignore
}
- job = Find({ list: $scope.jobs, key: 'id', val: id });
+ if ($scope.completed_jobs) {
+ jobs = $scope.completed_jobs;
+ }
+ else if ($scope.running_jobs) {
+ jobs = $scope.running_jobs;
+ }
+ else if ($scope.queued_jobs) {
+ jobs = $scope.queued_jobs;
+ }
+ else if ($scope.all_jobs) {
+ jobs = $scope.all_jobs;
+ }
+ else if ($scope.jobs) {
+ jobs = $scope.jobs;
+ }
+
+ job = Find({list: jobs, key: 'id', val: id });
+
if (job.type === 'inventory_update') {
typeId = job.inventory_source;
} else if (job.type === 'project_update') {
@@ -122,7 +143,14 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $
};
$scope.$on('ws-jobs', function(){
- let path = GetBasePath(list.basePath) || GetBasePath(list.name);
+ let path;
+ if (GetBasePath(list.basePath) || GetBasePath(list.name)) {
+ path = GetBasePath(list.basePath) || GetBasePath(list.name);
+ } else {
+ // completed jobs base path involves $stateParams
+ let interpolator = $interpolate(list.basePath);
+ path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
+ }
qs.search(path, $state.params[`${list.iterator}_search`])
.then(function(searchResponse) {
$scope[`${list.iterator}_dataset`] = searchResponse.data;
@@ -133,8 +161,4 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $
$scope.$on('ws-schedules', function(){
$state.reload();
});
-}
-
-JobsListController.$inject = ['$state', '$rootScope', '$log', '$scope', '$compile', '$stateParams',
- 'ClearScope', 'Find', 'DeleteJob', 'RelaunchJob', 'AllJobsList', 'ScheduledJobsList', 'GetBasePath', 'Dataset','QuerySet'
-];
+}];
diff --git a/awx/ui/client/src/partials/jobs.html b/awx/ui/client/src/jobs/jobs.partial.html
similarity index 100%
rename from awx/ui/client/src/partials/jobs.html
rename to awx/ui/client/src/jobs/jobs.partial.html
diff --git a/awx/ui/client/src/jobs/jobs.route.js b/awx/ui/client/src/jobs/jobs.route.js
new file mode 100644
index 0000000000..aeba88e427
--- /dev/null
+++ b/awx/ui/client/src/jobs/jobs.route.js
@@ -0,0 +1,58 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ import { N_ } from '../i18n';
+ import {templateUrl} from '../shared/template-url/template-url.factory';
+
+export default {
+ searchPrefix: 'job',
+ name: 'jobs',
+ url: '/jobs',
+ ncyBreadcrumb: {
+ label: N_("JOBS")
+ },
+ params: {
+ job_search: {
+ value: {
+ not__launch_type: 'sync',
+ order_by: '-finished'
+ },
+ squash: ''
+ }
+ },
+ data: {
+ socket: {
+ "groups": {
+ "jobs": ["status_changed"],
+ "schedules": ["changed"]
+ }
+ }
+ },
+ resolve: {
+ Dataset: ['AllJobsList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => {
+ let path = GetBasePath(list.basePath) || GetBasePath(list.name);
+ return qs.search(path, $stateParams[`${list.iterator}_search`]);
+ }],
+ ListDefinition: ['AllJobsList', (list) => {
+ return list;
+ }]
+ },
+ views: {
+ '@': {
+ templateUrl: templateUrl('jobs/jobs')
+ },
+ 'list@jobs': {
+ templateProvider: function(AllJobsList, generateList) {
+ let html = generateList.build({
+ list: AllJobsList,
+ mode: 'edit'
+ });
+ return html;
+ },
+ controller: 'JobsList'
+ }
+ }
+};
diff --git a/awx/ui/client/src/jobs/main.js b/awx/ui/client/src/jobs/main.js
new file mode 100644
index 0000000000..b1782cead6
--- /dev/null
+++ b/awx/ui/client/src/jobs/main.js
@@ -0,0 +1,15 @@
+/*************************************************
+ * Copyright (c) 2017 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import jobsList from './jobs-list.controller';
+import jobsRoute from './jobs.route';
+
+export default
+ angular.module('JobsModule', [])
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(jobsRoute);
+ }])
+ .controller('JobsList', jobsList);
diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js
index 6ed6517ac7..9722e24f2a 100644
--- a/awx/ui/client/src/shared/stateDefinitions.factory.js
+++ b/awx/ui/client/src/shared/stateDefinitions.factory.js
@@ -230,7 +230,7 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
break;
}
states.push(formNode);
- states = states.concat(this.generateLookupNodes(form, formNode)).concat(this.generateFormListDefinitions(form, formNode));
+ states = states.concat(this.generateLookupNodes(form, formNode)).concat(this.generateFormListDefinitions(form, formNode, params));
return states;
},
/**
@@ -241,7 +241,7 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
* @params {object} formStateDefinition - the parent form node
* @returns {array} Array of state definitions [{...}, {...}, ...]
*/
- generateFormListDefinitions: function(form, formStateDefinition) {
+ generateFormListDefinitions: function(form, formStateDefinition, params) {
function buildRbacUserTeamDirective(){
let states = [];
@@ -559,14 +559,17 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
function buildListDefinition(field) {
let state,
- list = field.include ? $injector.get(field.include) : field;
- state = $stateExtender.buildDefinition({
+ list = field.include ? $injector.get(field.include) : field,
+ // Added this line specifically for Completed Jobs but should be OK
+ // for all the rest of the related tabs
+ breadcrumbLabel = field.iterator.replace('_', ' '),
+ stateConfig = {
searchPrefix: `${list.iterator}`,
name: `${formStateDefinition.name}.${list.iterator}s`,
url: `/${list.iterator}s`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
- label: `${field.iterator}s`
+ label: `${breadcrumbLabel}s`
},
params: {
[list.iterator + '_search']: {
@@ -583,14 +586,7 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
FormDefinition() : FormDefinition
});
return html;
- },
- controller: ['$scope', 'ListDefinition', 'Dataset',
- function($scope, list, Dataset) {
- $scope.list = list;
- $scope[`${list.iterator}_dataset`] = Dataset.data;
- $scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results;
- }
- ]
+ }
}
},
resolve: {
@@ -611,7 +607,26 @@ export default ['$injector', '$stateExtender', '$log', 'i18n', function($injecto
}
]
}
- });
+ };
+
+ if(params.controllers && params.controllers.related && params.controllers.related[field.name]) {
+ stateConfig.views.related.controller = params.controllers.related[field.name];
+ }
+ else if(field.name === 'permissions') {
+ stateConfig.views.related.controller = 'PermissionsList';
+ }
+ else {
+ // Generic controller
+ stateConfig.views.related.controller = ['$scope', 'ListDefinition', 'Dataset',
+ function($scope, list, Dataset) {
+ $scope.list = list;
+ $scope[`${list.iterator}_dataset`] = Dataset.data;
+ $scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results;
+ }
+ ];
+ }
+
+ state = $stateExtender.buildDefinition(stateConfig);
// appy any default search parameters in form definition
if (field.search) {
state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search);
diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js
index 70bb1a9bcf..953fd22c95 100644
--- a/awx/ui/client/src/templates/main.js
+++ b/awx/ui/client/src/templates/main.js
@@ -52,7 +52,10 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA
modes: ['edit'],
form: 'JobTemplateForm',
controllers: {
- edit: 'JobTemplateEdit'
+ edit: 'JobTemplateEdit',
+ related: {
+ completed_jobs: 'JobsList'
+ }
},
data: {
activityStream: true,