From 71904c3b5ba42463268f850304d53a0e6881ee03 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Mon, 24 Mar 2014 15:42:32 -0400 Subject: [PATCH 1/4] Latest Jobs and scheduling changes. --- awx/ui/static/js/app.js | 6 +- awx/ui/static/js/controllers/JobHosts.js | 17 +- awx/ui/static/js/controllers/Jobs.js | 42 +- awx/ui/static/js/helpers/Jobs.js | 142 +++-- awx/ui/static/js/helpers/refresh.js | 2 +- awx/ui/static/js/helpers/search.js | 67 +- awx/ui/static/js/lists/CompletedJobs.js | 52 +- awx/ui/static/js/lists/Jobs.js | 12 +- awx/ui/static/js/lists/Projects.js | 13 +- awx/ui/static/js/lists/QueuedJobs.js | 26 +- awx/ui/static/js/lists/RunningJobs.js | 51 +- awx/ui/static/js/lists/ScheduledJobs.js | 78 +++ awx/ui/static/less/ansible-ui.less | 46 +- awx/ui/static/lib/ansible/directives.js | 11 +- .../static/lib/ansible/generator-helpers.js | 23 +- awx/ui/static/lib/ansible/list-generator.js | 9 +- awx/ui/static/partials/jobs.html | 6 +- .../sample/data/jobs/completed/data.json | 576 +++++++++++++++++- awx/ui/templates/ui/index.html | 2 + 19 files changed, 985 insertions(+), 196 deletions(-) create mode 100644 awx/ui/static/js/lists/ScheduledJobs.js diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index d2f63cbae7..4c70cdca4b 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -89,10 +89,12 @@ angular.module('ansible', [ 'ActivityDetailDefinition', 'VariablesHelper', 'SchedulesListDefinition', + 'ScheduledJobsDefinition', 'AngularScheduler', 'Timezones', 'SchedulesHelper', - 'QueuedJobsDefinition' + 'QueuedJobsDefinition', + 'JobsListDefinition' ]) .constant('AngularScheduler.partials', $basePath + 'lib/angular-scheduler/lib/') @@ -105,7 +107,7 @@ angular.module('ansible', [ $routeProvider. when('/jobs', { templateUrl: urlPrefix + 'partials/jobs.html', - controller: 'JobsList' + controller: 'JobsListController' }). when('/jobs/:id', { diff --git a/awx/ui/static/js/controllers/JobHosts.js b/awx/ui/static/js/controllers/JobHosts.js index 45d4338568..ef595cbef4 100644 --- a/awx/ui/static/js/controllers/JobHosts.js +++ b/awx/ui/static/js/controllers/JobHosts.js @@ -56,15 +56,20 @@ function JobHostSummaryList($scope, $rootScope, $location, $log, $routeParams, R } $scope.removePostRefresh = $scope.$on('PostRefresh', function () { - // Set status, tooltips, badget icons, etc. - for (var i = 0; i < $scope.jobhosts.length; i++) { - $scope.jobhosts[i].host_name = $scope.jobhosts[i].summary_fields.host.name; + // Set status, tooltips, badges icons, etc. + $scope.jobhosts.forEach(function(element, i) { + $scope.jobhosts[i].host_name = ($scope.jobhosts[i].summary_fields.host) ? $scope.jobhosts[i].summary_fields.host.name : ''; $scope.jobhosts[i].status = ($scope.jobhosts[i].failed) ? 'failed' : 'success'; $scope.jobhosts[i].statusBadgeToolTip = JobStatusToolTip($scope.jobhosts[i].status) + " Click to view details."; - $scope.jobhosts[i].statusLinkTo = '/#/jobs/' + $scope.jobhosts[i].job + '/job_events/?host=' + - encodeURI($scope.jobhosts[i].summary_fields.host.name); - } + if ($scope.jobhosts[i].summary_fields.host) { + $scope.jobhosts[i].statusLinkTo = '/#/jobs/' + $scope.jobhosts[i].job + '/job_events/?host=' + + encodeURI($scope.jobhosts[i].summary_fields.host.name); + } + else { + $scope.jobhosts[i].statusLinkTo = '/#/jobs/' + $scope.jobhosts[i].job + '/job_events'; + } + }); if ($scope.job_id !== null && $scope.job_id !== undefined && $scope.job_id !== '') { // need job_status so we can show/hide refresh button diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index d439e47db3..675d63b924 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -10,13 +10,16 @@ 'use strict'; -function JobsList($scope, $compile, ClearScope, Breadcrumbs, LoadScope, RunningJobsList, CompletedJobsList, QueuedJobsList, - GetChoices, GetBasePath, Wait) { +function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBreadCrumbs, LoadScope, RunningJobsList, CompletedJobsList, QueuedJobsList, + ScheduledJobsList, GetChoices, GetBasePath, Wait, DeleteJob) { ClearScope(); - var e, completed_scope, running_scope, queued_scope, choicesCount = 0, listsCount = 0; - // schedule_scope; + var e, + completed_scope, running_scope, queued_scope, scheduled_scope, + choicesCount = 0, listsCount = 0; + + LoadBreadCrumbs(); // Add breadcrumbs e = angular.element(document.getElementById('breadcrumbs')); @@ -45,7 +48,7 @@ function JobsList($scope, $compile, ClearScope, Breadcrumbs, LoadScope, RunningJ scope: completed_scope, list: CompletedJobsList, id: 'completed-jobs', - url: '/static/sample/data/jobs/completed/data.json' + url: GetBasePath('unified_jobs') + '?status__in=(succesful,failed,error,canceled)' ///static/sample/data/jobs/completed/data.json' }); running_scope = $scope.$new(); LoadScope({ @@ -53,7 +56,7 @@ function JobsList($scope, $compile, ClearScope, Breadcrumbs, LoadScope, RunningJ scope: running_scope, list: RunningJobsList, id: 'active-jobs', - url: '/static/sample/data/jobs/running/data.json' + url: GetBasePath('unified_jobs') + '?status=running' }); queued_scope = $scope.$new(); LoadScope({ @@ -61,8 +64,29 @@ function JobsList($scope, $compile, ClearScope, Breadcrumbs, LoadScope, RunningJ scope: queued_scope, list: QueuedJobsList, id: 'queued-jobs', - url: '/static/sample/data/jobs/queued/data.json' + url: GetBasePath('unified_jobs') + '?status__in(pending,waiting,new)' //'/static/sample/data/jobs/queued/data.json' }); + scheduled_scope = $scope.$new(); + LoadScope({ + parent_scope: $scope, + scope: scheduled_scope, + list: ScheduledJobsList, + id: 'scheduled-jobs', + url: GetBasePath('schedules') + }); + + completed_scope.deleteJob = function(id) { + DeleteJob({ scope: completed_scope, id: id }); + }; + + queued_scope.deleteJob = function(id) { + DeleteJob({ scope: queued_scope, id: id }); + }; + + running_scope.deleteJob = function(id) { + DeleteJob({ scope: running_scope, id: id }); + }; + }); if ($scope.removeChoicesReady) { @@ -95,8 +119,8 @@ function JobsList($scope, $compile, ClearScope, Breadcrumbs, LoadScope, RunningJ } -JobsList.$inject = ['$scope', '$compile', 'ClearScope', 'Breadcrumbs', 'LoadScope', 'RunningJobsList', 'CompletedJobsList', - 'QueuedJobsList', 'GetChoices', 'GetBasePath', 'Wait']; +JobsListController.$inject = ['$scope', '$compile', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'LoadScope', 'RunningJobsList', 'CompletedJobsList', + 'QueuedJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', 'DeleteJob']; function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobForm, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index 44fea28a78..7f71276b5d 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -9,7 +9,7 @@ 'use strict'; -angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper']) +angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers']) .factory('JobStatusToolTip', [ function () { @@ -166,7 +166,7 @@ angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinitio * */ .factory('LoadScope', ['SearchInit', 'PaginateInit', 'GenerateList', 'PageRangeSetup', 'ProcessErrors', 'Rest', - function(SearchInit, PaginateInit, GenerateList, PageRangeSetup, ProcessErrors, Rest) { + function(SearchInit, PaginateInit, GenerateList) { return function(params) { var parent_scope = params.parent_scope, scope = params.scope, @@ -180,7 +180,7 @@ angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinitio breadCrumbs: false, scope: scope, searchSize: 'col-lg-4 col-md-6 col-sm-12 col-xs-12', - showSearch: false + showSearch: true }); SearchInit({ @@ -197,35 +197,25 @@ angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinitio pageSize: 10 }); + scope.iterator = list.iterator; + // The following bits probably don't belong here once the API is available. if (scope.removePostRefresh) { scope.removePostRefresh(); } - scope.$on('PostRefresh', function(e, data){ - var i, modifier; - PageRangeSetup({ - scope: scope, - count: data.count, - next: data.next, - previous: data.previous, - iterator: list.iterator - }); - scope[list.iterator + 'Loading'] = false; - for (i = 1; i <= 3; i++) { - modifier = (i === 1) ? '' : i; - scope[list.iterator + 'HoldInput' + modifier] = false; - } - scope[list.name] = data.results; - window.scrollTo(0, 0); + scope.$on('PostRefresh', function(){ scope[list.name].forEach(function(item, item_idx) { + var fld, field, + itm = scope[list.name][item_idx]; + // Set the item type label if (list.fields.type) { parent_scope.type_choices.every(function(choice) { if (choice.value === item.type) { - scope[list.name][item_idx].type = choice.label; + itm.type = choice.label; return false; } return true; @@ -234,33 +224,107 @@ angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinitio // Set the job status label parent_scope.status_choices.every(function(status) { if (status.value === item.status) { - scope[list.name][item_idx].status_label = status.label; + itm.status_label = status.label; return false; } return true; }); + if (list.name === 'completed_jobs' || list.name === 'running_jobs') { - scope[list.name][item_idx].status_tip = scope[list.name][item_idx].status_label + '. Click for details.'; + itm.status_tip = itm.status_label + '. Click for details.'; } - else { - scope[list.name][item_idx].status_tip = 'Pending'; + else if (list.name === 'queued_jobs') { + itm.status_tip = 'Pending'; + } + else if (list.name === 'scheduled_jobs') { + itm.enabled = (itm.enabled) ? true : false; + itm.play_tip = (itm.enabled) ? 'Schedule is Active. Click to temporarily stop.' : 'Schedule is temporarily stopped. Click to activate.'; } - scope[list.name][item_idx].status_popover_title = scope[list.name][item_idx].status_label; - scope[list.name][item_idx].status_popover = "

" + scope[list.name][item_idx].job_explanation + "

\n"; - scope[list.name][item_idx].status_popover += "

More...

\n"; - }); + // Copy summary_field values + for (field in list.fields) { + fld = list.fields[field]; + if (fld.sourceModel) { + if (itm.summary_fields[fld.sourceModel]) { + itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField]; + } + } + } + + itm.status_popover_title = itm.status_label; + itm.status_popover = "

" + itm.job_explanation + "

\n" + + "

More...

\n" + + "
esc or click to close
\n"; + }); parent_scope.$emit('listLoaded'); }); - - Rest.setUrl(url); - Rest.get() - .success(function(data) { - scope.$emit('PostRefresh', data); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); + scope.search(list.iterator); }; -}]); \ No newline at end of file +}]) + +.factory('DeleteJob', ['Find', 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Prompt', +function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt){ + return function(params) { + + var scope = params.scope, + id = params.id, + action, jobs, job, url, action_label, hdr; + + 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; + } + 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 = 'Cancel Job'; + } else { + url = GetBasePath('jobs') + id + '/'; + action_label = 'delete'; + hdr = 'Delete Job'; + } + + action = function () { + Wait('start'); + Rest.setUrl(url); + if (action_label === 'cancel') { + Rest.post() + .success(function () { + $('#prompt-modal').modal('hide'); + scope.search(scope.iterator); + }) + .error(function (data, status) { + $('#prompt-modal').modal('hide'); + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + + ' failed. POST returned status: ' + status }); + }); + } else { + Rest.destroy() + .success(function () { + $('#prompt-modal').modal('hide'); + scope.search(scope.iterator); + }) + .error(function (data, status) { + $('#prompt-modal').modal('hide'); + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + + ' failed. DELETE returned status: ' + status }); + }); + } + }; + + Prompt({ + hdr: hdr, + body: "
Are you sure you want to " + action_label + " job " + id + " " + job.name + "?
", + action: action + }); + + }; +}]); + diff --git a/awx/ui/static/js/helpers/refresh.js b/awx/ui/static/js/helpers/refresh.js index 1300b5c3bd..a7f25d6f9b 100644 --- a/awx/ui/static/js/helpers/refresh.js +++ b/awx/ui/static/js/helpers/refresh.js @@ -44,7 +44,7 @@ angular.module('RefreshHelper', ['RestServices', 'Utilities', 'PaginationHelpers scope[iterator + 'HoldInput' + modifier] = false; } scope[set] = data.results; - window.scrollTo(0, 0); + //window.scrollTo(0, 0); Wait('stop'); scope.$emit('PostRefresh'); }) diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js index 8f4f7a275f..086aad308e 100644 --- a/awx/ui/static/js/helpers/search.js +++ b/awx/ui/static/js/helpers/search.js @@ -234,16 +234,19 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) if (scope.removeDoSearch) { scope.removeDoSearch(); } - scope.removeDoSearch = scope.$on('doSearch', function (e, iterator, page, load) { + scope.removeDoSearch = scope.$on('doSearch', function (e, iterator, page, load, calcOnly) { // // Execute the search // - scope[iterator + 'Loading'] = (load === undefined || load === true) ? true : false; - var url = defaultUrl, + var url = (calcOnly) ? '' : defaultUrl, connect; + + if (!calcOnly) { + scope[iterator + 'Loading'] = (load === undefined || load === true) ? true : false; + scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; + } - //finalize and execute the query - scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; + //finalize and execute the query if (scope[iterator + 'SearchParams']) { if (/\/$/.test(url)) { url += '?' + scope[iterator + 'SearchParams']; @@ -261,20 +264,26 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) connect = (/\/$/.test(url)) ? '?' : '&'; url += connect + scope[iterator + 'ExtraParms']; } - url = url.replace(/\&\&/, '&'); - Refresh({ - scope: scope, - set: set, - iterator: iterator, - url: url - }); + url = url.replace(/\&\&/g, '&'); + + if (calcOnly) { + scope.$emit('searchParamsReady', url); + } + else { + Refresh({ + scope: scope, + set: set, + iterator: iterator, + url: url + }); + } }); if (scope.removePrepareSearch) { scope.removePrepareSearch(); } - scope.removePrepareSearch = scope.$on('prepareSearch', function (e, iterator, page, load, spin) { + scope.removePrepareSearch = scope.$on('prepareSearch', function (e, iterator, page, load, calcOnly) { // // Start building the search key/value pairs. This will process each search widget, if the // selected field is an object type (used on activity stream). @@ -320,13 +329,13 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) } } } - scope.$emit('prepareSearch2', iterator, page, load, spin); + scope.$emit('prepareSearch2', iterator, page, load, calcOnly); }); if (scope.removePrepareSearch2) { scope.removePrepareSearch2(); } - scope.removePrepareSearch2 = scope.$on('prepareSearch2', function (e, iterator, page, load, spin) { + scope.removePrepareSearch2 = scope.$on('prepareSearch2', function (e, iterator, page, load, calcOnly) { // Continue building the search by examining the remaining search widgets. If we're looking at activity_stream, // there's more than one. var i, modifier, @@ -408,7 +417,7 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) scope[iterator + 'SearchParams'] += 'order_by=' + encodeURI(sort_order); } - scope.$emit('doSearch', iterator, page, load, spin); + scope.$emit('doSearch', iterator, page, load, calcOnly); }); scope.startSearch = function (e, iterator) { @@ -418,22 +427,28 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) } }; - scope.search = function (iterator, page, load) { - // Called to initiate a searh. - // Page is optional. Added to accomodate back function on Job Events detail. - // Spin optional -set to false if spin not desired. - // Load optional -set to false if loading message not desired - load = (load === undefined) ? true : false; + /** + * Initiate a searh. + * + * @iterator: required, list.iterator value + * @Page: optional. Added to accomodate back function on Job Events detail. + * @Load: optional, set to false if 'Loading' message not desired + * @calcOnly: optiona, set to true when you want to calc or figure out search params without executing the search + */ + scope.search = function (iterator, page, load, calcOnly) { + page = page || null; + load = (load) ? true : false; + calcOnly = (calcOnly) ? true : false; if (load) { - scope[set] = []; + scope[set] = []; //clear the list array to make sure 'Loading' is the only thing visible on the list } - scope.$emit('prepareSearch', iterator, page, load); + scope.$emit('prepareSearch', iterator, page, load, calcOnly); }; scope.sort = function (fld) { - // reset sort icons back to 'icon-sort' on all columns - // except the one clicked + // Reset sort icons back to 'icon-sort' on all columns + // except the one clicked. $('.list-header').each(function () { if ($(this).attr('id') !== fld + '-header') { var icon = $(this).find('i'); diff --git a/awx/ui/static/js/lists/CompletedJobs.js b/awx/ui/static/js/lists/CompletedJobs.js index 2a5ee79d01..2e57bead77 100644 --- a/awx/ui/static/js/lists/CompletedJobs.js +++ b/awx/ui/static/js/lists/CompletedJobs.js @@ -14,6 +14,7 @@ angular.module('CompletedJobsDefinition', []) name: 'completed_jobs', iterator: 'completed_job', editTitle: 'Completed Jobs', + 'class': 'table-condensed', index: false, hover: true, well: false, @@ -25,7 +26,12 @@ angular.module('CompletedJobsDefinition', []) key: true, desc: true, searchType: 'int', - columnClass: 'col-lg-1 col-md-2 col-sm-2 col-xs-2' + columnClass: 'col-md-1 col-sm-2 col-xs-2' + }, + inventory: { + label: 'Inventory ID', + searchType: 'int', + searchOnly: true }, modified: { label: 'Completed On', @@ -34,6 +40,12 @@ angular.module('CompletedJobsDefinition', []) filter: "date:'MM/dd/yy HH:mm:ss'", columnClass: "col-md-2 hidden-xs" }, + next_job_run: { + label: 'Next Run', + searchable: false, + filter: "date:'MM/dd/yy HH:mm:ss'", + columnClass: "col-md-2 hidden-sm hidden-xs" + }, type: { label: 'Type', link: false, @@ -41,8 +53,10 @@ angular.module('CompletedJobsDefinition', []) }, name: { label: 'Name', - columnClass: 'col-sm-4 col-xs-5', - ngHref: 'nameHref' + columnClass: 'col-md-3 col-xs-5', + ngHref: 'nameHref', + sourceModel: 'template', + sourceField: 'name' }, failed: { label: 'Job failed?', @@ -51,17 +65,29 @@ angular.module('CompletedJobsDefinition', []) searchValue: 'true', searchOnly: true, nosort: true + }, + status: { + label: 'Status', + searchType: 'select', + searchOnly: true, + searchOptions: [ + { name: "Success", value: "successful" }, + { name: "Error", value: "error" }, + { name: "Failed", value: "failed" }, + { name: "Canceled", value: "canceled" } + ] } }, - + actions: { + columnClass: 'col-md-2 col-sm-3 col-xs-3', refresh: { mode: 'all', awToolTip: "Refresh the page", ngClick: "refresh()" } }, - + fieldActions: { status: { mode: 'all', @@ -79,18 +105,10 @@ angular.module('CompletedJobsDefinition', []) awToolTip: 'Relaunch the job', dataPlacement: 'top' }, - cancel: { - mode: 'all', - ngClick: 'deleteJob(completed_job.id)', - awToolTip: 'Cancel a running or pending job', - ngShow: "completed_job.status == 'pending' || completed_job.status == 'running' || completed_job.status == 'waiting'", - dataPlacement: 'top' - }, "delete": { mode: 'all', ngClick: 'deleteJob(completed_job.id)', awToolTip: 'Delete the job', - ngShow: "completed_job.status != 'pending' && completed_job.status != 'running' && completed_job.status != 'waiting'", dataPlacement: 'top' }, dropdown: { @@ -99,11 +117,9 @@ angular.module('CompletedJobsDefinition', []) icon: 'fa-search-plus', 'class': 'btn-default btn-xs', options: [ - { ngClick: 'editJob(completed_job.id, completed_job.summary_fields.job_template.name)', label: 'Status' }, - { ngClick: 'viewEvents(completed_job.id, completed_job.summary_fields.job_template.name)', label: 'Events', - ngHide: "completed_job.status == 'new'" }, - { ngClick: 'viewSummary(completed_job.id, completed_job.summary_fields.job_template.name)', label: 'Host Summary', - ngHide: "completed_job.status == 'new'" } + { ngHref: '/#/jobs/{{ completed_job.id }}', label: 'Status' }, + { ngHref: '/#/jobs/{{ completed_job.id }}/job_events', label: 'Events', ngHide: "completed_job.status == 'new'" }, + { ngHref: '/#/jobs/{{ completed_job.id }}/job_host_summaries', label: 'Host Summary' } ] } } diff --git a/awx/ui/static/js/lists/Jobs.js b/awx/ui/static/js/lists/Jobs.js index 10fa69c0a7..4bbb968a6a 100644 --- a/awx/ui/static/js/lists/Jobs.js +++ b/awx/ui/static/js/lists/Jobs.js @@ -10,7 +10,7 @@ 'use strict'; angular.module('JobsListDefinition', []) - .value( 'JobList', { + .value( 'JobsList', { name: 'jobs', iterator: 'job', @@ -39,13 +39,9 @@ angular.module('JobsListDefinition', []) searchable: false, filter: "date:'MM/dd HH:mm:ss'" }, - job_template: { - label: 'Job Template', - ngBind: 'job.summary_fields.job_template.name', - //ngHref: "{{ '/#/job_templates/?name=' + job.summary_fields.job_template.name }}", - ngHref:"{{ '/#/job_templates/' + job.job_template }}", - sourceModel: 'job_template', - sourceField: 'name' + name: { + label: 'Name', + link: false }, failed: { label: 'Job failed?', diff --git a/awx/ui/static/js/lists/Projects.js b/awx/ui/static/js/lists/Projects.js index 195d5f973a..0e6fe77192 100644 --- a/awx/ui/static/js/lists/Projects.js +++ b/awx/ui/static/js/lists/Projects.js @@ -93,36 +93,31 @@ angular.module('ProjectsListDefinition', []) fieldActions: { edit: { - label: 'Edit', ngClick: "editProject(project.id)", - awToolTip: 'Edit project properties', + awToolTip: 'Edit the project', dataPlacement: 'top' }, scm_update: { - label: 'Update', ngClick: 'SCMUpdate(project.id)', awToolTip: "{{ project.scm_update_tooltip }}", ngClass: "project.scm_type_class", dataPlacement: 'top' }, cancel: { - label: 'Stop', ngClick: "cancelUpdate(project.id, project.name)", - awToolTip: 'Cancel a running SCM update process', + awToolTip: 'Cancel the SCM update', ngShow: "project.status == 'updating'", dataPlacement: 'top' }, schedule: { - label: 'Schedule', mode: 'all', ngHref: '#/projects/{{ project.id }}/schedules', - awToolTip: 'Schedule future project sync runs', + awToolTip: 'Schedule future SCM updates', dataPlacement: 'top' }, "delete": { - label: 'Delete', ngClick: "deleteProject(project.id, project.name)", - awToolTip: 'Permanently remove project from the database', + awToolTip: 'Delete the project', ngShow: "project.status !== 'updating'", dataPlacement: 'top' } diff --git a/awx/ui/static/js/lists/QueuedJobs.js b/awx/ui/static/js/lists/QueuedJobs.js index aec55998ec..514983d376 100644 --- a/awx/ui/static/js/lists/QueuedJobs.js +++ b/awx/ui/static/js/lists/QueuedJobs.js @@ -14,6 +14,7 @@ angular.module('QueuedJobsDefinition', []) name: 'queued_jobs', iterator: 'queued_job', editTitle: 'Queued Jobs', + 'class': 'table-condensed', index: false, hover: true, well: false, @@ -24,7 +25,7 @@ angular.module('QueuedJobsDefinition', []) key: true, desc: true, searchType: 'int', - columnClass: 'col-lg-1 col-md-2 col-sm-2 col-xs-2' + columnClass: 'col-md-1 col-sm-2 col-xs-2' }, inventory: { label: 'Inventory ID', @@ -38,6 +39,12 @@ angular.module('QueuedJobsDefinition', []) filter: "date:'MM/dd/yy HH:mm:ss'", columnClass: 'col-md-2 hidden-xs' }, + next_job_run: { + label: 'Next Run', + searchable: false, + filter: "date:'MM/dd/yy HH:mm:ss'", + columnClass: "col-md-2 hidden-sm hidden-xs" + }, type: { label: 'Type', link: false, @@ -45,20 +52,15 @@ angular.module('QueuedJobsDefinition', []) }, name: { label: 'Name', - columnClass: 'col-sm-4 col-xs-5', - ngHref: 'nameHref' - }, - failed: { - label: 'Job failed?', - searchSingleValue: true, - searchType: 'boolean', - searchValue: 'true', - searchOnly: true, - nosort: true + columnClass: 'col-sm-3 col-xs-5', + ngHref: 'nameHref', + sourceModel: 'template', + sourceField: 'name' } }, - + actions: { + columnClass: 'col-md-2 col-sm-3 col-xs-3', refresh: { mode: 'all', awToolTip: "Refresh the page", diff --git a/awx/ui/static/js/lists/RunningJobs.js b/awx/ui/static/js/lists/RunningJobs.js index 23bf6b4e87..19879e76b3 100644 --- a/awx/ui/static/js/lists/RunningJobs.js +++ b/awx/ui/static/js/lists/RunningJobs.js @@ -14,6 +14,7 @@ angular.module('RunningJobsDefinition', []) name: 'running_jobs', iterator: 'running_job', editTitle: 'Completed Jobs', + 'class': 'table-condensed', index: false, hover: true, well: false, @@ -24,7 +25,7 @@ angular.module('RunningJobsDefinition', []) key: true, desc: true, searchType: 'int', - columnClass: 'col-lg-1 col-md-2 col-sm-2 col-xs-2' + columnClass: 'col-md-1 col-sm-2 col-xs-2' }, inventory: { label: 'Inventory ID', @@ -38,6 +39,12 @@ angular.module('RunningJobsDefinition', []) filter: "date:'MM/dd/yy HH:mm:ss'", columnClass: "col-md-2 hidden-xs" }, + next_job_run: { + label: 'Next Run', + searchable: false, + filter: "date:'MM/dd/yy HH:mm:ss'", + columnClass: "col-md-2 hidden-sm hidden-xs" + }, type: { label: 'Type', link: false, @@ -45,33 +52,31 @@ angular.module('RunningJobsDefinition', []) }, name: { label: 'Name', - columnClass: 'col-sm-4 col-xs-5', - ngHref: 'nameHref' - }, - failed: { - label: 'Job failed?', - searchSingleValue: true, - searchType: 'boolean', - searchValue: 'true', - searchOnly: true, - nosort: true + columnClass: 'col-md-3 col-xs-5', + ngHref: 'nameHref', + sourceModel: 'template', + sourceField: 'name' } }, - + actions: { + columnClass: 'col-md-2 col-sm-3 col-xs-3', refresh: { mode: 'all', awToolTip: "Refresh the page", ngClick: "refresh()" } }, - + fieldActions: { status: { mode: 'all', + awToolTip: "{{ running_job.status_tip }}", + awTipPlacement: "top", + dataTitle: "{{ running_job.status_popover_title }}", iconClass: 'fa icon-job-{{ running_job.status }}', - awToolTip: "{{ running_job.statusToolTip }}", - dataPlacement: 'top' + awPopOver: "{{ running_job.status_popover }}", + dataPlacement: 'left' }, submit: { icon: 'icon-rocket', @@ -84,14 +89,6 @@ angular.module('RunningJobsDefinition', []) mode: 'all', ngClick: 'deleteJob(running_job.id)', awToolTip: 'Cancel the job', - ngShow: "running_job.status == 'pending' || running_job.status == 'running' || running_job.status == 'waiting'", - dataPlacement: 'top' - }, - "delete": { - mode: 'all', - ngClick: 'deleteJob(running_job.id)', - awToolTip: 'Delete the job', - ngShow: "running_job.status != 'pending' && running_job.status != 'running' && running_job.status != 'waiting'", dataPlacement: 'top' }, dropdown: { @@ -100,11 +97,9 @@ angular.module('RunningJobsDefinition', []) icon: 'fa-search-plus', 'class': 'btn-default btn-xs', options: [ - { ngClick: 'editJob(running_job.id, running_job.summary_fields.job_template.name)', label: 'Status' }, - { ngClick: 'viewEvents(running_job.id, running_job.summary_fields.job_template.name)', label: 'Events', - ngHide: "running_job.status == 'new'" }, - { ngClick: 'viewSummary(running_job.id, running_job.summary_fields.job_template.name)', label: 'Host Summary', - ngHide: "running_job.status == 'new'" } + { ngHref: '/#/jobs/{{ running_job.id }}', label: 'Status' }, + { ngHref: '/#/jobs/{{ running_job.id }}/job_events', label: 'Events' }, + { ngHref: '/#/jobs/{{ running_job.id }}/job_host_summaries', label: 'Host Summary' } ] } } diff --git a/awx/ui/static/js/lists/ScheduledJobs.js b/awx/ui/static/js/lists/ScheduledJobs.js new file mode 100644 index 0000000000..2d7332403d --- /dev/null +++ b/awx/ui/static/js/lists/ScheduledJobs.js @@ -0,0 +1,78 @@ +/********************************************* + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * ScheduledJobs.js + * + * + */ + +'use strict'; + +angular.module('ScheduledJobsDefinition', []) + .value( 'ScheduledJobsList', { + + name: 'scheduled_jobs', + iterator: 'scheduled_job', + editTitle: 'Scheduled Jobs', + 'class': 'table-condensed', + index: false, + hover: true, + well: false, + + fields: { + next_run: { + label: 'Next Run', + link: false, + columnClass: "col-md-2" + }, + dtend: { + label: 'Ends On', + searchable: false, + filter: "date:'MM/dd/yy HH:mm:ss'", + columnClass: "col-md-2 hidden-xs" + }, + type: { + label: 'Type', + link: false, + columnClass: "col-md-2 hidden-sm hidden-xs" + }, + template_name: { + label: 'Name', + columnClass: "col-md-4 col-xs-5", + sourceModel: "template", + sourceField: "name" + } + }, + + actions: { + columnClass: 'col-md-2 col-sm-3 col-xs-3', + refresh: { + mode: 'all', + awToolTip: "Refresh the page", + ngClick: "refresh()" + } + }, + + fieldActions: { + "play": { + mode: "all", + ngClick: "toggleSchedule(scheduled_job.id)", + awToolTip: "{{ scheduled_job.play_tip }}", + dataTipWatch: "scheduled_job.play_tip", + iconClass: "{{ 'fa icon-schedule-enabled-' + scheduled_job.enabled }}", + dataPlacement: 'top', + }, + "edit": { + mode: "all", + ngClick: "edit(scheduled_job.id)", + awToolTip: "Edit the schedule", + dataPlacement: "top" + }, + "delete": { + mode: 'all', + ngClick: 'deleteJob(completed_job.id)', + awToolTip: 'Delete the schedule', + dataPlacement: 'top' + } + } + }); \ No newline at end of file diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index d8fb3a1045..853e2be5d2 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -226,6 +226,7 @@ textarea.allowresize { } .popover { z-index: 2000; + min-width: 200px; max-width: 325px; } .popover-footer { @@ -889,7 +890,28 @@ input[type="checkbox"].checkbox-no-label { .table-hover-inverse tbody tr:hover > td, .table-hover-inverse tbody tr:hover > th { - background-color: #dff0d8; + background-color: #c6e5e5; +} + +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #c6e5e5; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr.success:hover > th { + background-color: #c6e5e5; } .table-summary thead > tr > th, @@ -1017,6 +1039,14 @@ input[type="checkbox"].checkbox-no-label { content: "\f111"; } + .icon-schedule-enabled-true:before { + content: "\f04c"; + } + + .icon-schedule-enabled-false:before { + content: "\f04b"; + } + .badge { padding: 2px 3px 3px 4px; font-size: 10px; @@ -1025,20 +1055,12 @@ input[type="checkbox"].checkbox-no-label { } .job-list { - margin-top: 30px; - .row { - margin-left: 0; - margin-right: 0; - } - .page-row div:first-child { - padding-left: 8px; - } + margin-top: 20px; .title { - font-size: 14px; - padding-left: 8px; + margin-left: 3px; font-weight: bold; margin-bottom: 6px; - color: @black; + color: #666; } thead >tr >th, .page-row { font-size: 12px; diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index ba521a97ed..3884ceddec 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -291,31 +291,26 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job $(element).popover({ placement: placement, delay: 0, title: title, content: attrs.awPopOver, trigger: trigger, html: true, container: container }); $(element).click(function() { - var self = $(this).attr('id'); + var self = $(this); $('.help-link, .help-link-white').each( function() { - if (self !== $(this).attr('id')) { + if (self.attr('id') !== $(this).attr('id')) { $(this).popover('hide'); } }); - $('.popover').each(function() { // remove lingering popover
. Seems to be a bug in TB3 RC1 $(this).remove(); }); - $('.tooltip').each( function() { // close any lingering tool tipss $(this).hide(); }); - $(this).popover('toggle'); - $('.popover').each(function() { $compile($(this))(scope); //make nested directives work! }); - $('.popover-content, .popover-title').click(function() { - $('#' + self).popover('hide'); + $(self).popover('hide'); }); }); diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js index 1ceba4fed3..8c8b3af128 100644 --- a/awx/ui/static/lib/ansible/generator-helpers.js +++ b/awx/ui/static/lib/ansible/generator-helpers.js @@ -9,7 +9,7 @@ 'use strict'; -angular.module('GeneratorHelpers', ['GeneratorHelpers']) +angular.module('GeneratorHelpers', []) .factory('Attr', function () { return function (obj, key, fld) { @@ -630,7 +630,8 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) // var iterator = params.iterator, form = params.template, - useMini = params.mini, + size = params.size, + includeSize = (params.includeSize === undefined) ? true : params.includeSize, fld, i, html = '', modifier, @@ -638,12 +639,14 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) for (i = 1; i <= searchWidgets; i++) { modifier = (i === 1) ? '' : i; - html += "
\n"; + + if (includeSize) { + html += "
\n"; + } - html += "
\n"; html += "
\n"; html += "
\n"; + + if (includeSize) { + html += "
\n"; + } } return html; diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index 9679b5701d..ecf73bec33 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -164,9 +164,9 @@ angular.module('ListGenerator', ['GeneratorHelpers']) html += "
\n"; } - html += "
\n"; - + if (options.showSearch=== undefined || options.showSearch === true) { + html += "
\n"; if (list.name !== 'groups') { if (options.searchSize) { html += SearchWidget({ @@ -260,7 +260,6 @@ angular.module('ListGenerator', ['GeneratorHelpers']) //lookup html += "
\n"; } - html += "
\n"; } @@ -400,8 +399,8 @@ angular.module('ListGenerator', ['GeneratorHelpers']) action: field_action }); } - html += (fAction.label) ? " " + list.fieldActions[field_action].label + - "" : ""; + //html += (fAction.label) ? " " + list.fieldActions[field_action].label + + // "" : ""; html += ""; } } diff --git a/awx/ui/static/partials/jobs.html b/awx/ui/static/partials/jobs.html index 8096fa0a71..bc51d27b7e 100644 --- a/awx/ui/static/partials/jobs.html +++ b/awx/ui/static/partials/jobs.html @@ -1,11 +1,11 @@
- +
-
-
+
+
Completed
diff --git a/awx/ui/static/sample/data/jobs/completed/data.json b/awx/ui/static/sample/data/jobs/completed/data.json index 09dd9b8192..4092226ee1 100644 --- a/awx/ui/static/sample/data/jobs/completed/data.json +++ b/awx/ui/static/sample/data/jobs/completed/data.json @@ -1,5 +1,5 @@ { - "count": 5, + "count": 15, "next": "/blah/blah/blah", "previous": null, "results": [ @@ -576,6 +576,580 @@ "MAIL": "/var/spool/mail/vagrant", "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:" } + }, + { + "id": 6, + "url": "/api/v1/jobs/1/", + "related": { + "job_host_summaries": "/api/v1/jobs/1/job_host_summaries/", + "activity_stream": "/api/v1/jobs/1/activity_stream/", + "job_events": "/api/v1/jobs/1/job_events/", + "job_template": "/api/v1/job_templates/3/", + "inventory": "/api/v1/inventories/4/", + "project": "/api/v1/projects/1/", + "credential": "/api/v1/credentials/8/", + "start": "/api/v1/jobs/1/start/", + "cancel": "/api/v1/jobs/1/cancel/" + }, + "summary_fields": { + "credential": { + "name": "ssh", + "description": "machine creds", + "kind": "ssh", + "cloud": false + }, + "job_template": { + "name": "Hello World", + "description": "" + }, + "project": { + "name": "Examples", + "description": "Ansible example project", + "scm_type": "git" + }, + "inventory": { + "name": "Rackspace", + "description": "", + "has_active_failures": true, + "total_hosts": 20, + "hosts_with_active_failures": 20, + "total_groups": 3, + "groups_with_active_failures": 3, + "has_inventory_sources": true, + "total_inventory_sources": 1, + "inventory_sources_with_failures": 1 + } + }, + "created": "2014-03-06T16:51:04.557Z", + "modified": "2014-03-06T16:51:14.272Z", + "job_template": 3, + "job_type": "run", + "type": "playbook_run", + "name": "Hello World", + "job_explanation": "Bad things happened", + "status": "failed", + "failed": true, + "inventory": 4, + "project": 1, + "playbook": "lamp_simple/site.yml", + "credential": 8, + "cloud_credential": null, + "forks": 0, + "limit": "", + "verbosity": 0, + "extra_vars": "{\n\t\"variable1\": \"some value\",\n\t\"variable2\": \"another value\"\n}", + "job_tags": "", + "launch_type": "manual", + "result_traceback": "", + "passwords_needed_to_start": [], + "job_args": "[\"ssh-agent\", \"sh\", \"-c\", \"ssh-add /tmp/tmp5N437j && ansible-playbook -i /vagrant/ansible-commander/awx/plugins/inventory/awxrest.py -u vagrant -e '{\\\"variable1\\\": \\\"some value\\\", \\\"variable2\\\": \\\"another value\\\"}' lamp_simple/site.yml\"]", + "job_cwd": "/vagrant/ansible-commander/awx/projects/_1__examples", + "job_env": { + "CELERY_LOG_REDIRECT_LEVEL": "WARNING", + "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", + "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199", + "LESSOPEN": "|/usr/bin/lesspipe.sh %s", + "_MP_FORK_LOGFILE_": "", + "SSH_CLIENT": "10.0.2.2 61378 22", + "CVS_RSH": "ssh", + "LOGNAME": "vagrant", + "USER": "vagrant", + "HOME": "/home/vagrant", + "PATH": "/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin", + "REST_API_TOKEN": "**********************************", + "CALLBACK_CONSUMER_PORT": "tcp://127.0.0.1:5557", + "ANSIBLE_CALLBACK_PLUGINS": "/vagrant/ansible-commander/awx/plugins/callback", + "LANG": "en_US.UTF-8", + "HISTCONTROL": "ignoredups", + "TERM": "xterm", + "SHELL": "/bin/bash", + "TZ": "America/New_York", + "_MP_FORK_LOGFORMAT_": "[%(asctime)s: %(levelname)s/%(processName)s] %(message)s", + "SHLVL": "1", + "G_BROKEN_FILENAMES": "1", + "HISTSIZE": "1000", + "CELERY_LOG_FILE": "", + "DJANGO_PROJECT_DIR": "/vagrant/ansible-commander", + "ANSIBLE_HOST_KEY_CHECKING": "False", + "JOB_ID": "1", + "PYTHONPATH": "/vagrant/ansible-commander/awx/lib/site-packages:", + "CELERY_LOADER": "djcelery.loaders.DjangoLoader", + "_MP_FORK_LOGLEVEL_": "10", + "ANSIBLE_NOCOLOR": "1", + "JOB_CALLBACK_DEBUG": "1", + "REST_API_URL": "http://127.0.0.1:8013", + "_": "/usr/bin/nohup", + "SSH_CONNECTION": "10.0.2.2 61378 10.0.2.15 22", + "INVENTORY_HOSTVARS": "True", + "SSH_TTY": "/dev/pts/0", + "CELERY_LOG_LEVEL": "10", + "HOSTNAME": "vagrant-centos64.vagrantup.com", + "INVENTORY_ID": "4", + "PWD": "/home/vagrant", + "CELERY_LOG_REDIRECT": "1", + "DJANGO_SETTINGS_MODULE": "awx.settings.development", + "MAIL": "/var/spool/mail/vagrant", + "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:" + } + }, + { + "id": 7, + "url": "/api/v1/jobs/2/", + "related": { + "job_host_summaries": "/api/v1/jobs/2/job_host_summaries/", + "activity_stream": "/api/v1/jobs/2/activity_stream/", + "job_events": "/api/v1/jobs/2/job_events/", + "job_template": "/api/v1/job_templates/3/", + "inventory": "/api/v1/inventories/4/", + "project": "/api/v1/projects/1/", + "credential": "/api/v1/credentials/8/", + "start": "/api/v1/jobs/2/start/", + "cancel": "/api/v1/jobs/2/cancel/" + }, + "summary_fields": { + "credential": { + "name": "ssh", + "description": "machine creds", + "kind": "ssh", + "cloud": false + }, + "job_template": { + "name": "Hello World", + "description": "" + }, + "project": { + "name": "Examples", + "description": "Ansible example project", + "status": "successful" + }, + "inventory": { + "name": "Rackspace", + "description": "", + "has_active_failures": true, + "total_hosts": 20, + "hosts_with_active_failures": 20, + "total_groups": 3, + "groups_with_active_failures": 3, + "has_inventory_sources": true, + "total_inventory_sources": 1, + "inventory_sources_with_failures": 1 + } + }, + "created": "2014-03-07T23:28:06.999Z", + "modified": "2014-03-07T23:28:16.424Z", + "job_template": 3, + "job_type": "run", + "job_explanation": "AWS access error. Check your credentials.", + "status": "failed", + "failed": true, + "type": "inventory_sync", + "name": "AWS Cloud", + "inventory": 4, + "project": 1, + "credential": 8, + "cloud_credential": null, + "forks": 0, + "limit": "", + "verbosity": 0, + "extra_vars": "{\n\t\"variable1\": \"some value\",\n\t\"variable2\": \"another value\"\n}", + "job_tags": "", + "launch_type": "manual", + "result_traceback": "", + "passwords_needed_to_start": [], + "job_args": "[\"ssh-agent\", \"sh\", \"-c\", \"ssh-add /tmp/tmpoeaDyc && ansible-playbook -i /vagrant/ansible-commander/awx/plugins/inventory/awxrest.py -u vagrant -e '{\\\"variable1\\\": \\\"some value\\\", \\\"variable2\\\": \\\"another value\\\"}' lamp_simple/site.yml\"]", + "job_cwd": "/vagrant/ansible-commander/awx/projects/_1__examples", + "job_env": { + "CELERY_LOG_REDIRECT_LEVEL": "WARNING", + "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", + "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199", + "LESSOPEN": "|/usr/bin/lesspipe.sh %s", + "_MP_FORK_LOGFILE_": "", + "SSH_CLIENT": "10.0.2.2 61378 22", + "CVS_RSH": "ssh", + "LOGNAME": "vagrant", + "USER": "vagrant", + "HOME": "/home/vagrant", + "PATH": "/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin", + "REST_API_TOKEN": "**********************************", + "CALLBACK_CONSUMER_PORT": "tcp://127.0.0.1:5557", + "ANSIBLE_CALLBACK_PLUGINS": "/vagrant/ansible-commander/awx/plugins/callback", + "LANG": "en_US.UTF-8", + "HISTCONTROL": "ignoredups", + "TERM": "xterm", + "SHELL": "/bin/bash", + "TZ": "America/New_York", + "_MP_FORK_LOGFORMAT_": "[%(asctime)s: %(levelname)s/%(processName)s] %(message)s", + "SHLVL": "1", + "G_BROKEN_FILENAMES": "1", + "HISTSIZE": "1000", + "CELERY_LOG_FILE": "", + "DJANGO_PROJECT_DIR": "/vagrant/ansible-commander", + "ANSIBLE_HOST_KEY_CHECKING": "False", + "JOB_ID": "2", + "PYTHONPATH": "/vagrant/ansible-commander/awx/lib/site-packages:", + "CELERY_LOADER": "djcelery.loaders.DjangoLoader", + "_MP_FORK_LOGLEVEL_": "10", + "ANSIBLE_NOCOLOR": "1", + "JOB_CALLBACK_DEBUG": "1", + "REST_API_URL": "http://127.0.0.1:8013", + "_": "/usr/bin/nohup", + "SSH_CONNECTION": "10.0.2.2 61378 10.0.2.15 22", + "INVENTORY_HOSTVARS": "True", + "SSH_TTY": "/dev/pts/0", + "CELERY_LOG_LEVEL": "10", + "HOSTNAME": "vagrant-centos64.vagrantup.com", + "INVENTORY_ID": "4", + "PWD": "/home/vagrant", + "CELERY_LOG_REDIRECT": "1", + "DJANGO_SETTINGS_MODULE": "awx.settings.development", + "MAIL": "/var/spool/mail/vagrant", + "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:" + } + }, + { + "id": 8, + "url": "/api/v1/jobs/2/", + "related": { + "job_host_summaries": "/api/v1/jobs/2/job_host_summaries/", + "activity_stream": "/api/v1/jobs/2/activity_stream/", + "job_events": "/api/v1/jobs/2/job_events/", + "job_template": "/api/v1/job_templates/3/", + "inventory": "/api/v1/inventories/4/", + "project": "/api/v1/projects/1/", + "credential": "/api/v1/credentials/8/", + "start": "/api/v1/jobs/2/start/", + "cancel": "/api/v1/jobs/2/cancel/" + }, + "summary_fields": { + "credential": { + "name": "ssh", + "description": "machine creds", + "kind": "ssh", + "cloud": false + }, + "job_template": { + "name": "Hello World", + "description": "" + }, + "project": { + "name": "Examples", + "description": "Ansible example project", + "status": "successful" + }, + "inventory": { + "name": "Rackspace", + "description": "", + "has_active_failures": true, + "total_hosts": 20, + "hosts_with_active_failures": 20, + "total_groups": 3, + "groups_with_active_failures": 3, + "has_inventory_sources": true, + "total_inventory_sources": 1, + "inventory_sources_with_failures": 1 + } + }, + "created": "2014-03-07T23:28:06.999Z", + "modified": "2014-03-07T23:28:16.424Z", + "job_template": 3, + "job_type": "run", + "job_explanation": "Completed successfully", + "status": "successful", + "failed": false, + "type": "inventory_sync", + "inventory": 4, + "name": "Rackspace", + "project": 1, + "playbook": "lamp_simple/site.yml", + "credential": 8, + "cloud_credential": null, + "forks": 0, + "limit": "", + "verbosity": 0, + "extra_vars": "{\n\t\"variable1\": \"some value\",\n\t\"variable2\": \"another value\"\n}", + "job_tags": "", + "launch_type": "manual", + "result_traceback": "", + "passwords_needed_to_start": [], + "job_args": "[\"ssh-agent\", \"sh\", \"-c\", \"ssh-add /tmp/tmpoeaDyc && ansible-playbook -i /vagrant/ansible-commander/awx/plugins/inventory/awxrest.py -u vagrant -e '{\\\"variable1\\\": \\\"some value\\\", \\\"variable2\\\": \\\"another value\\\"}' lamp_simple/site.yml\"]", + "job_cwd": "/vagrant/ansible-commander/awx/projects/_1__examples", + "job_env": { + "CELERY_LOG_REDIRECT_LEVEL": "WARNING", + "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", + "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199", + "LESSOPEN": "|/usr/bin/lesspipe.sh %s", + "_MP_FORK_LOGFILE_": "", + "SSH_CLIENT": "10.0.2.2 61378 22", + "CVS_RSH": "ssh", + "LOGNAME": "vagrant", + "USER": "vagrant", + "HOME": "/home/vagrant", + "PATH": "/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin", + "REST_API_TOKEN": "**********************************", + "CALLBACK_CONSUMER_PORT": "tcp://127.0.0.1:5557", + "ANSIBLE_CALLBACK_PLUGINS": "/vagrant/ansible-commander/awx/plugins/callback", + "LANG": "en_US.UTF-8", + "HISTCONTROL": "ignoredups", + "TERM": "xterm", + "SHELL": "/bin/bash", + "TZ": "America/New_York", + "_MP_FORK_LOGFORMAT_": "[%(asctime)s: %(levelname)s/%(processName)s] %(message)s", + "SHLVL": "1", + "G_BROKEN_FILENAMES": "1", + "HISTSIZE": "1000", + "CELERY_LOG_FILE": "", + "DJANGO_PROJECT_DIR": "/vagrant/ansible-commander", + "ANSIBLE_HOST_KEY_CHECKING": "False", + "JOB_ID": "2", + "PYTHONPATH": "/vagrant/ansible-commander/awx/lib/site-packages:", + "CELERY_LOADER": "djcelery.loaders.DjangoLoader", + "_MP_FORK_LOGLEVEL_": "10", + "ANSIBLE_NOCOLOR": "1", + "JOB_CALLBACK_DEBUG": "1", + "REST_API_URL": "http://127.0.0.1:8013", + "_": "/usr/bin/nohup", + "SSH_CONNECTION": "10.0.2.2 61378 10.0.2.15 22", + "INVENTORY_HOSTVARS": "True", + "SSH_TTY": "/dev/pts/0", + "CELERY_LOG_LEVEL": "10", + "HOSTNAME": "vagrant-centos64.vagrantup.com", + "INVENTORY_ID": "4", + "PWD": "/home/vagrant", + "CELERY_LOG_REDIRECT": "1", + "DJANGO_SETTINGS_MODULE": "awx.settings.development", + "MAIL": "/var/spool/mail/vagrant", + "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:" + } + }, + { + "id": 9, + "url": "/api/v1/jobs/2/", + "related": { + "job_host_summaries": "/api/v1/jobs/2/job_host_summaries/", + "activity_stream": "/api/v1/jobs/2/activity_stream/", + "job_events": "/api/v1/jobs/2/job_events/", + "job_template": "/api/v1/job_templates/3/", + "inventory": "/api/v1/inventories/4/", + "project": "/api/v1/projects/1/", + "credential": "/api/v1/credentials/8/", + "start": "/api/v1/jobs/2/start/", + "cancel": "/api/v1/jobs/2/cancel/" + }, + "summary_fields": { + "credential": { + "name": "ssh", + "description": "machine creds", + "kind": "ssh", + "cloud": false + }, + "job_template": { + "name": "Hello World", + "description": "" + }, + "project": { + "name": "Examples", + "description": "Ansible example project", + "status": "successful" + }, + "inventory": { + "name": "Rackspace", + "description": "", + "has_active_failures": true, + "total_hosts": 20, + "hosts_with_active_failures": 20, + "total_groups": 3, + "groups_with_active_failures": 3, + "has_inventory_sources": true, + "total_inventory_sources": 1, + "inventory_sources_with_failures": 1 + } + }, + "created": "2014-03-07T23:28:06.999Z", + "modified": "2014-03-07T23:28:16.424Z", + "job_explanation": "Completed successfully", + "status": "successful", + "failed": true, + "job_template": 3, + "job_type": "run", + "type": "scm_sync", + "name": "Examples", + "inventory": 4, + "project": 1, + "playbook": "lamp_simple/site.yml", + "credential": 8, + "cloud_credential": null, + "forks": 0, + "limit": "", + "verbosity": 0, + "extra_vars": "{\n\t\"variable1\": \"some value\",\n\t\"variable2\": \"another value\"\n}", + "job_tags": "", + "launch_type": "manual", + "result_traceback": "", + "passwords_needed_to_start": [], + "job_args": "[\"ssh-agent\", \"sh\", \"-c\", \"ssh-add /tmp/tmpoeaDyc && ansible-playbook -i /vagrant/ansible-commander/awx/plugins/inventory/awxrest.py -u vagrant -e '{\\\"variable1\\\": \\\"some value\\\", \\\"variable2\\\": \\\"another value\\\"}' lamp_simple/site.yml\"]", + "job_cwd": "/vagrant/ansible-commander/awx/projects/_1__examples", + "job_env": { + "CELERY_LOG_REDIRECT_LEVEL": "WARNING", + "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", + "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199", + "LESSOPEN": "|/usr/bin/lesspipe.sh %s", + "_MP_FORK_LOGFILE_": "", + "SSH_CLIENT": "10.0.2.2 61378 22", + "CVS_RSH": "ssh", + "LOGNAME": "vagrant", + "USER": "vagrant", + "HOME": "/home/vagrant", + "PATH": "/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin", + "REST_API_TOKEN": "**********************************", + "CALLBACK_CONSUMER_PORT": "tcp://127.0.0.1:5557", + "ANSIBLE_CALLBACK_PLUGINS": "/vagrant/ansible-commander/awx/plugins/callback", + "LANG": "en_US.UTF-8", + "HISTCONTROL": "ignoredups", + "TERM": "xterm", + "SHELL": "/bin/bash", + "TZ": "America/New_York", + "_MP_FORK_LOGFORMAT_": "[%(asctime)s: %(levelname)s/%(processName)s] %(message)s", + "SHLVL": "1", + "G_BROKEN_FILENAMES": "1", + "HISTSIZE": "1000", + "CELERY_LOG_FILE": "", + "DJANGO_PROJECT_DIR": "/vagrant/ansible-commander", + "ANSIBLE_HOST_KEY_CHECKING": "False", + "JOB_ID": "2", + "PYTHONPATH": "/vagrant/ansible-commander/awx/lib/site-packages:", + "CELERY_LOADER": "djcelery.loaders.DjangoLoader", + "_MP_FORK_LOGLEVEL_": "10", + "ANSIBLE_NOCOLOR": "1", + "JOB_CALLBACK_DEBUG": "1", + "REST_API_URL": "http://127.0.0.1:8013", + "_": "/usr/bin/nohup", + "SSH_CONNECTION": "10.0.2.2 61378 10.0.2.15 22", + "INVENTORY_HOSTVARS": "True", + "SSH_TTY": "/dev/pts/0", + "CELERY_LOG_LEVEL": "10", + "HOSTNAME": "vagrant-centos64.vagrantup.com", + "INVENTORY_ID": "4", + "PWD": "/home/vagrant", + "CELERY_LOG_REDIRECT": "1", + "DJANGO_SETTINGS_MODULE": "awx.settings.development", + "MAIL": "/var/spool/mail/vagrant", + "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:" + } + }, + { + "id": 10, + "url": "/api/v1/jobs/2/", + "related": { + "job_host_summaries": "/api/v1/jobs/2/job_host_summaries/", + "activity_stream": "/api/v1/jobs/2/activity_stream/", + "job_events": "/api/v1/jobs/2/job_events/", + "job_template": "/api/v1/job_templates/3/", + "inventory": "/api/v1/inventories/4/", + "project": "/api/v1/projects/1/", + "credential": "/api/v1/credentials/8/", + "start": "/api/v1/jobs/2/start/", + "cancel": "/api/v1/jobs/2/cancel/" + }, + "summary_fields": { + "credential": { + "name": "ssh", + "description": "machine creds", + "kind": "ssh", + "cloud": false + }, + "job_template": { + "name": "Hello World", + "description": "" + }, + "project": { + "name": "Examples", + "description": "Ansible example project", + "status": "successful" + }, + "inventory": { + "name": "Rackspace", + "description": "", + "has_active_failures": true, + "total_hosts": 20, + "hosts_with_active_failures": 20, + "total_groups": 3, + "groups_with_active_failures": 3, + "has_inventory_sources": true, + "total_inventory_sources": 1, + "inventory_sources_with_failures": 1 + } + }, + "created": "2014-03-07T23:28:06.999Z", + "modified": "2014-03-07T23:28:16.424Z", + "job_template": 3, + "job_type": "run", + "job_explanation": "Ended in a horrible fireball", + "status": "failed", + "failed": true, + "type": "playbook_run", + "name": "Web server restart", + "inventory": 4, + "project": 1, + "playbook": "lamp_simple/site.yml", + "credential": 8, + "cloud_credential": null, + "forks": 0, + "limit": "", + "verbosity": 0, + "extra_vars": "{\n\t\"variable1\": \"some value\",\n\t\"variable2\": \"another value\"\n}", + "job_tags": "", + "launch_type": "manual", + "result_traceback": "", + "passwords_needed_to_start": [], + "job_args": "[\"ssh-agent\", \"sh\", \"-c\", \"ssh-add /tmp/tmpoeaDyc && ansible-playbook -i /vagrant/ansible-commander/awx/plugins/inventory/awxrest.py -u vagrant -e '{\\\"variable1\\\": \\\"some value\\\", \\\"variable2\\\": \\\"another value\\\"}' lamp_simple/site.yml\"]", + "job_cwd": "/vagrant/ansible-commander/awx/projects/_1__examples", + "job_env": { + "CELERY_LOG_REDIRECT_LEVEL": "WARNING", + "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", + "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199", + "LESSOPEN": "|/usr/bin/lesspipe.sh %s", + "_MP_FORK_LOGFILE_": "", + "SSH_CLIENT": "10.0.2.2 61378 22", + "CVS_RSH": "ssh", + "LOGNAME": "vagrant", + "USER": "vagrant", + "HOME": "/home/vagrant", + "PATH": "/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/vagrant/bin", + "REST_API_TOKEN": "**********************************", + "CALLBACK_CONSUMER_PORT": "tcp://127.0.0.1:5557", + "ANSIBLE_CALLBACK_PLUGINS": "/vagrant/ansible-commander/awx/plugins/callback", + "LANG": "en_US.UTF-8", + "HISTCONTROL": "ignoredups", + "TERM": "xterm", + "SHELL": "/bin/bash", + "TZ": "America/New_York", + "_MP_FORK_LOGFORMAT_": "[%(asctime)s: %(levelname)s/%(processName)s] %(message)s", + "SHLVL": "1", + "G_BROKEN_FILENAMES": "1", + "HISTSIZE": "1000", + "CELERY_LOG_FILE": "", + "DJANGO_PROJECT_DIR": "/vagrant/ansible-commander", + "ANSIBLE_HOST_KEY_CHECKING": "False", + "JOB_ID": "2", + "PYTHONPATH": "/vagrant/ansible-commander/awx/lib/site-packages:", + "CELERY_LOADER": "djcelery.loaders.DjangoLoader", + "_MP_FORK_LOGLEVEL_": "10", + "ANSIBLE_NOCOLOR": "1", + "JOB_CALLBACK_DEBUG": "1", + "REST_API_URL": "http://127.0.0.1:8013", + "_": "/usr/bin/nohup", + "SSH_CONNECTION": "10.0.2.2 61378 10.0.2.15 22", + "INVENTORY_HOSTVARS": "True", + "SSH_TTY": "/dev/pts/0", + "CELERY_LOG_LEVEL": "10", + "HOSTNAME": "vagrant-centos64.vagrantup.com", + "INVENTORY_ID": "4", + "PWD": "/home/vagrant", + "CELERY_LOG_REDIRECT": "1", + "DJANGO_SETTINGS_MODULE": "awx.settings.development", + "MAIL": "/var/spool/mail/vagrant", + "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.tbz=01;31:*.tbz2=01;31:*.bz=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:" + } } ] } \ No newline at end of file diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 85079598ad..a2bb8d1609 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -108,6 +108,7 @@ + @@ -120,6 +121,7 @@ + From f153105f79deada646ef559dddcf98ee3984f0a2 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Tue, 25 Mar 2014 10:02:10 -0400 Subject: [PATCH 2/4] Added support for schedule.enabled to Jobs page. --- awx/ui/static/js/controllers/Jobs.js | 20 ++++- awx/ui/static/js/helpers/Jobs.js | 14 +++- awx/ui/static/js/helpers/Schedules.js | 97 ++++++++++++++++--------- awx/ui/static/js/lists/JobTemplates.js | 14 ++-- awx/ui/static/js/lists/Projects.js | 10 +-- awx/ui/static/js/lists/ScheduledJobs.js | 2 +- awx/ui/static/lib/ansible/directives.js | 13 +++- 7 files changed, 120 insertions(+), 50 deletions(-) diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index 675d63b924..a2bc6d7c11 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -11,7 +11,7 @@ 'use strict'; function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBreadCrumbs, LoadScope, RunningJobsList, CompletedJobsList, QueuedJobsList, - ScheduledJobsList, GetChoices, GetBasePath, Wait, DeleteJob) { + ScheduledJobsList, GetChoices, GetBasePath, Wait, DeleteJob, ToggleScheduleEnabled, Find) { ClearScope(); @@ -75,6 +75,15 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea url: GetBasePath('schedules') }); + if (scheduled_scope.removeScheduleToggled) { + scheduled_scope.removeScheduleToggled(); + } + scheduled_scope.removeScheduleToggled = function(e, id) { + //scheduled_scope.search(ScheduledJobsList.iterator); + var schedule = Find({ list: scheduled_scope[ScheduledJobsList.name], key: 'id', val: id}); + schedule.enabled = (schedule.enabled) ? false : true; + }; + completed_scope.deleteJob = function(id) { DeleteJob({ scope: completed_scope, id: id }); }; @@ -86,6 +95,13 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea running_scope.deleteJob = function(id) { DeleteJob({ scope: running_scope, id: id }); }; + + scheduled_scope.toggleSchedule = function(id) { + ToggleScheduleEnabled({ + scope: scheduled_scope, + id: id + }); + }; }); @@ -120,7 +136,7 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea } JobsListController.$inject = ['$scope', '$compile', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'LoadScope', 'RunningJobsList', 'CompletedJobsList', - 'QueuedJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', 'DeleteJob']; + 'QueuedJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', 'DeleteJob', 'ToggleScheduleEnabled', 'Find']; function JobsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, JobForm, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index 7f71276b5d..50851813a5 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -9,7 +9,7 @@ 'use strict'; -angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers']) +angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers', 'SchedulesHelper']) .factory('JobStatusToolTip', [ function () { @@ -326,5 +326,17 @@ function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt){ }); }; +}]) + +.factory('ToggleScheduleEnabled', ['ToggleSchedule', function(ToggleSchedule) { + return function(params) { + var scope = params.scope, + id = params.id; + ToggleSchedule({ + scope: scope, + id: id, + callback: 'ScheduleToggled' + }); + }; }]); diff --git a/awx/ui/static/js/helpers/Schedules.js b/awx/ui/static/js/helpers/Schedules.js index af165b56ce..38eea7f05a 100644 --- a/awx/ui/static/js/helpers/Schedules.js +++ b/awx/ui/static/js/helpers/Schedules.js @@ -107,38 +107,6 @@ angular.module('SchedulesHelper', ['Utilities', 'SchedulesHelper']) }; }]) - /*.factory('ShowDetails', [ function() { - return function(params) { - - var scope = params.scope, - scheduler = params.scheduler, - e = params.e, - rrule; - - if ($(e.target).text() === 'Details') { - if (scheduler.isValid()) { - scope.schedulerIsValid = true; - rrule = scheduler.getRRule(); - scope.occurrence_list = []; - scope.dateChoice = 'utc'; - rrule.all(function(date, i){ - if (i < 10) { - scope.occurrence_list.push({ utc: date.toUTCString(), local: date.toString() }); - return true; - } - return false; - }); - scope.rrule_nlp_description = rrule.toText().replace(/^RRule error.*$/,'Natural language description not available'); - scope.rrule = scheduler.getValue().rrule; - } - else { - scope.schedulerIsValid = false; - $('#scheduler-tabs a:first').tab('show'); - } - } - }; - }])*/ - .factory('EditSchedule', ['SchedulerInit', 'ShowSchedulerModal', 'Wait', 'Rest', function(SchedulerInit, ShowSchedulerModal, Wait, Rest) { return function(params) { @@ -246,4 +214,67 @@ angular.module('SchedulesHelper', ['Utilities', 'SchedulesHelper']) } }); }; - }]); \ No newline at end of file + }]) + + /** + * Flip a schedule's enable flag + * + * ToggleSchedule({ + * scope: scope, + * id: schedule.id to update + * callback: scope.$emit label to call when update completes + * }); + * + */ + .factory('ToggleSchedule', ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', function(Wait, GetBasePath, ProcessErrors, Rest) { + return function(params) { + var scope = params.scope, + id = params.id, + callback = params.callback, + url = GetBasePath('schedules') + id +'/'; + + // Perform the update + if (scope.removeScheduleFound) { + scope.removeScheduleFound(); + } + scope.removeScheduleFound = scope.$on('removeScheduleFound', function(e, data) { + data.enabled = (data.enabled) ? false : true; + Rest.put(data) + .success( function() { + if (callback) { + scope.$emit(callback, id); + } + }) + .error( function() { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status }); + }); + }); + + // Get the existing record + Rest.setUrl(url); + Rest.get() + .success(function(){ + + }) + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); + }); + }; + }]); + + + + + + + + + + + + + + + diff --git a/awx/ui/static/js/lists/JobTemplates.js b/awx/ui/static/js/lists/JobTemplates.js index 903a828ce2..f803b8ae22 100644 --- a/awx/ui/static/js/lists/JobTemplates.js +++ b/awx/ui/static/js/lists/JobTemplates.js @@ -48,13 +48,6 @@ angular.module('JobTemplatesListDefinition', []) }, fieldActions: { - edit: { - label: 'Edit', - ngClick: "editJobTemplate(job_template.id)", - awToolTip: 'Edit template', - "class": 'btn-default btn-xs', - dataPlacement: 'top' - }, submit: { label: 'Launch', mode: 'all', @@ -69,6 +62,13 @@ angular.module('JobTemplatesListDefinition', []) awToolTip: 'Schedule future job template runs', dataPlacement: 'top' }, + edit: { + label: 'Edit', + ngClick: "editJobTemplate(job_template.id)", + awToolTip: 'Edit template', + "class": 'btn-default btn-xs', + dataPlacement: 'top' + }, "delete": { label: 'Delete', ngClick: "deleteJobTemplate(job_template.id, job_template.name)", diff --git a/awx/ui/static/js/lists/Projects.js b/awx/ui/static/js/lists/Projects.js index 0e6fe77192..7309dbd2fd 100644 --- a/awx/ui/static/js/lists/Projects.js +++ b/awx/ui/static/js/lists/Projects.js @@ -92,11 +92,6 @@ angular.module('ProjectsListDefinition', []) }, fieldActions: { - edit: { - ngClick: "editProject(project.id)", - awToolTip: 'Edit the project', - dataPlacement: 'top' - }, scm_update: { ngClick: 'SCMUpdate(project.id)', awToolTip: "{{ project.scm_update_tooltip }}", @@ -115,6 +110,11 @@ angular.module('ProjectsListDefinition', []) awToolTip: 'Schedule future SCM updates', dataPlacement: 'top' }, + edit: { + ngClick: "editProject(project.id)", + awToolTip: 'Edit the project', + dataPlacement: 'top' + }, "delete": { ngClick: "deleteProject(project.id, project.name)", awToolTip: 'Delete the project', diff --git a/awx/ui/static/js/lists/ScheduledJobs.js b/awx/ui/static/js/lists/ScheduledJobs.js index 2d7332403d..3ec9bd9fae 100644 --- a/awx/ui/static/js/lists/ScheduledJobs.js +++ b/awx/ui/static/js/lists/ScheduledJobs.js @@ -60,7 +60,7 @@ angular.module('ScheduledJobsDefinition', []) awToolTip: "{{ scheduled_job.play_tip }}", dataTipWatch: "scheduled_job.play_tip", iconClass: "{{ 'fa icon-schedule-enabled-' + scheduled_job.enabled }}", - dataPlacement: 'top', + dataPlacement: 'top' }, "edit": { mode: "all", diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index 3884ceddec..1776ba7f80 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -292,9 +292,20 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job content: attrs.awPopOver, trigger: trigger, html: true, container: container }); $(element).click(function() { var self = $(this); + try { + self.tooltip('hide'); + } + catch(e) { + // ignore + } $('.help-link, .help-link-white').each( function() { if (self.attr('id') !== $(this).attr('id')) { - $(this).popover('hide'); + try { + $(this).popover('hide'); + } + catch(e) { + // ignore + } } }); $('.popover').each(function() { From 54f30624457cf92df3d0645b1465cda100f22814 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Tue, 25 Mar 2014 10:09:00 -0400 Subject: [PATCH 3/4] Set next_run to be the key field, sorted in desc order. Moved name to appear before type. --- awx/ui/static/js/lists/ScheduledJobs.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/awx/ui/static/js/lists/ScheduledJobs.js b/awx/ui/static/js/lists/ScheduledJobs.js index 3ec9bd9fae..077dac295b 100644 --- a/awx/ui/static/js/lists/ScheduledJobs.js +++ b/awx/ui/static/js/lists/ScheduledJobs.js @@ -23,7 +23,10 @@ angular.module('ScheduledJobsDefinition', []) next_run: { label: 'Next Run', link: false, - columnClass: "col-md-2" + searchable: false, + columnClass: "col-md-2", + key: true, + desc: true }, dtend: { label: 'Ends On', @@ -31,16 +34,16 @@ angular.module('ScheduledJobsDefinition', []) filter: "date:'MM/dd/yy HH:mm:ss'", columnClass: "col-md-2 hidden-xs" }, - type: { - label: 'Type', - link: false, - columnClass: "col-md-2 hidden-sm hidden-xs" - }, template_name: { label: 'Name', columnClass: "col-md-4 col-xs-5", sourceModel: "template", sourceField: "name" + }, + type: { + label: 'Type', + link: false, + columnClass: "col-md-2 hidden-sm hidden-xs" } }, From 166cf61dc10d0a3672d521b9da1c0297aef78877 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Tue, 25 Mar 2014 10:53:23 -0400 Subject: [PATCH 4/4] Added schedule.enabled support to job_template.schedules and projects.schedules. --- awx/ui/static/js/controllers/Schedules.js | 45 ++++++++++++++----- awx/ui/static/js/lists/Schedules.js | 18 ++++++-- awx/ui/static/sample/data/schedules/data.json | 3 ++ .../sample/data/schedules/inventory/data.json | 3 ++ .../sample/data/schedules/projects/data.json | 3 ++ 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/awx/ui/static/js/controllers/Schedules.js b/awx/ui/static/js/controllers/Schedules.js index 0177f46d6b..4334dbc133 100644 --- a/awx/ui/static/js/controllers/Schedules.js +++ b/awx/ui/static/js/controllers/Schedules.js @@ -11,19 +11,32 @@ 'use strict'; function ScheduleEdit($scope, $compile, $location, $routeParams, SchedulesList, GenerateList, Rest, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, -GetBasePath, LookUpInit, Wait, SchedulerInit, Breadcrumbs, SearchInit, PaginateInit, PageRangeSetup, EditSchedule, AddSchedule, Find) { +GetBasePath, LookUpInit, Wait, SchedulerInit, Breadcrumbs, SearchInit, PaginateInit, PageRangeSetup, EditSchedule, AddSchedule, Find, ToggleSchedule) { ClearScope(); - var base, e, id, job_template, url; + var base, e, id, parentObject, url; base = $location.path().replace(/^\//, '').split('/')[0]; - - $scope.$on('job_template_ready', function() { + + if ($scope.removePostRefresh) { + $scope.removePostRefresh(); + } + $scope.removePostRefresh = $scope.$on('PostRefresh', function() { + var list = $scope.schedules; + list.forEach(function(element, idx) { + list[idx].play_tip = (element.enabled) ? 'Schedule is Active. Click to temporarily stop.' : 'Schedule is temporarily stopped. Click to activate.'; + }); + }); + + if ($scope.removeParentLoaded) { + $scope.removeParentLoaded(); + } + $scope.removeScheduledLoaded = $scope.$on('ParentLoaded', function() { // Add breadcrumbs LoadBreadCrumbs({ path: $location.path().replace(/\/schedules$/,''), - title: job_template.name + title: parentObject.name }); e = angular.element(document.getElementById('breadcrumbs')); e.html(Breadcrumbs({ list: SchedulesList, mode: 'edit' })); @@ -112,16 +125,28 @@ GetBasePath, LookUpInit, Wait, SchedulerInit, Breadcrumbs, SearchInit, PaginateI AddSchedule({ scope: $scope, schedule: schedule, url: url }); }; + if ($scope.removeScheduleToggled) { + $scope.removeScheduleToggled(); + } + $scope.removeScheduleToggled = function() { + $scope.search(SchedulesList.iterator); + }; - //scheduler = SchedulerInit({ scope: $scope }); - //scheduler.inject('scheduler-target', true); + $scope.toggleSchedule = function(id) { + ToggleSchedule({ + scope: $scope, + id: id, + callback: 'ScheduleToggled' + }); + }; + // Load the parent object id = $routeParams.id; Rest.setUrl(GetBasePath(base) + id); Rest.get() .success(function(data) { - job_template = data; - $scope.$emit('job_template_ready'); + parentObject = data; + $scope.$emit('ParentLoaded'); }) .error(function(status) { ProcessErrors($scope, null, status, null, { hdr: 'Error!', @@ -131,5 +156,5 @@ GetBasePath, LookUpInit, Wait, SchedulerInit, Breadcrumbs, SearchInit, PaginateI ScheduleEdit.$inject = ['$scope', '$compile', '$location', '$routeParams', 'SchedulesList', 'GenerateList', 'Rest', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'LookUpInit', 'Wait', 'SchedulerInit', 'Breadcrumbs', 'SearchInit', 'PaginateInit', 'PageRangeSetup', 'EditSchedule', 'AddSchedule', -'Find' +'Find', 'ToggleSchedule' ]; \ No newline at end of file diff --git a/awx/ui/static/js/lists/Schedules.js b/awx/ui/static/js/lists/Schedules.js index 5f490b2070..328b8dc80c 100644 --- a/awx/ui/static/js/lists/Schedules.js +++ b/awx/ui/static/js/lists/Schedules.js @@ -22,13 +22,16 @@ angular.module('SchedulesListDefinition', []) fields: { name: { key: true, - label: 'Name' + label: 'Name', + ngClick: "editSchedule(schedule.id)" }, dtstart: { - label: 'Start' + label: 'Start', + searchable: false }, dtend: { - label: 'End' + label: 'End', + searchable: false } }, @@ -46,6 +49,14 @@ angular.module('SchedulesListDefinition', []) }, fieldActions: { + "play": { + mode: "all", + ngClick: "toggleSchedule(schedule.id)", + awToolTip: "{{ schedule.play_tip }}", + dataTipWatch: "schedule.play_tip", + iconClass: "{{ 'fa icon-schedule-enabled-' + schedule.enabled }}", + dataPlacement: "top" + }, edit: { label: 'Edit', ngClick: "editSchedule(schedule.id)", @@ -53,7 +64,6 @@ angular.module('SchedulesListDefinition', []) awToolTip: 'Edit schedule', dataPlacement: 'top' }, - "delete": { label: 'Delete', ngClick: "deleteSchedule(schedule.id)", diff --git a/awx/ui/static/sample/data/schedules/data.json b/awx/ui/static/sample/data/schedules/data.json index 9d95725650..18d450b748 100644 --- a/awx/ui/static/sample/data/schedules/data.json +++ b/awx/ui/static/sample/data/schedules/data.json @@ -10,6 +10,7 @@ "project": null, "job_class": "ansible:playbook", "name": "Hourly", + "enabled": true, "dtstart": "2014-03-10T17:00:00.000Z" , "dtend": null, "rrule": "FREQ=HOURLY;DTSTART=20140310T170000Z;INTERVAL=1" @@ -21,6 +22,7 @@ "project": null, "job_class": "ansible:playbook", "name": "Weekly", + "enabled": true, "dtstart": "2014-03-17T13:00:00.000Z", "dtend": null, "rrule": "FREQ=WEEKLY;DTSTART=20140317T130000Z;INTERVAL=1;COUNT=10;BYDAY=MO" @@ -32,6 +34,7 @@ "project": null, "job_class": "ansible:playbook", "name": "Monthly", + "enabled": false, "dtstart": "2014-04-06T01:00:00.000Z", "dtend": "2020-03-01T01:00:00.000Z", "rrule": "FREQ=MONTHLY;DTSTART=20140406T010000Z;INTERVAL=1;UNTIL=20200301T010000Z;BYMONTHDAY=1" diff --git a/awx/ui/static/sample/data/schedules/inventory/data.json b/awx/ui/static/sample/data/schedules/inventory/data.json index e377db960f..f13b6353d6 100644 --- a/awx/ui/static/sample/data/schedules/inventory/data.json +++ b/awx/ui/static/sample/data/schedules/inventory/data.json @@ -11,6 +11,7 @@ "job_class": "inventory:sync", "job_type": "inventory_sync", "name": "Hourly", + "enabled": true, "dtstart": "2014-03-10T17:00:00.000Z" , "dtend": null, "rrule": "FREQ=HOURLY;DTSTART=20140310T170000Z;INTERVAL=1" @@ -23,6 +24,7 @@ "job_class": "inventory:sync", "job_type": "inventory_sync", "name": "Weekly", + "enabled": true, "dtstart": "2014-03-17T13:00:00.000Z", "dtend": null, "rrule": "FREQ=WEEKLY;DTSTART=20140317T130000Z;INTERVAL=1;COUNT=10;BYDAY=MO" @@ -35,6 +37,7 @@ "job_class": "inventory:sync", "job_type": "inventory_sync", "name": "Monthly", + "enabled": false, "dtstart": "2014-04-06T01:00:00.000Z", "dtend": "2020-03-01T01:00:00.000Z", "rrule": "FREQ=MONTHLY;DTSTART=20140406T010000Z;INTERVAL=1;UNTIL=20200301T010000Z;BYMONTHDAY=1" diff --git a/awx/ui/static/sample/data/schedules/projects/data.json b/awx/ui/static/sample/data/schedules/projects/data.json index c9c7031ab2..3229635198 100644 --- a/awx/ui/static/sample/data/schedules/projects/data.json +++ b/awx/ui/static/sample/data/schedules/projects/data.json @@ -11,6 +11,7 @@ "job_class": "project:sync", "job_type": "project_sync", "name": "Hourly", + "enabled": false, "dtstart": "2014-03-10T17:00:00.000Z" , "dtend": null, "rrule": "FREQ=HOURLY;DTSTART=20140310T170000Z;INTERVAL=1" @@ -23,6 +24,7 @@ "job_class": "project:sync", "job_type": "project_sync", "name": "Weekly", + "enabled": true, "dtstart": "2014-03-17T13:00:00.000Z", "dtend": null, "rrule": "FREQ=WEEKLY;DTSTART=20140317T130000Z;INTERVAL=1;COUNT=10;BYDAY=MO" @@ -35,6 +37,7 @@ "job_class": "project:sync", "job_type": "project_sync", "name": "Monthly", + "enabled": true, "dtstart": "2014-04-06T01:00:00.000Z", "dtend": "2020-03-01T01:00:00.000Z", "rrule": "FREQ=MONTHLY;DTSTART=20140406T010000Z;INTERVAL=1;UNTIL=20200301T010000Z;BYMONTHDAY=1"