Merge pull request #1657 from jlmitch5/jobsNewListUi

implement new style jobs list in ui
This commit is contained in:
John Mitchell 2018-03-26 11:07:56 -04:00 committed by GitHub
commit ac70945071
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 883 additions and 1605 deletions

View File

@ -6,6 +6,7 @@ import atFeaturesApplications from '~features/applications';
import atFeaturesCredentials from '~features/credentials';
import atFeaturesTemplates from '~features/templates';
import atFeaturesUsers from '~features/users';
import atFeaturesJobs from '~features/jobs';
const MODULE_NAME = 'at.features';
@ -16,7 +17,8 @@ angular.module(MODULE_NAME, [
atFeaturesApplications,
atFeaturesCredentials,
atFeaturesTemplates,
atFeaturesUsers
atFeaturesUsers,
atFeaturesJobs
]);
export default MODULE_NAME;

View File

@ -0,0 +1,13 @@
import JobsStrings from './jobs.strings';
import jobsRoute from './routes/jobs.route';
const MODULE_NAME = 'at.features.jobs';
angular
.module(MODULE_NAME, [])
.service('JobsStrings', JobsStrings)
.run(['$stateExtender', ($stateExtender) => {
$stateExtender.addState(jobsRoute);
}]);
export default MODULE_NAME;

View File

@ -0,0 +1,19 @@
<div class="tab-pane" id="jobs-page">
<at-panel ng-cloak id="htmlTemplate">
<div>
<div ng-hide="$state.is('jobs.schedules')">
<at-panel-heading hide-dismiss="true">
<translate>JOBS</translate>
</at-panel-heading>
<div ui-view="jobsList"></div>
</div>
<div ng-hide="!$state.is('jobs.schedules')">
<at-panel-heading hide-dismiss="true">
<translate>SCHEDULES</translate>
</at-panel-heading>
<div ui-view="schedulesList"></div>
</div>
</div>
</at-panel>
<div ng-include="'/static/partials/logviewer.html'"></div>
</div>

View File

@ -0,0 +1,20 @@
function JobsStrings (BaseString) {
BaseString.call(this, 'jobs');
const { t } = this;
const ns = this.jobs;
ns.list = {
ROW_ITEM_LABEL_STARTED: t.s('Started'),
ROW_ITEM_LABEL_FINISHED: t.s('Finished'),
ROW_ITEM_LABEL_LAUNCHED_BY: t.s('Launched By'),
ROW_ITEM_LABEL_JOB_TEMPLATE: t.s('Job Template'),
ROW_ITEM_LABEL_INVENTORY: t.s('Inventory'),
ROW_ITEM_LABEL_PROJECT: t.s('Project'),
ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'),
};
}
JobsStrings.$inject = ['BaseStringService'];
export default JobsStrings;

View File

@ -0,0 +1,137 @@
/** ***********************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
************************************************ */
const mapChoices = choices => Object
.assign(...choices.map(([k, v]) => ({ [k]: v })));
function ListJobsController (
$scope,
$state,
Dataset,
resolvedModels,
strings,
qs,
Prompt,
$filter,
ProcessErrors,
Wait,
Rest
) {
const vm = this || {};
const [unifiedJob] = resolvedModels;
vm.strings = strings;
// smart-search
const name = 'jobs';
const iterator = 'job';
const key = 'job_dataset';
$scope.list = { iterator, name };
$scope.collection = { iterator, basePath: 'unified_jobs' };
$scope[key] = Dataset.data;
$scope[name] = Dataset.data.results;
$scope.$on('updateDataset', (e, dataset) => {
$scope[key] = dataset;
$scope[name] = dataset.results;
});
$scope.$on('ws-jobs', () => {
qs.search(unifiedJob.path, $state.params.job_search)
.then(({ data }) => {
$scope.$emit('updateDataset', data);
});
});
vm.jobTypes = mapChoices(unifiedJob
.options('actions.GET.type.choices'));
vm.getLink = ({ type, id }) => {
let link;
switch (type) {
case 'job':
link = `/#/jobs/${id}`;
break;
case 'ad_hoc_command':
link = `/#/ad_hoc_commands/${id}`;
break;
case 'system_job':
link = `/#/management_jobs/${id}`;
break;
case 'project_update':
link = `/#/scm_update/${id}`;
break;
case 'inventory_update':
link = `/#/inventory_sync/${id}`;
break;
case 'workflow_job':
link = `/#/workflows/${id}`;
break;
default:
link = '';
break;
}
return link;
};
vm.deleteJob = (job) => {
const action = () => {
$('#prompt-modal').modal('hide');
Wait('start');
Rest.setUrl(job.url);
Rest.destroy()
.then(() => {
let reloadListStateParams = null;
if ($scope.jobs.length === 1 && $state.params.job_search &&
!_.isEmpty($state.params.job_search.page) &&
$state.params.job_search.page !== '1') {
const page = `${(parseInt(reloadListStateParams
.job_search.page, 10) - 1)}`;
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.job_search.page = page;
}
$state.go('.', reloadListStateParams, { reload: true });
})
.catch(({ data, status }) => {
ProcessErrors($scope, data, status, null, {
hdr: strings.get('error.HEADER'),
msg: strings.get('error.CALL', { path: `${job.url}`, status })
});
})
.finally(() => {
Wait('stop');
});
};
const deleteModalBody = `<div class="Prompt-bodyQuery">${strings.get('deleteResource.CONFIRM', 'job')}</div>`;
Prompt({
hdr: strings.get('deleteResource.HEADER'),
resourceName: $filter('sanitize')(job.name),
body: deleteModalBody,
action,
actionText: 'DELETE'
});
};
}
ListJobsController.$inject = [
'$scope',
'$state',
'Dataset',
'resolvedModels',
'JobsStrings',
'QuerySet',
'Prompt',
'$filter',
'ProcessErrors',
'Wait',
'Rest'
];
export default ListJobsController;

View File

@ -0,0 +1,82 @@
<at-panel-body>
<div class="at-List-toolbar">
<smart-search
class="at-List-search"
django-model="jobs"
base-path="unified_jobs"
iterator="job"
list="list"
dataset="job_dataset"
collection="collection"
search-tags="searchTags"
query-set="querySet">
</smart-search>
</div>
<at-list results="jobs">
<!-- TODO: implement resources are missing red indicator as present in mockup -->
<at-row ng-repeat="job in jobs" job-id="{{ job.id }}">
<div class="at-Row-items">
<!-- TODO: include workflow tab as well -->
<at-row-item
status="{{ job.status }}"
status-tip="Job {{job.status}}. Click for details."
header-value="{{ job.name }}"
header-link="{{ vm.getLink(job) }}"
header-tag="{{ vm.jobTypes[job.type] }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_STARTED') }}"
value="{{ job.started | longDate }}"
inline="true">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_FINISHED') }}"
value="{{ job.finished | longDate }}"
inline="true">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_LAUNCHED_BY') }}"
value="{{ job.summary_fields.created_by.username }}"
value-link="/#/users/{{ job.summary_fields.created_by.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_JOB_TEMPLATE') }}"
value="{{ job.summary_fields.job_template.name }}"
value-link="/#/templates/job_template/{{ job.summary_fields.job_template.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_INVENTORY') }}"
value="{{ job.summary_fields.inventory.name }}"
value-link="/#/inventories/{{ job.summary_fields.inventory.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_PROJECT') }}"
value="{{ job.summary_fields.project.name }}"
value-link="/#/projects/{{ job.summary_fields.project.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_CREDENTIALS') }}"
tag-values="job.summary_fields.credentials"
tags-are-creds="true">
</at-row-item>
<labels-list class="LabelList" show-delete="false" is-row-item="true">
</labels-list>
</div>
<div class="at-Row-actions">
<at-relaunch job="job"
ng-show="job.summary_fields.user_capabilities.start">
</at-relaunch>
<at-row-action icon="fa-trash" ng-click="vm.deleteJob(job)"
ng-show="job.summary_fields.user_capabilities.delete">
</at-row-action>
</div>
</at-row>
</at-list>
<paginate
collection="collection"
dataset="job_dataset"
iterator="job"
base-path="unified_jobs"
query-set="querySet">
</paginate>
</at-panel-body>

View File

@ -0,0 +1,64 @@
import listContainerController from '~src/instance-groups/jobs/instanceGroupsJobsListContainer.controller';
import { N_ } from '../../../src/i18n';
import jobsListController from '../jobsList.controller';
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
const listContainerTemplate = require('~src/instance-groups/jobs/instanceGroupsJobsListContainer.partial.html');
export default {
name: 'instanceGroups.jobs',
url: '/:instance_group_id/jobs',
ncyBreadcrumb: {
parent: 'instanceGroups.edit',
label: N_('JOBS')
},
params: {
job_search: {
value: {
page_size: '10',
order_by: '-finished'
},
dynamic: true
}
},
views: {
'instanceGroupsJobsContainer@instanceGroups': {
templateUrl: listContainerTemplate,
controller: listContainerController,
controllerAs: 'vm'
},
'jobsList@instanceGroups.jobs': {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
},
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const groupId = $stateParams.instance_group_id;
const searchParam = $stateParams.job_search;
const searchPath = `api/v2/instance_groups/${groupId}/jobs`;
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
]
}
};

View File

@ -0,0 +1,64 @@
import listContainerController from '~src/instance-groups/jobs/instanceJobsListContainer.controller';
import { N_ } from '../../../src/i18n';
import jobsListController from '../jobsList.controller';
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
const listContainerTemplate = require('~src/instance-groups/jobs/instanceJobsListContainer.partial.html');
export default {
name: 'instanceGroups.instanceJobs',
url: '/:instance_group_id/instances/:instance_id/jobs',
ncyBreadcrumb: {
parent: 'instanceGroups.edit',
label: N_('JOBS')
},
views: {
'instanceJobsContainer@instanceGroups': {
templateUrl: listContainerTemplate,
controller: listContainerController,
controllerAs: 'vm'
},
'jobsList@instanceGroups.instanceJobs': {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
},
},
params: {
job_search: {
value: {
page_size: '10',
order_by: '-finished'
},
dynamic: true
},
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const instanceId = $stateParams.instance_id;
const searchParam = $stateParams.job_search;
const searchPath = `api/v2/instances/${instanceId}/jobs`;
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
]
}
};

View File

@ -0,0 +1,63 @@
import { N_ } from '../../../src/i18n';
import jobsListController from '../jobsList.controller';
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
export default {
url: '/completed_jobs',
params: {
job_search: {
value: {
page_size: '20',
or__job__inventory: '',
or__adhoccommand__inventory: '',
or__inventoryupdate__inventory_source__inventory: '',
order_by: '-id'
},
dynamic: true,
squash: ''
}
},
ncyBreadcrumb: {
label: N_('JOBS')
},
views: {
related: {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
}
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const inventoryId = $stateParams.inventory_id ?
$stateParams.inventory_id : $stateParams.smartinventory_id;
const searchParam = _.assign($stateParams.job_search, {
or__job__inventory: inventoryId,
or__adhoccommand__inventory: inventoryId,
or__inventoryupdate__inventory_source__inventory: inventoryId });
const searchPath = GetBasePath('unified_jobs');
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
]
}
};

View File

@ -0,0 +1,67 @@
import { N_ } from '../../../src/i18n';
import jobsListController from '../jobsList.controller';
const indexTemplate = require('~features/jobs/index.view.html');
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
export default {
searchPrefix: 'job',
name: 'jobs',
url: '/jobs',
ncyBreadcrumb: {
label: N_('JOBS')
},
params: {
job_search: {
value: {
not__launch_type: 'sync',
order_by: '-finished'
},
dynamic: true,
squash: false
}
},
data: {
socket: {
groups: {
jobs: ['status_changed'],
schedules: ['changed']
}
}
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const searchParam = $stateParams.job_search;
const searchPath = GetBasePath('unified_jobs');
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
],
},
views: {
'@': {
templateUrl: indexTemplate
},
'jobsList@jobs': {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
}
}
};

View File

@ -0,0 +1,50 @@
import jobsListController from '../jobsList.controller';
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
export default {
name: 'portalMode.allJobs',
url: '/alljobs?{job_search:queryset}',
params: {
job_search: {
value: {
page_size: '20',
order_by: '-finished'
},
dynamic: true
}
},
views: {
'jobs@portalMode': {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
}
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const searchParam = $stateParams.job_search;
const searchPath = GetBasePath('unified_jobs');
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
]
}
};

View File

@ -0,0 +1,53 @@
import jobsListController from '../jobsList.controller';
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
export default {
name: 'portalMode.myJobs',
url: '/myjobs?{job_search:queryset}',
params: {
job_search: {
value: {
page_size: '20',
order_by: '-finished',
created_by: null
},
dynamic: true
}
},
views: {
'jobs@portalMode': {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
}
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
'$rootScope',
($stateParams, Wait, GetBasePath, qs, $rootScope) => {
const searchParam = _.assign($stateParams.job_search, {
created_by: $rootScope.current_user.id });
const searchPath = GetBasePath('unified_jobs');
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
]
}
};

View File

@ -0,0 +1,60 @@
import { N_ } from '../../../src/i18n';
import jobsListController from '../jobsList.controller';
const jobsListTemplate = require('~features/jobs/jobsList.view.html');
export default {
url: '/completed_jobs',
name: 'templates.editJobTemplate.completed_jobs',
params: {
job_search: {
value: {
page_size: '20',
job__job_template: '',
order_by: '-id'
},
dynamic: true,
squash: ''
}
},
ncyBreadcrumb: {
label: N_('COMPLETED JOBS')
},
views: {
related: {
templateUrl: jobsListTemplate,
controller: jobsListController,
controllerAs: 'vm'
}
},
resolve: {
resolvedModels: [
'UnifiedJobModel',
(UnifiedJob) => {
const models = [
new UnifiedJob(['options']),
];
return Promise.all(models);
},
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const templateId = $stateParams.job_template_id ?
$stateParams.job_template_id : $stateParams.job_template_id;
const searchParam = _.assign($stateParams
.job_search, { job__job_template: templateId });
const searchPath = GetBasePath('unified_jobs');
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => Wait('stop'));
}
]
}
};

View File

@ -151,6 +151,10 @@
line-height: @at-height-list-row-item;
}
.at-RowItem-status {
margin-right: @at-margin-right-list-row-item-status;
}
.at-RowItem--isHeader {
color: @at-color-body-text;
margin-bottom: @at-margin-bottom-list-header;
@ -263,6 +267,16 @@
margin: 2px 20px 0 0;
}
.at-RowItem--inline {
display: inline-flex;
margin-right: @at-margin-right-list-row-item-inline;
.at-RowItem-label {
width: auto;
margin-right: @at-margin-right-list-row-item-inline-label;
}
}
@media screen and (max-width: @at-breakpoint-compact-list) {
.at-Row-actions {
flex-direction: column;
@ -271,4 +285,14 @@
.at-RowAction {
margin: @at-margin-list-row-action-mobile;
}
.at-RowItem--inline {
display: flex;
margin-right: inherit;
.at-RowItem-label {
width: @at-width-list-row-item-label;
margin-right: inherit;
}
}
}

View File

@ -7,10 +7,13 @@ function atRowItem () {
transclude: true,
templateUrl,
scope: {
inline: '@',
badge: '@',
headerValue: '@',
headerLink: '@',
headerTag: '@',
status: '@',
statusTip: '@',
labelValue: '@',
labelLink: '@',
labelState: '@',

View File

@ -1,5 +1,13 @@
<div class="at-RowItem" ng-class="{'at-RowItem--isHeader': headerValue}"
ng-show="headerValue || value || (smartStatus && smartStatus.summary_fields.recent_jobs.length) || (tagValues && tagValues.length)">
<div class="at-RowItem" ng-class="{'at-RowItem--isHeader': headerValue, 'at-RowItem--inline': inline}"
ng-show="status || headerValue || value || (smartStatus && smartStatus.summary_fields.recent_jobs.length) || (tagValues && tagValues.length)">
<div class="at-RowItem-status" ng-if="status">
<a ng-if="headerLink" ng-href="{{ headerLink }}"
aw-tool-tip="{{ statusTip }}" aw-tip-watch="statusTip"
data-placement="top">
<i class="fa icon-job-{{ status }}"></i>
</a>
<i ng-if="!headerLink" class="fa icon-job-{{ status }}"></i>
</div>
<div class="at-RowItem-header" ng-if="headerValue && headerLink">
<a ng-href="{{ headerLink }}">{{ headerValue }}</a>
</div>
@ -41,4 +49,4 @@
{{ tag.name }}
</div>
</div>
</div>
</div>

View File

@ -29,4 +29,8 @@
.at-TabGroup + .at-Panel-body {
margin-top: 20px;
}
}
.at-TabGroup--padBelow {
margin-bottom: 20px;
}

View File

@ -0,0 +1,21 @@
let Base;
function UnifiedJobModel (method, resource, config) {
Base.call(this, 'unified_jobs');
this.Constructor = UnifiedJobModel;
return this.create(method, resource, config);
}
function UnifiedJobModelLoader (BaseModel) {
Base = BaseModel;
return UnifiedJobModel;
}
UnifiedJobModelLoader.$inject = [
'BaseModel'
];
export default UnifiedJobModelLoader;

View File

@ -23,6 +23,7 @@ import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
import WorkflowJob from '~models/WorkflowJob';
import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
import UnifiedJob from '~models/UnifiedJob';
const MODULE_NAME = 'at.lib.models';
@ -49,6 +50,7 @@ angular
.service('OrganizationModel', Organization)
.service('ProjectModel', Project)
.service('ScheduleModel', Schedule)
.service('UnifiedJobModel', UnifiedJob)
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
.service('WorkflowJobModel', WorkflowJob)
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)

View File

@ -262,6 +262,9 @@
@at-margin-right-list-row-item-tag-icon: 8px;
@at-margin-left-list-row-item-tag-container: -10px;
@at-margin-list-row-action-mobile: 10px;
@at-margin-right-list-row-item-status: @at-space-2x;
@at-margin-right-list-row-item-inline: @at-space-4x;
@at-margin-right-list-row-item-inline-label: @at-space-2x;
@at-height-divider: @at-margin-panel;
@at-height-input: 30px;

View File

@ -33,7 +33,6 @@ import login from './login/main';
import activityStream from './activity-stream/main';
import standardOut from './standard-out/main';
import Templates from './templates/main';
import jobs from './jobs/main';
import teams from './teams/main';
import users from './users/main';
import projects from './projects/main';
@ -92,7 +91,6 @@ angular
standardOut.name,
Templates.name,
portalMode.name,
jobs.name,
teams.name,
users.name,
projects.name,

View File

@ -7,7 +7,9 @@
<div ui-view="instances"></div>
<div ui-view="jobs"></div>
<div ui-view="instanceJobsContainer"></div>
<div ui-view="instanceGroupsJobsContainer"></div>
<div ui-view="list"></div>
</div>

View File

@ -23,8 +23,12 @@ function InstanceGroupsStrings (BaseString) {
IS_OFFLINE: t.s('Unavailable to run jobs.'),
IS_OFFLINE_LABEL: t.s('Unavailable')
};
ns.jobs = {
PANEL_TITLE: t.s('Jobs')
};
}
InstanceGroupsStrings.$inject = ['BaseStringService'];
export default InstanceGroupsStrings;
export default InstanceGroupsStrings;

View File

@ -1,89 +0,0 @@
function InstanceJobsController ($scope, $filter, $state, model, strings, jobStrings, Instance) {
const vm = this || {};
let { instance } = model;
const instance_id = instance.get('id');
init();
function init(){
vm.strings = strings;
vm.jobStrings = jobStrings;
vm.queryset = { page_size: '10', order_by: '-finished'};
vm.jobs = instance.get('related.jobs.results');
vm.dataset = instance.get('related.jobs');
vm.count = instance.get('related.jobs.count');
vm.panelTitle = `${jobStrings.get('list.PANEL_TITLE')} | ${instance.get('hostname')}`;
vm.tab = {
details: {_hide: true},
instances: {_hide: true},
jobs: {_hide: true}
};
}
vm.getTime = function(time) {
let val = "";
if (time) {
val += $filter('longDate')(time);
}
if (val === "") {
val = undefined;
}
return val;
};
$scope.isSuccessful = function (status) {
return (status === "successful");
};
$scope.viewjobResults = function(job) {
var goTojobResults = function(state) {
$state.go(state, { id: job.id }, { reload: true });
};
switch (job.type) {
case 'job':
goTojobResults('jobResult');
break;
case 'ad_hoc_command':
goTojobResults('adHocJobStdout');
break;
case 'system_job':
goTojobResults('managementJobStdout');
break;
case 'project_update':
goTojobResults('scmUpdateStdout');
break;
case 'inventory_update':
goTojobResults('inventorySyncStdout');
break;
case 'workflow_job':
goTojobResults('workflowResults');
break;
}
};
$scope.$on('ws-jobs', () => {
new Instance(['get', 'options'], [instance_id, instance_id])
.then((data) => {
return data.extend('get', 'jobs', {params: {page_size: "10", order_by: "-finished"}});
})
.then((data) => {
instance = data;
init();
});
});
}
InstanceJobsController.$inject = [
'$scope',
'$filter',
'$state',
'resolvedModels',
'InstanceGroupsStrings',
'JobStrings',
'InstanceModel'
];
export default InstanceJobsController;

View File

@ -0,0 +1,38 @@
function InstanceGroupJobsContainerController (strings, $state) {
const vm = this || {};
init();
function init() {
const instanceGroupId = $state.params.instance_group_id;
vm.panelTitle = strings.get('jobs.PANEL_TITLE');
vm.strings = strings;
vm.tab = {
details: {
_go: 'instanceGroups.edit',
_params: { instance_group_id: instanceGroupId },
_label: strings.get('tab.DETAILS')
},
instances: {
_go: 'instanceGroups.instances',
_params: { instance_group_id: instanceGroupId },
_label: strings.get('tab.INSTANCES')
},
jobs: {
_active: true,
_params: { instance_group_id: instanceGroupId },
_label: strings.get('tab.JOBS')
}
};
}
}
InstanceGroupJobsContainerController.$inject = [
'InstanceGroupsStrings',
'$state'
];
export default InstanceGroupJobsContainerController;

View File

@ -0,0 +1,11 @@
<at-panel>
<at-panel-heading>
{{ vm.panelTitle }}
</at-panel-heading>
<at-tab-group class="at-TabGroup--padBelow">
<at-tab state="vm.tab.details">{{:: vm.strings.get('tab.DETAILS') }}</at-tab>
<at-tab state="vm.tab.instances">{{:: vm.strings.get('tab.INSTANCES') }}</at-tab>
<at-tab state="vm.tab.jobs">{{:: vm.strings.get('tab.JOBS') }}</at-tab>
</at-tab-group>
<div ui-view="jobsList"></div>
</at-panel>

View File

@ -0,0 +1,17 @@
function InstanceGroupJobsContainerController (strings) {
const vm = this || {};
init();
function init() {
vm.panelTitle = strings.get('jobs.PANEL_TITLE');
vm.strings = strings;
}
}
InstanceGroupJobsContainerController.$inject = [
'InstanceGroupsStrings'
];
export default InstanceGroupJobsContainerController;

View File

@ -0,0 +1,6 @@
<at-panel>
<at-panel-heading>
{{ vm.panelTitle }}
</at-panel-heading>
<div ui-view="jobsList"></div>
</at-panel>

View File

@ -1,86 +0,0 @@
<at-panel>
<at-panel-heading>
{{ vm.panelTitle }}
</at-panel-heading>
<at-tab-group>
<at-tab state="vm.tab.details">{{:: vm.strings.get('tab.DETAILS') }}</at-tab>
<at-tab state="vm.tab.instances">{{:: vm.strings.get('tab.INSTANCES') }}</at-tab>
<at-tab state="vm.tab.jobs">{{:: vm.strings.get('tab.JOBS') }}</at-tab>
</at-tab-group>
<at-panel-body>
<div class="at-List-toolbar">
<smart-search
class="at-List-search"
django-model="jobs"
base-path="unified_jobs"
iterator="job"
list="list"
dataset="job_dataset"
collection="collection"
search-tags="searchTags"
query-set="querySet">
</smart-search>
</div>
<at-list results="vm.jobs">
<at-row ng-repeat="job in vm.jobs"
ng-class="{'at-Row--active': (job.id === vm.activeId)}"
job-id="{{ job.id }}">
<div class="at-RowStatus">
<a ng-click="vm.viewJobResults(job)" aw-tool-tip="{{ job.status_tip }}" data-tip-watch="job.status_tip" aw-tip-placement="right" >
<i class="fa icon-job-{{ job.status }}"></i>
</a>
</div>
<div class="at-Row-items">
<at-row-item
class="at-RowItem--isHeaderLink"
header-value="{{ job.name }}"
header-tag="{{ job.type }}"
ng-click="vm.viewjobResults(job)">
</at-row-item>
<at-row-item
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_STARTED') }}"
value="{{ vm.getTime(job.started) }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_FINISHED') }}"
value="{{ vm.getTime(job.finished) }}">
</at-row-item>
<at-row-item
header-value="{{ job.name }}"
header-link="/#/jobs/workflow_job/{{ job.id }}"
header-tag="{{ vm.jobTypes[job.type] }}"
ng-if="job.type === 'workflow_job_job'">
</at-row-item>
<at-row-item
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_TEMPLATE') }}"
value="{{ job.summary_fields.job_template.name }}"
value-link="/#/templates/job_template/{{ job.summary_fields.job_template.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_INVENTORY') }}"
value="{{ job.summary_fields.inventory.name }}"
value-link="/#/inventories/inventory/{{ job.summary_fields.inventory.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_PROJECT') }}"
value="{{ job.summary_fields.project.name }}"
value-link="/#/projects/{{ job.summary_fields.project.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.jobStrings.get('list.ROW_ITEM_LABEL_CREDENTIALS') }}"
tag-values="job.summary_fields.credentials"
tags-are-creds="true">
</at-row-item>
</div>
</at-row>
</at-list>
<paginate
collection="vm.jobs"
dataset="vm.dataset"
iterator="job"
base-path="unified_jobs"
query-set="vm.queryset">
</paginate>
</at-panel-body>
</at-panel>

View File

@ -1,100 +0,0 @@
function InstanceGroupJobsController ($scope, $filter, $state, model, strings, jobStrings, InstanceGroup) {
const vm = this || {};
let { instanceGroup } = model;
const instance_group_id = instanceGroup.get('id');
init();
function init(){
vm.strings = strings;
vm.jobStrings = jobStrings;
vm.queryset = { page_size: '10', order_by: '-finished', instance_group_id: instance_group_id };
vm.jobs = instanceGroup.get('related.jobs.results');
vm.dataset = instanceGroup.get('related.jobs');
vm.count = instanceGroup.get('related.jobs.count');
vm.panelTitle = instanceGroup.get('name');
vm.tab = {
details: {
_go: 'instanceGroups.edit',
_params: { instance_group_id },
_label: strings.get('tab.DETAILS')
},
instances: {
_go: 'instanceGroups.instances',
_params: { instance_group_id },
_label: strings.get('tab.INSTANCES')
},
jobs: {
_active: true,
_label: strings.get('tab.JOBS')
}
};
}
vm.getTime = function(time) {
let val = "";
if (time) {
val += $filter('longDate')(time);
}
if (val === "") {
val = undefined;
}
return val;
};
$scope.isSuccessful = function (status) {
return (status === "successful");
};
vm.viewjobResults = function(job) {
var goTojobResults = function(state) {
$state.go(state, { id: job.id }, { reload: true });
};
switch (job.type) {
case 'job':
goTojobResults('jobResult');
break;
case 'ad_hoc_command':
goTojobResults('adHocJobStdout');
break;
case 'system_job':
goTojobResults('managementJobStdout');
break;
case 'project_update':
goTojobResults('scmUpdateStdout');
break;
case 'inventory_update':
goTojobResults('inventorySyncStdout');
break;
case 'workflow_job':
goTojobResults('workflowResults');
break;
}
};
$scope.$on('ws-jobs', () => {
new InstanceGroup(['get', 'options'], [instance_group_id, instance_group_id])
.then((instance_group) => {
return instance_group.extend('get', 'jobs', {params: {page_size: "10", order_by: "-finished"}});
})
.then((instance_group) => {
instanceGroup = instance_group;
init();
});
});
}
InstanceGroupJobsController.$inject = [
'$scope',
'$filter',
'$state',
'resolvedModels',
'InstanceGroupsStrings',
'JobStrings',
'InstanceGroupModel'
];
export default InstanceGroupJobsController;

View File

@ -1,76 +0,0 @@
export default ['i18n', function (i18n) {
return {
name: 'jobs',
iterator: 'job',
basePath: 'api/v2/instance_groups/{{$stateParams.instance_group_id}}/jobs/',
index: false,
hover: false,
well: true,
emptyListText: i18n._('No jobs have yet run.'),
listTitle: false,
fields: {
status: {
label: '',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus',
dataTipWatch: 'job.status_tip',
awToolTip: "{{ job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ job.status_popover_title }}",
icon: 'icon-job-{{ job.status }}',
iconOnly: true,
ngClick: "viewjobResults(job)",
nosort: true
},
id: {
label: i18n._('ID'),
ngClick: "viewjobResults(job)",
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ job.status_tip }}",
dataPlacement: 'top',
noLink: true
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6',
ngClick: "viewjobResults(job)",
badgePlacement: 'right',
badgeCustom: true,
nosort: true,
badgeIcon: `<a href="{{ job.workflow_result_link }}"
aw-tool-tip="{{'View workflow results'|translate}}"
data-placement="top"
data-original-title="" title="">
<i class="WorkflowBadge"
ng-show="job.launch_type === 'workflow' ">
W
</i>
</a>`
},
type: {
label: i18n._('Type'),
ngBind: 'job.type_label',
columnClass: "col-lg-2 hidden-md hidden-sm hidden-xs",
nosort: true
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
columnClass: "col-lg-2 col-md-3 col-sm-3 hidden-xs",
key: true,
desc: true,
nosort: true
},
labels: {
label: i18n._('Labels'),
type: 'labels',
nosort: true,
showDelete: false,
columnClass: 'List-tableCell col-lg-4 col-md-4 hidden-sm hidden-xs',
sourceModel: 'labels',
sourceField: 'name'
},
}
};
}];

View File

@ -1,30 +0,0 @@
function JobStrings (BaseString) {
BaseString.call(this, 'jobs');
const { t } = this;
const ns = this.jobs;
ns.state = {
LIST_BREADCRUMB_LABEL: t.s('JOBS')
};
ns.list = {
PANEL_TITLE: t.s('JOBS'),
ADD_BUTTON_LABEL: t.s('ADD'),
ADD_DD_JT_LABEL: t.s('Job Template'),
ADD_DD_WF_LABEL: t.s('Workflow Template'),
ROW_ITEM_LABEL_ACTIVITY: t.s('Activity'),
ROW_ITEM_LABEL_INVENTORY: t.s('Inventory'),
ROW_ITEM_LABEL_PROJECT: t.s('Project'),
ROW_ITEM_LABEL_TEMPLATE: t.s('Template'),
ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'),
ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'),
ROW_ITEM_LABEL_RAN: t.s('Last Ran'),
ROW_ITEM_LABEL_STARTED: t.s('Started'),
ROW_ITEM_LABEL_FINISHED: t.s('Finished')
};
}
JobStrings.$inject = ['BaseStringService'];
export default JobStrings;

View File

@ -15,10 +15,6 @@ import InstanceGroupsListController from './list/instance-groups-list.controller
import InstancesTemplate from './instances/instances-list.partial.html';
import InstanceListController from './instances/instances.controller';
import JobsTemplate from './jobs/jobs-list.partial.html';
import InstanceGroupJobsListController from './jobs/jobs.controller';
import InstanceJobsListController from './instances/instance-jobs/instance-jobs.controller';
import InstanceModalTemplate from './instances/instance-modal.partial.html';
import InstanceModalController from './instances/instance-modal.controller.js';
@ -26,7 +22,9 @@ import list from './instance-groups.list';
import service from './instance-groups.service';
import InstanceGroupsStrings from './instance-groups.strings';
import JobStrings from './jobs/jobs.strings';
import instanceGroupJobsRoute from '~features/jobs/routes/instanceGroupJobs.route.js';
import instanceJobsRoute from '~features/jobs/routes/instanceJobs.route.js';
const MODULE_NAME = 'instanceGroups';
@ -255,73 +253,8 @@ function InstanceGroupsRun ($stateExtender, strings, ComponentsStrings) {
resolvedModels: InstanceGroupsResolve
});
$stateExtender.addState({
name: 'instanceGroups.instanceJobs',
url: '/:instance_group_id/instances/:instance_id/jobs',
ncyBreadcrumb: {
parent: 'instanceGroups.instances',
label: ComponentsStrings.get('layout.JOBS')
},
views: {
'instanceJobs@instanceGroups': {
templateUrl: JobsTemplate,
controller: 'InstanceJobsListController',
controllerAs: 'vm'
},
},
params: {
job_search: {
value: {
page_size: '10',
order_by: '-finished'
},
dynamic: true
},
},
data: {
socket: {
"groups": {
"jobs": ["status_changed"],
}
}
},
resolvedModels: InstanceGroupsResolve
});
$stateExtender.addState({
name: 'instanceGroups.jobs',
url: '/:instance_group_id/jobs',
ncyBreadcrumb: {
parent: 'instanceGroups.edit',
label: ComponentsStrings.get('layout.JOBS')
},
params: {
job_search: {
value: {
page_size: '10',
order_by: '-finished'
},
dynamic: true
}
},
data: {
socket: {
"groups": {
"jobs": ["status_changed"],
}
}
},
views: {
'jobs@instanceGroups': {
templateUrl: JobsTemplate,
controller: 'InstanceGroupJobsListController',
controllerAs: 'vm'
},
},
resolve: {
resolvedModels: InstanceGroupsResolve
}
});
$stateExtender.addState(instanceJobsRoute);
$stateExtender.addState(instanceGroupJobsRoute);
}
InstanceGroupsRun.$inject = [
@ -334,16 +267,13 @@ angular.module(MODULE_NAME, [])
.service('InstanceGroupsService', service)
.factory('InstanceGroupList', list)
.controller('InstanceGroupsListController', InstanceGroupsListController)
.controller('InstanceGroupJobsListController', InstanceGroupJobsListController)
.controller('InstanceListController', InstanceListController)
.controller('InstanceJobsListController', InstanceJobsListController)
.directive('instanceListPolicy', InstanceListPolicy)
.directive('instanceGroupsMultiselect', instanceGroupsMultiselect)
.directive('instanceGroupsModal', instanceGroupsModal)
.directive('capacityAdjuster', CapacityAdjuster)
.directive('capacityBar', CapacityBar)
.service('InstanceGroupsStrings', InstanceGroupsStrings)
.service('JobStrings', JobStrings)
.run(InstanceGroupsRun);
export default MODULE_NAME;

View File

@ -8,12 +8,11 @@ import adhoc from './adhoc/main';
import group from './related/groups/main';
import sources from './related/sources/main';
import relatedHost from './related/hosts/main';
import inventoryCompletedJobs from './related/completed-jobs/main';
import inventoryList from './list/main';
import InventoryList from './inventory.list';
import adHocRoute from './adhoc/adhoc.route';
import insights from './insights/main';
import completedJobsRoute from './related/completed-jobs/completed-jobs.route';
import completedJobsRoute from '~features/jobs/routes/inventoryCompletedJobs.route.js';
import inventorySourceEditRoute from './related/sources/edit/sources-edit.route';
import inventorySourceEditNotificationsRoute from './related/sources/edit/sources-notifications.route';
import inventorySourceAddRoute from './related/sources/add/sources-add.route';
@ -53,7 +52,6 @@ angular.module('inventory', [
group.name,
sources.name,
relatedHost.name,
inventoryCompletedJobs.name,
inventoryList.name,
insights.name,
SmartInventory.name,

View File

@ -1,86 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
return {
// These tooltip fields are consumed to build disabled related tabs tooltips in the form > add view
awToolTip: i18n._('Please save and run a job to view.'),
dataPlacement: 'top',
name: 'completed_jobs',
basePath: 'unified_jobs',
iterator: 'completed_job',
search: {
"or__job__inventory": ''
},
editTitle: i18n._('COMPLETED JOBS'),
index: false,
hover: true,
well: true,
emptyListText: i18n._('No completed jobs'),
fields: {
status: {
label: '',
columnClass: 'List-staticColumn--smallStatus',
awToolTip: "{{ completed_job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ completed_job.status_popover_title }}",
icon: 'icon-job-{{ completed_job.status }}',
iconOnly: true,
uiSref: '{{completed_job.linkToDetails}}',
nosort: true
},
id: {
label: i18n._('ID'),
uiSref: '{{completed_job.linkToDetails}}',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ completed_job.status_tip }}",
dataPlacement: 'top'
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
uiSref: '{{completed_job.linkToDetails}}',
awToolTip: "{{ completed_job.name | sanitize }}",
dataPlacement: 'top'
},
type: {
label: i18n._('Type'),
ngBind: 'completed_job.type_label',
link: false,
columnClass: "col-lg-2 col-md-2 hidden-sm hidden-xs",
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
columnClass: "col-lg-3 col-md-3 col-sm-3 hidden-xs",
key: true,
desc: true
}
},
actions: { },
fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
submit: {
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start",
// uses the at-relaunch directive
relaunch: true
},
"delete": {
mode: 'all',
ngClick: 'deleteJob(completed_job.id)',
awToolTip: i18n._('Delete the job'),
dataPlacement: 'top',
ngShow: 'completed_job.summary_fields.user_capabilities.delete'
}
}
};}];

View File

@ -1,60 +0,0 @@
import { N_ } from '../../../../i18n';
export default {
url: "/completed_jobs",
params: {
completed_job_search: {
value: {
page_size: '20',
or__job__inventory:"",
or__adhoccommand__inventory:"",
or__inventoryupdate__inventory_source__inventory:"",
order_by: "-id"
},
dynamic: true,
squash:""
}
},
ncyBreadcrumb: {
label: N_("COMPLETED JOBS")
},
views: {
'related': {
templateProvider: function(FormDefinition, GenerateForm) {
let html = GenerateForm.buildCollection({
mode: 'edit',
related: 'completed_jobs',
form: typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition
});
return html;
},
controller: 'JobsList'
}
},
resolve: {
ListDefinition: ['InventoryCompletedJobsList', (InventoryCompletedJobsList) => {
return InventoryCompletedJobsList;
}],
Dataset: ['InventoryCompletedJobsList', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope',
(list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => {
// allow related list definitions to use interpolated $rootScope / $stateParams in basePath field
let path, interpolator;
if (GetBasePath(list.basePath)) {
path = GetBasePath(list.basePath);
} else {
interpolator = $interpolate(list.basePath);
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
}
let inventory_id = $stateParams.inventory_id ? $stateParams.inventory_id : $stateParams.smartinventory_id;
$stateParams[`${list.iterator}_search`].or__job__inventory = inventory_id;
$stateParams[`${list.iterator}_search`].or__adhoccommand__inventory = inventory_id;
$stateParams[`${list.iterator}_search`].or__inventoryupdate__inventory_source__inventory = inventory_id;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
}
};

View File

@ -1,11 +0,0 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import list from './completed-jobs.list';
export default
angular.module('inventoryCompletedJobs', [])
.factory('InventoryCompletedJobsList', list);

View File

@ -4,25 +4,7 @@
* All Rights Reserved
*************************************************/
export default ['i18n', 'InventoryCompletedJobsList', function(i18n, InventoryCompletedJobsList) {
var completed_jobs_object = {
name: 'completed_jobs',
index: false,
basePath: "unified_jobs",
title: i18n._('Completed Jobs'),
iterator: 'completed_job',
generateList: true,
skipGenerator: true,
search: {
"or__job__inventory": ''
},
ngClick: "$state.go('inventories.editSmartInventory.completed_jobs')"
};
let clone = _.clone(InventoryCompletedJobsList);
completed_jobs_object = angular.extend(clone, completed_jobs_object);
export default ['i18n', function(i18n) {
return {
addTitle: i18n._('NEW SMART INVENTORY'),
@ -169,7 +151,11 @@ export default ['i18n', 'InventoryCompletedJobsList', function(i18n, InventoryCo
ngClick: "$state.go('inventories.editSmartInventory.hosts');",
skipGenerator: true
},
completed_jobs: completed_jobs_object
completed_jobs: {
title: i18n._('Completed Jobs'),
skipGenerator: true,
ngClick: "$state.go('inventories.editSmartInventory.completed_jobs')"
}
}
};

View File

@ -10,25 +10,8 @@
* @description This form is for adding/editing an inventory
*/
export default ['i18n', 'InventoryCompletedJobsList',
function(i18n, InventoryCompletedJobsList) {
var completed_jobs_object = {
name: 'completed_jobs',
index: false,
basePath: "unified_jobs",
include: "InventoryCompletedJobsList",
title: i18n._('Completed Jobs'),
iterator: 'completed_job',
generateList: true,
skipGenerator: true,
search: {
"or__job__inventory": ''
}
};
let clone = _.clone(InventoryCompletedJobsList);
completed_jobs_object = angular.extend(clone, completed_jobs_object);
export default ['i18n',
function(i18n) {
return {
addTitle: i18n._('NEW INVENTORY'),
@ -185,7 +168,10 @@ function(i18n, InventoryCompletedJobsList) {
iterator: 'inventory_source',
skipGenerator: true
},
completed_jobs: completed_jobs_object
completed_jobs: {
title: i18n._('Completed Jobs'),
skipGenerator: true
}
},
relatedButtons: {
remediate_inventory: {

View File

@ -1,115 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
return {
name: 'jobs',
basePath: 'unified_jobs',
iterator: 'job',
editTitle: i18n._('ALL JOBS'),
index: false,
hover: true,
well: false,
emptyListText: i18n._('No jobs have yet run.'),
title: false,
fields: {
status: {
label: '',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus',
dataTipWatch: 'job.status_tip',
awToolTip: "{{ job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ job.status_popover_title }}",
icon: 'icon-job-{{ job.status }}',
iconOnly: true,
uiSref: '{{job.linkToDetails}}',
nosort: true
},
id: {
label: 'ID',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ job.status_tip }}",
dataPlacement: 'top',
noLink: true
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6',
uiSref: '{{job.linkToDetails}}',
badgePlacement: 'right',
badgeCustom: true,
badgeIcon: `<a href="{{ job.workflow_result_link }}"
aw-tool-tip="{{'View workflow results'|translate}}"
data-placement="top"
data-original-title="" title="">
<i class="WorkflowBadge"
ng-show="job.launch_type === 'workflow' ">
W
</i>
</a>`
},
type: {
label: i18n._('Type'),
ngBind: 'job.type_label',
link: false,
columnClass: "col-lg-2 hidden-md hidden-sm hidden-xs",
columnShow: "showJobType",
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
columnClass: "col-lg-2 col-md-3 col-sm-3 hidden-xs",
key: true,
desc: true
},
labels: {
label: i18n._('Labels'),
type: 'labels',
nosort: true,
showDelete: false,
columnClass: 'List-tableCell col-lg-4 col-md-4 hidden-sm hidden-xs',
sourceModel: 'labels',
sourceField: 'name'
},
},
actions: { },
fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
"view": {
mode: "all",
ngClick: "viewjobResults(job)",
awToolTip: i18n._("View the job"),
dataPlacement: "top"
},
submit: {
ngShow: "!(job.type == 'system_job') && job.summary_fields.user_capabilities.start",
// uses the at-relaunch directive
relaunch: true,
},
cancel: {
mode: 'all',
ngClick: 'deleteJob(job.id)',
awToolTip: i18n._('Cancel the job'),
dataPlacement: 'top',
ngShow: "(job.status === 'running'|| job.status === 'waiting' || job.status === 'pending') && job.summary_fields.user_capabilities.start"
},
"delete": {
mode: 'all',
ngClick: 'deleteJob(job.id)',
awToolTip: i18n._('Delete the job'),
dataPlacement: 'top',
ngShow: "(job.status !== 'running' && job.status !== 'waiting' && job.status !== 'pending') && job.summary_fields.user_capabilities.delete"
}
}
};
}];

View File

@ -1,145 +0,0 @@
export default
function DeleteJob($state, Find, Rest, Wait, ProcessErrors, Prompt, Alert,
$filter, i18n) {
return function(params) {
var scope = params.scope,
id = params.id,
job = params.job,
callback = params.callback,
action, jobs, url, action_label, hdr;
if (!job) {
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.status === 'pending' || job.status === 'running' || job.status === 'waiting') {
url = job.related.cancel;
action_label = 'cancel';
hdr = i18n._('Cancel');
} else {
url = job.url;
action_label = 'delete';
hdr = i18n._('Delete');
}
action = function () {
Wait('start');
Rest.setUrl(url);
if (action_label === 'cancel') {
Rest.post()
.then(() => {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
$state.reload();
Wait('stop');
}
})
.catch(({obj, status}) => {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
// ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. POST returned status: ' + status });
});
} else {
Rest.destroy()
.then(() => {
$('#prompt-modal').modal('hide');
if (callback) {
scope.$emit(callback, action_label);
}
else {
let reloadListStateParams = null;
if(scope.jobs.length === 1 && $state.params.job_search && !_.isEmpty($state.params.job_search.page) && $state.params.job_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.job_search.page = (parseInt(reloadListStateParams.job_search.page)-1).toString();
}
$state.go('.', reloadListStateParams, {reload: true});
Wait('stop');
}
})
.catch(({obj, status}) => {
Wait('stop');
$('#prompt-modal').modal('hide');
if (status === 403) {
Alert('Error', obj.detail);
}
// Ignore the error. The job most likely already finished.
//ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
// ' failed. DELETE returned status: ' + status });
});
}
};
if (scope.removeCancelNotAllowed) {
scope.removeCancelNotAllowed();
}
scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() {
Wait('stop');
Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info');
});
if (scope.removeCancelJob) {
scope.removeCancelJob();
}
scope.removeCancelJob = scope.$on('CancelJob', function() {
var cancelBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to submit the request to cancel this job?") + "</div>";
var deleteBody = "<div class=\"Prompt-bodyQuery\">" + i18n._("Are you sure you want to delete this job?") + "</div>";
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")
});
});
if (action_label === 'cancel') {
Rest.setUrl(url);
Rest.get()
.then(({data}) => {
if (data.can_cancel) {
scope.$emit('CancelJob');
}
else {
scope.$emit('CancelNotAllowed');
}
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url +
' failed. GET returned: ' + status });
});
}
else {
scope.$emit('CancelJob');
}
};
}
DeleteJob.$inject =
[ '$state', 'Find', 'Rest', 'Wait',
'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n'
];

View File

@ -1,143 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Jobs
* @description This controller's for the jobs page
*/
export default ['$state', '$rootScope', '$scope', '$stateParams', 'Find', 'DeleteJob',
'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate',
function($state, $rootScope, $scope, $stateParams, Find, DeleteJob,
GetBasePath, Dataset, qs, ListDefinition, $interpolate) {
var list = ListDefinition;
init();
function init() {
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.showJobType = true;
}
$scope.$on(`${list.iterator}_options`, function(event, data){
$scope.options = data.data.actions.GET;
optionsRequestDataProcessing();
});
$scope.$watchCollection(`${$scope.list.name}`, function() {
optionsRequestDataProcessing();
}
);
// iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
if($scope[list.name] && $scope[list.name].length > 0) {
$scope[list.name].forEach(function(item, item_idx) {
var itm = $scope[list.name][item_idx];
switch (item.type) {
case 'job':
item.linkToDetails = `jobResult({id: ${item.id}})`;
break;
case 'ad_hoc_command':
item.linkToDetails = `adHocJobStdout({id: ${item.id}})`;
break;
case 'system_job':
item.linkToDetails = `managementJobStdout({id: ${item.id}})`;
break;
case 'project_update':
item.linkToDetails = `scmUpdateStdout({id: ${item.id}})`;
break;
case 'inventory_update':
item.linkToDetails = `inventorySyncStdout({id: ${item.id}})`;
break;
case 'workflow_job':
item.linkToDetails = `workflowResults({id: ${item.id}})`;
break;
}
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.forEach(function(choice) {
if (choice[0] === item.type) {
itm.type_label = choice[1];
}
});
}
buildTooltips(itm);
});
}
}
function buildTooltips(job) {
job.status_tip = 'Job ' + job.status + ". Click for details.";
}
$scope.deleteJob = function(id) {
DeleteJob({ scope: $scope, id: id });
};
$scope.viewjobResults = function(job) {
var goTojobResults = function(state) {
$state.go(state, { id: job.id }, { reload: true });
};
switch (job.type) {
case 'job':
goTojobResults('jobResult');
break;
case 'ad_hoc_command':
goTojobResults('adHocJobStdout');
break;
case 'system_job':
goTojobResults('managementJobStdout');
break;
case 'project_update':
goTojobResults('scmUpdateStdout');
break;
case 'inventory_update':
goTojobResults('inventorySyncStdout');
break;
case 'workflow_job':
goTojobResults('workflowResults');
break;
}
};
$scope.$on('ws-jobs', function(){
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;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
});
});
$scope.$on('ws-schedules', function(){
$state.reload();
});
}];

View File

@ -1,23 +0,0 @@
<div class="tab-pane" id="jobs-page">
<div ng-cloak id="htmlTemplate" class="Panel">
<div>
<div id="jobs_tabs" class="Form-tabHolder">
<div class="Form-tabs">
<div class="Form-tab" id="active_jobs_link" ng-class="{'is-selected': $state.is('jobs')}" ng-click="$state.go('jobs')">
<translate>Jobs</translate>
</div>
<div id="scheduled_jobs_link" class="Form-tab" ng-class="{'is-selected': $state.is('jobs.schedules')}" ng-click="$state.go('.schedules')">
<translate>Schedules</translate>
</div>
</div>
<div class="Form-tabActions">
<button id="refresh_btn" ng-show="socketStatus === 'error'" aw-tool-tip="Refresh the page" data-placement="top" class="btn List-buttonDefault" ng-click="refreshJobs()" toolbar="true">
<span><translate>REFRESH</translate></span>
</button>
</div>
</div>
<div ui-view="list"></div>
</div>
</div>
<div ng-include="'/static/partials/logviewer.html'"></div>
</div>

View File

@ -1,59 +0,0 @@
/*************************************************
* 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'
},
dynamic: true,
squash: false
}
},
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'
}
}
};

View File

@ -1,19 +0,0 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import jobsList from './jobs-list.controller';
import jobsRoute from './jobs.route';
import DeleteJob from './factories/delete-job.factory';
import AllJobsList from './all-jobs.list';
export default
angular.module('JobsModule', [])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(jobsRoute);
}])
.controller('JobsList', jobsList)
.factory('DeleteJob', DeleteJob)
.factory('AllJobsList', AllJobsList);

View File

@ -6,7 +6,6 @@
import managementJobsCard from './card/main';
import managementJobsScheduler from './scheduler/main';
import list from './management-jobs.list';
import managementJobsNotifications from './notifications/main';
export default
@ -14,5 +13,4 @@ export default
managementJobsCard.name,
managementJobsScheduler.name,
managementJobsNotifications.name
])
.factory('managementJobsListObject', list);
]);

View File

@ -1,43 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default function(){
return {
name: 'configure_jobs',
iterator: 'configure_job',
index: false,
hover: true,
listTitle: 'MANAGEMENT JOBS',
fields: {
name: {
label: 'Name',
columnClass: 'col-sm-4 col-xs-4',
awToolTip: '{{configure_job.description | sanitize}}',
dataPlacement: 'top'
}
},
actions: {
},
fieldActions: {
submit: {
label: 'Launch',
mode: 'all',
ngClick: 'submitJob(configure_job.id, configure_job.name)',
awToolTip: 'Start a job using this template',
dataPlacement: 'top'
},
schedule: {
label: 'Schedule',
mode: 'all',
ngClick: 'configureSchedule()',
awToolTip: 'Schedule job template runs',
dataPlacement: 'top',
}
}
};
}

View File

@ -1,49 +0,0 @@
import { PortalModeJobsController } from '../portal-mode-jobs.controller';
// Using multiple named views requires a parent layout
// https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
export default {
name: 'portalMode.allJobs',
url: '/alljobs?{job_search:queryset}',
ncyBreadcrumb: {
skip: true
},
params: {
job_search: {
value: {
page_size: '20',
order_by: '-finished'
},
dynamic: true
}
},
data: {
socket: {
"groups": {
"jobs": ["status_changed"]
}
}
},
views: {
'jobs@portalMode': {
templateProvider: function(PortalJobsList, generateList) {
let html = generateList.build({
list: PortalJobsList,
mode: 'edit'
});
return html;
},
controller: PortalModeJobsController
}
},
resolve: {
jobsDataset: ['PortalJobsList', 'QuerySet', '$rootScope', '$stateParams', 'GetBasePath',
function(list, qs, $rootScope, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return $rootScope.loginConfig.promise.then(() => {
return qs.search(path, $stateParams[`${list.iterator}_search`]);
});
}
]
}
};

View File

@ -1,51 +0,0 @@
import { PortalModeJobsController } from '../portal-mode-jobs.controller';
// Using multiple named views requires a parent layout
// https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
export default {
name: 'portalMode.myJobs',
url: '/myjobs?{job_search:queryset}',
ncyBreadcrumb: {
skip: true
},
params: {
job_search: {
value: {
page_size: '20',
order_by: '-finished',
created_by: null
},
dynamic: true
}
},
data: {
socket: {
"groups": {
"jobs": ["status_changed"]
}
}
},
views: {
'jobs@portalMode': {
templateProvider: function(PortalJobsList, generateList) {
let html = generateList.build({
list: PortalJobsList,
mode: 'edit'
});
return html;
},
controller: PortalModeJobsController
}
},
resolve: {
jobsDataset: ['PortalJobsList', 'QuerySet', '$rootScope', '$stateParams', 'GetBasePath',
function(list, qs, $rootScope, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return $rootScope.loginConfig.promise.then(() => {
$stateParams[`${list.iterator}_search`].created_by = $rootScope.current_user.id;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
});
}
]
}
};

View File

@ -5,17 +5,15 @@
*************************************************/
import route from './portal-mode.route';
import myJobsRoute from './jobs/portal-mode-my-jobs.route';
import allJobsRoute from './jobs/portal-mode-all-jobs.route';
import PortalJobsList from './portal-jobs.list';
import myJobsRoute from '~features/jobs/routes/portalModeMyJobs.route.js';
import allJobsRoute from '~features/jobs/routes/portalModeAllJobs.route.js';
import PortalJobTemplateList from './portal-job-templates.list';
export default
angular.module('portalMode', [])
.factory('PortalJobsList', PortalJobsList)
.factory('PortalJobTemplateList', PortalJobTemplateList)
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(route);
$stateExtender.addState(myJobsRoute);
$stateExtender.addState(allJobsRoute);
$stateExtender.addState(route);
$stateExtender.addState(myJobsRoute);
$stateExtender.addState(allJobsRoute);
}]);

View File

@ -1,50 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
return {
name: 'jobs',
iterator: 'job',
editTitle: i18n._('JOBS'),
index: false,
hover: true,
well: true,
listTitle: i18n._('JOBS'),
emptyListText: i18n._('There are no jobs to display at this time'),
searchBarFullWidth: true,
fields: {
status: {
label: '',
columnClass: 'List-staticColumn--smallStatus',
dataTitle: "{{ job.status_popover_title }}",
icon: 'icon-job-{{ job.status }}',
iconOnly: true,
nosort: true,
awTipPlacement: "top",
awToolTip: "{{ job.status_tip }}",
dataTipWatch: 'job.status_tip',
ngClick:"viewjobResults(job)",
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6 List-staticColumnAdjacent',
linkTo: '/#/jobs/{{job.id}}',
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
key: true,
desc: true,
columnClass: "col-lg-4 col-md-4 col-sm-3"
}
},
actions: { }
};}];

View File

@ -1,105 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export function PortalModeJobsController($scope, $state, qs, GetBasePath, PortalJobsList, Dataset) {
var list = PortalJobsList;
$scope.$on('ws-jobs', function() {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
qs.search(path, $state.params[`${list.iterator}_search`])
.then(function(searchResponse) {
$scope[`${list.iterator}_dataset`] = searchResponse.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
});
});
init();
function init(data) {
let d = (!data) ? Dataset : data;
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = d.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.iterator = list.iterator;
}
$scope.refresh = function() {
$state.go('.', null, {reload: true});
};
$scope.$on(`${list.iterator}_options`, function(event, data){
$scope.options = data.data.actions.GET;
optionsRequestDataProcessing();
});
$scope.$watchCollection(`${$scope.list.name}`, function() {
optionsRequestDataProcessing();
}
);
// iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
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.forEach(function(choice) {
if (choice[0] === item.type) {
itm.type_label = choice[1];
}
});
}
buildTooltips(itm);
});
}
}
function buildTooltips(job) {
job.status_tip = `Job ${job.status}. Click for details.`;
}
$scope.viewjobResults = function(job) {
var goTojobResults = function(state) {
$state.go(state, { id: job.id }, { reload: true });
};
switch (job.type) {
case 'job':
goTojobResults('jobResult');
break;
case 'ad_hoc_command':
goTojobResults('adHocJobStdout');
break;
case 'system_job':
goTojobResults('managementJobStdout');
break;
case 'project_update':
goTojobResults('scmUpdateStdout');
break;
case 'inventory_update':
goTojobResults('inventorySyncStdout');
break;
case 'workflow_job':
goTojobResults('workflowResults');
break;
}
};
}
PortalModeJobsController.$inject = ['$scope', '$state', 'QuerySet', 'GetBasePath', 'PortalJobsList', 'jobsDataset'];

View File

@ -1,26 +0,0 @@
<div id="portal-container-jobs" class="Panel">
<div class="FormToggle-container">
<div class="btn-group">
<button ng-class="{'btn-primary': activeFilter === 'user', 'Button-primary--hollow': activeFilter !== 'user'}"
ng-click='filterUser()'
class="btn btn-xs"
translate>
My Jobs
</button>
<button ng-class="{'btn-primary': activeFilter === 'all', 'Button-primary--hollow': activeFilter !== 'all'}"
ng-click='filterAll()'
class="btn btn-xs"
translate>
All Jobs
</button>
</div>
<div class="PortalMode-refresh">
<button id="refresh_btn" aw-tool-tip="Refresh the page" data-placement="top" class="btn List-buttonDefault"
ng-click="refresh()" ng-show="socketStatus == 'error'">
<span translate>REFRESH</span>
</button>
</div>
</div>
<div id="portal-jobs">
</div>
</div>

View File

@ -349,7 +349,7 @@ export default
}]
},
views: {
'list@jobs': {
'schedulesList@jobs': {
templateProvider: function(ScheduleList, generateList){
let html = generateList.build({
list: ScheduleList,

View File

@ -1,87 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
return {
// These tooltip fields are consumed to build disabled related tabs tooltips in the form > add view
awToolTip: i18n._('Please save and run a job to view.'),
dataPlacement: 'top',
name: 'completed_jobs',
basePath: 'api/v2/job_templates/{{$stateParams.job_template_id}}/jobs',
search: {
or__status__in: "successful,failed,error,canceled",
order_by: "-id"
},
iterator: 'completed_job',
editTitle: i18n._('COMPLETED JOBS'),
index: false,
hover: true,
well: false,
emptyListText: i18n._('No completed jobs'),
fields: {
status: {
label: '',
columnClass: 'List-staticColumn--smallStatus',
awToolTip: "{{ completed_job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ completed_job.status_popover_title }}",
icon: 'icon-job-{{ completed_job.status }}',
iconOnly: true,
uiSref: '{{completed_job.linkToDetails}}',
nosort: true
},
id: {
label: 'ID',
uiSref: '{{completed_job.linkToDetails}}',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ completed_job.status_tip }}",
dataPlacement: 'top'
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
uiSref: '{{completed_job.linkToDetails}}',
awToolTip: "{{ completed_job.name | sanitize }}",
dataPlacement: 'top'
},
type: {
label: i18n._('Type'),
ngBind: 'completed_job.type_label',
link: false,
columnClass: "col-lg-2 col-md-2 hidden-sm hidden-xs",
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
columnClass: "col-lg-3 col-md-3 col-sm-3 hidden-xs",
key: true,
desc: true
}
},
actions: { },
fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
submit: {
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start",
// uses the at-relaunch directive
relaunch: true
},
"delete": {
mode: 'all',
ngClick: 'deleteJob(completed_job.id)',
awToolTip: i18n._('Delete the job'),
dataPlacement: 'top',
ngShow: 'completed_job.summary_fields.user_capabilities.delete'
}
}
};}];

View File

@ -11,8 +11,8 @@
*/
export default ['NotificationsList', 'CompletedJobsList', 'i18n',
function(NotificationsList, CompletedJobsList, i18n) {
export default ['NotificationsList', 'i18n',
function(NotificationsList, i18n) {
return function() {
var JobTemplateFormObject = {
@ -433,7 +433,9 @@ function(NotificationsList, CompletedJobsList, i18n) {
include: "NotificationsList"
},
"completed_jobs": {
include: "CompletedJobsList"
title: i18n._('Completed Jobs'),
skipGenerator: true,
ngClick: "$state.go('templates.editJobTemplate.completed_jobs')"
}
},
@ -473,11 +475,6 @@ function(NotificationsList, CompletedJobsList, i18n) {
JobTemplateFormObject.related[itm].ngClick = "$state.go('templates.editJobTemplate.notifications')";
JobTemplateFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list
}
if (JobTemplateFormObject.related[itm].include === "CompletedJobsList") {
JobTemplateFormObject.related[itm] = CompletedJobsList;
JobTemplateFormObject.related[itm].ngClick = "$state.go('templates.editJobTemplate.completed_jobs')";
JobTemplateFormObject.related[itm].generateList = true;
}
}
return JobTemplateFormObject;

View File

@ -95,6 +95,11 @@ export default
if (scope.$parent.$parent.template) {
scope.labels = scope.$parent.$parent.template.summary_fields.labels.results.slice(0, 5);
scope.count = scope.$parent.$parent.template.summary_fields.labels.count;
} else if (scope.$parent.$parent.job) {
if (_.has(scope, '$parent.$parent.job.summary_fields.labels.results')) {
scope.labels = scope.$parent.$parent.job.summary_fields.labels.results.slice(0, 5);
scope.count = scope.$parent.$parent.job.summary_fields.labels.count;
}
} else {
scope.$watchCollection(scope.$parent.list.iterator, function() {
// To keep the array of labels fresh, we need to set up a watcher - otherwise, the

View File

@ -16,11 +16,11 @@ import workflowMaker from './workflows/workflow-maker/main';
import workflowControls from './workflows/workflow-controls/main';
import workflowService from './workflows/workflow.service';
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';
import listRoute from '~features/templates/list.route.js';
import templateCompletedJobsRoute from '~features/jobs/routes/templateCompletedJobs.route.js';
export default
angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, prompt.name, workflowAdd.name, workflowEdit.name,
@ -29,7 +29,6 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
.service('TemplatesService', templatesService)
.service('WorkflowService', workflowService)
.factory('WorkflowForm', WorkflowForm)
.factory('CompletedJobsList', CompletedJobsList)
// TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc.
.factory('TemplateList', TemplateList)
.value('InventorySourcesList', InventorySourcesList)
@ -151,10 +150,7 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
modes: ['edit'],
form: 'JobTemplateForm',
controllers: {
edit: 'JobTemplateEdit',
related: {
completed_jobs: 'JobsList'
}
edit: 'JobTemplateEdit'
},
data: {
activityStream: true,
@ -742,6 +738,7 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
return result.concat(definition.states);
}, [
stateExtender.buildDefinition(listRoute),
stateExtender.buildDefinition(templateCompletedJobsRoute),
stateExtender.buildDefinition(workflowMaker)
])
};

1
tower-license Submodule

@ -0,0 +1 @@
Subproject commit 009cc73a80e5123143e9c780c7011903d4c2a331