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,