diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 3fd08a0b21..628cef6f76 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -113,18 +113,18 @@ angular.module('ansible', [ controller: 'JobsListController' }). - when('/jobs/:id', { + /* when('/jobs/:id', { templateUrl: urlPrefix + 'partials/jobs.html', controller: 'JobsEdit' - }). + }). */ when('/jobs/:id/job_events', { - templateUrl: urlPrefix + 'partials/jobs.html', + templateUrl: urlPrefix + 'partials/job_events.html', controller: 'JobEventsList' }). when('/jobs/:id/job_host_summaries', { - templateUrl: urlPrefix + 'partials/jobs.html', + templateUrl: urlPrefix + 'partials/job_host_summaries.html', controller: 'JobHostSummaryList' }). diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 5145bc42e1..6429cff4bf 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -189,7 +189,7 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest Prompt({ hdr: 'Delete', - body: 'Are you sure you want to delete ' + name + '?', + body: '
Are you sure you want to delete ' + name + '?
', action: action }); }; diff --git a/awx/ui/static/js/controllers/Jobs.js b/awx/ui/static/js/controllers/Jobs.js index 77902099c6..54b5282388 100644 --- a/awx/ui/static/js/controllers/Jobs.js +++ b/awx/ui/static/js/controllers/Jobs.js @@ -11,14 +11,14 @@ 'use strict'; function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBreadCrumbs, LoadScope, RunningJobsList, CompletedJobsList, QueuedJobsList, - ScheduledJobsList, GetChoices, GetBasePath, Wait, DeleteJob, Find, DeleteSchedule, ToggleSchedule, RelaunchInventory, RelaunchPlaybook, RelaunchSCM, + ScheduledJobsList, GetChoices, GetBasePath, Wait, Find, JobsControllerInit, DeleteSchedule, ToggleSchedule, LoadDialogPartial, ScheduledJobEdit) { ClearScope(); var e, completed_scope, running_scope, queued_scope, scheduled_scope, - choicesCount = 0, listsCount = 0, relaunch, getTypeId; + choicesCount = 0; LoadBreadCrumbs(); @@ -27,47 +27,7 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea e.html(Breadcrumbs({ list: { editTitle: 'Jobs' } , mode: 'edit' })); $compile(e)($scope); - relaunch = function(params) { - var scope = params.scope, - id = params.id, - type = params.type, - name = params.name; - if (type === 'inventory_sync') { - RelaunchInventory({ scope: scope, id: id}); - } - else if (type === 'playbook_run') { - RelaunchPlaybook({ scope: scope, id: id, name: name }); - } - else if (type === 'scm_sync') { - RelaunchSCM({ }); - } - }; - getTypeId = function(job) { - var type_id; - if (job.type === 'inventory_sync') { - type_id = job.inventory_source; - } - else if (job.type === 'scm_sync') { - type_id = job.poject; - } - else if (job.type === 'playbook_run') { - type_id = job.id; - } - return type_id; - }; - - // After all the lists are loaded - if ($scope.removeListLoaded) { - $scope.removeListLoaded(); - } - $scope.removeListLoaded = $scope.$on('listLoaded', function() { - listsCount++; - if (listsCount === 3) { - Wait('stop'); - } - }); - // After all choices are ready, load up the lists and populate the page if ($scope.removeBuildJobsList) { $scope.removeBuildJobsList(); @@ -113,18 +73,6 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea scheduled_scope.search(ScheduledJobsList.iterator); }); - 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 }); - }; - scheduled_scope.toggleSchedule = function(id) { ToggleSchedule({ scope: scheduled_scope, @@ -144,25 +92,6 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea scheduled_scope.editSchedule = function(id) { ScheduledJobEdit({ scope: scheduled_scope, id: id }); }; - - completed_scope.relaunch = function(id) { - var job = Find({ list: completed_scope.completed_jobs, key: 'id', val: id }), - type_id = getTypeId(job); - relaunch({ scope: completed_scope, id: type_id, type: job.type, name: job.name }); - }; - - running_scope.relaunch = function(id) { - var job = Find({ list: running_scope.running_jobs, key: 'id', val: id }), - type_id = getTypeId(job); - relaunch({ scope: running_scope, id: type_id, type: job.type, name: job.name }); - }; - - queued_scope.relaunch = function(id) { - var job = Find({ list: queued_scope.queued_jobs, key: 'id', val: id }), - type_id = getTypeId(job); - relaunch({ scope: queued_scope, id: type_id, type: job.type, name: job.name }); - }; - }); if ($scope.removeChoicesReady) { @@ -198,11 +127,17 @@ function JobsListController ($scope, $compile, ClearScope, Breadcrumbs, LoadBrea element_id: 'schedule-dialog-target', callback: 'choicesReady' }); + + $scope.refreshJobs = function() { + queued_scope.search('queued_job'); + running_scope.search('running_job'); + completed_scope.search('completed_job'); + }; } JobsListController.$inject = ['$scope', '$compile', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'LoadScope', 'RunningJobsList', 'CompletedJobsList', - 'QueuedJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', 'DeleteJob', 'Find', 'DeleteSchedule', 'ToggleSchedule', 'RelaunchInventory', - 'RelaunchPlaybook', 'RelaunchSCM', 'LoadDialogPartial', 'ScheduledJobEdit']; + 'QueuedJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', 'Find', 'JobsControllerInit', + 'DeleteSchedule', 'ToggleSchedule', 'LoadDialogPartial', 'ScheduledJobEdit']; 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/Groups.js b/awx/ui/static/js/helpers/Groups.js index a57ad173b4..7f5c91ae6a 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -37,8 +37,7 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G scope.$emit('sourceTypeOptionsReady'); }) .error(function (data, status) { - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status }); }); @@ -651,7 +650,7 @@ function(SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize) { if (ww > 1199) { // desktop x = 675; - y = (780 > wh) ? wh - 15 : 780; + y = (800 > wh) ? wh - 15 : 800; maxrows = 18; } else if (ww <= 1199 && ww >= 768) { x = 550; @@ -740,8 +739,8 @@ function(SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize) { if (sources_scope.codeMirror) { sources_scope.codeMirror.destroy(); } - $('#group-modal-dialog').dialog('destroy'); $('#group-modal-dialog').hide(); + $('#group-modal-dialog').dialog('destroy'); $('#properties-tab').empty(); $('#sources-tab').empty(); $('#schedules-list').empty(); @@ -983,9 +982,12 @@ function(SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize) { if (modal_scope.searchCleanUp) { modal_scope.searchCleanup(); } - - $('#group-modal-dialog').dialog('close'); - + try { + $('#group-modal-dialog').dialog('close'); + } + catch(e) { + // ignore + } // Change the selected group if (groups_reload && parent_scope.selected_tree_id !== tree_id) { parent_scope.showHosts(tree_id, group_id, false); @@ -1058,7 +1060,7 @@ function(SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize) { overwrite: sources_scope.overwrite, overwrite_vars: sources_scope.overwrite_vars, update_on_launch: sources_scope.update_on_launch, - update_cache_timeout: sources_scope.update_cache_timeout + update_cache_timeout: (sources_scope.update_cache_timeout || 0) }; // Create a string out of selected list of regions @@ -1165,7 +1167,6 @@ function(SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize) { } }) .error(function (data, status) { - Wait('stop'); ProcessErrors(properties_scope, data, status, GroupForm, { hdr: 'Error!', msg: 'Failed to create group: ' + group_id + '. POST status: ' + status }); @@ -1243,7 +1244,7 @@ function(SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize) { Prompt({ hdr: 'Delete Group', - body: '

Are you sure you want to delete group ' + node.name + '?

', + body: '
Are you sure you want to delete group ' + node.name + '?
', action: action_to_take, 'class': 'btn-danger' }); diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index fd5367f44d..3080faf7b3 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -8,7 +8,7 @@ 'use strict'; angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition', - 'LookUpHelper', 'JobSubmissionHelper' ]) + 'LookUpHelper', 'JobSubmissionHelper', 'JobTemplateFormDefinition' ]) .factory('LaunchJob', ['Rest', 'Wait', 'ProcessErrors', function(Rest, Wait, ProcessErrors) { return function(params) { @@ -48,7 +48,7 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { LookUpInit({ url: GetBasePath('credentials') + '?kind=ssh', scope: scope, - form: JobTemplateForm, + form: JobTemplateForm(), current_item: null, list: CredentialList, field: 'credential', @@ -68,7 +68,7 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { callback = params.callback || 'PasswordsAccepted', password, form = CredentialForm, - html, + html = "", acceptedPasswords = {}, scope = parent_scope.$new(); @@ -80,14 +80,12 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { password = passwords.pop(); // Prompt for password - html += "
\n"; + html += "\n"; field = form.fields[password]; fld = password; scope[fld] = ''; html += "
\n"; - html += "\n"; - html += "
\n"; + html += "\n"; html += "A value is required!\n"; html += "\n"; html += "
\n"; - html += "
\n"; // Add the related confirm field if (field.associated) { @@ -108,9 +105,7 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { field = form.fields[field.associated]; scope[fld] = ''; html += "
\n"; - html += "\n"; - html += "
\n"; + html += "\n"; html += "Must match Password value\n" : ""; html += "\n"; html += "
\n"; - html += "
\n"; } html += "
\n"; $('#password-body').empty().html(html); @@ -139,8 +133,9 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { } scope.passwordAccept = function() { + $('#password-modal').modal('hide'); acceptedPasswords[password] = scope[password]; - if (password.length > 0) { + if (passwords.length > 0) { promptPassword(); } else { @@ -149,9 +144,12 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { }; scope.passwordCancel = function() { - Alert('Missing Password', 'Required password(s) not provided. The request will not be submitted.', 'alert-info'); + $('#password-modal').modal('hide'); + Alert('Missing Password', 'Required password(s) not provided. Your request will not be submitted.', 'alert-info'); parent_scope.$emit('PasswordsCanceled'); }; + + promptPassword(); }; }]) @@ -178,7 +176,7 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { new_job_id = data.id; launch_url = data.related.start; if (data.passwords_needed_to_start.length > 0) { - scope.$emit('PromptForPasswords'); + scope.$emit('PromptForPasswords', data.passwords_needed_to_start); } else { scope.$emit('StartPlaybookRun', {}); } @@ -293,8 +291,13 @@ function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) { // Refresh the project list after update request submitted Wait('stop'); Alert('Update Started', 'The request to start the SCM update process was submitted. ' + - 'To monitor the update status, refresh the page by clicking the Refresh button.', 'alert-info'); - scope.refresh(); + 'To monitor the update status, refresh the page by clicking the button.', 'alert-info'); + if (scope.refreshJobs) { + scope.refreshJobs(); + } + else if (scope.refresh) { + scope.refresh(); + } }); if (scope.removePromptForPasswords) { diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index 9daafff92e..237e4bd564 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -10,7 +10,98 @@ 'use strict'; angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers', - 'JobSubmissionHelper', 'SchedulesHelper']) + 'JobSubmissionHelper', 'SchedulesHelper', 'LogViewerHelper']) + +/** + * JobsControllerInit({ scope: $scope }); + * + * Initialize calling scope with all the bits required to support a jobs list + * + */ +.factory('JobsControllerInit', ['$location', 'Find', 'DeleteJob', 'RelaunchJob', 'LogViewer', + function($location, Find, DeleteJob, RelaunchJob, LogViewer) { + return function(params) { + var scope = params.scope, + parent_scope = params.parent_scope; + + scope.deleteJob = function(id) { + DeleteJob({ scope: scope, id: id }); + }; + + scope.relaunchJob = function(id) { + var list, job, typeId; + if (scope.completed_jobs) { + list = scope.completed_jobs; + } + else if (scope.running_jobs) { + list = scope.running_jobs; + } + else if (scope.queued_jobs) { + list = scope.queued_jobs; + } + job = Find({ list: list, key: 'id', val: id }); + if (job.type === 'inventory_update') { + typeId = job.inventory_source; + } + else if (job.type === 'project_update') { + typeId = job.project; + } + else if (job.type === 'job') { + typeId = job.id; + } + RelaunchJob({ scope: scope, id: typeId, type: job.type, name: job.name }); + }; + + scope.refreshJobs = function() { + parent_scope.refreshJobs(); + }; + + scope.viewJobLog = function(id, url) { + var list, job; + if (url) { + $location.path(url); + } + else { + if (scope.completed_jobs) { + list = scope.completed_jobs; + } + else if (scope.running_jobs) { + list = scope.running_jobs; + } + else if (scope.queued_jobs) { + list = scope.queued_jobs; + } + job = Find({ list: list, key: 'id', val: id }); + LogViewer({ + scope: scope, + url: job.url, + status_icon: 'icon-job-' + job.status + }); + } + }; + }; + } +]) + +.factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', + function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM) { + return function(params) { + var scope = params.scope, + id = params.id, + type = params.type, + name = params.name; + if (type === 'inventory_update') { + RelaunchInventory({ scope: scope, id: id}); + } + else if (type === 'job') { + RelaunchPlaybook({ scope: scope, id: id, name: name }); + } + else if (type === 'project_update') { + RelaunchSCM({ scope: scope, id: id }); + } + }; + } +]) .factory('JobStatusToolTip', [ function () { @@ -170,8 +261,8 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job * Called from JobsList controller to load each section or list on the page * */ -.factory('LoadScope', ['SearchInit', 'PaginateInit', 'GenerateList', 'PageRangeSetup', 'ProcessErrors', 'Rest', - function(SearchInit, PaginateInit, GenerateList) { +.factory('LoadScope', ['SearchInit', 'PaginateInit', 'GenerateList', 'JobsControllerInit', 'Rest', + function(SearchInit, PaginateInit, GenerateList, JobsControllerInit, Rest) { return function(params) { var parent_scope = params.parent_scope, scope = params.scope, @@ -199,7 +290,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job scope: scope, list: list, url: url, - pageSize: 10 + pageSize: 5 }); scope.iterator = list.iterator; @@ -208,6 +299,8 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job scope.removePostRefresh(); } scope.$on('PostRefresh', function(){ + + JobsControllerInit({ scope: scope, parent_scope: parent_scope }); scope[list.name].forEach(function(item, item_idx) { var fld, field, @@ -231,6 +324,20 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job } return true; }); + //Set the name link + if (item.type === "inventory_update") { + Rest.setUrl(item.related.inventory_source); + Rest.get() + .success(function(data) { + itm.nameHref = "/inventories/" + data.inventory; + }); + } + else if (item.type === "project_update") { + itm.nameHref = "/projects/" + item.project; + } + else if (item.type === "job") { + itm.nameHref = ""; + } if (list.name === 'completed_jobs' || list.name === 'running_jobs') { itm.status_tip = itm.status_label + '. Click for details.'; @@ -288,7 +395,7 @@ function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt){ action_label = 'cancel'; hdr = 'Cancel Job'; } else { - url = GetBasePath('jobs') + id + '/'; + url = job.related.cancel; //GetBasePath('unified_jobs') + id + '/'; action_label = 'delete'; hdr = 'Delete Job'; } @@ -368,7 +475,7 @@ function(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) { return function(params) { var scope = params.scope, id = params.id; - ProjectUpdate({ scope: scope, id: id }); + ProjectUpdate({ scope: scope, project_id: id }); }; }]) diff --git a/awx/ui/static/js/helpers/LogViewer.js b/awx/ui/static/js/helpers/LogViewer.js index 44e5d8d7a8..56b8404eef 100644 --- a/awx/ui/static/js/helpers/LogViewer.js +++ b/awx/ui/static/js/helpers/LogViewer.js @@ -7,12 +7,12 @@ 'use strict'; -angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator']) +angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', 'VariablesHelper']) .factory('LogViewer', ['$compile', 'CreateDialog', 'GetJob', 'Wait', 'GenerateForm', 'LogViewerStatusForm', 'AddTable', 'AddTextarea', - 'LogViewerOptionsForm', 'EnvTable', 'GetBasePath', 'LookUpName', 'Empty', + 'LogViewerOptionsForm', 'EnvTable', 'GetBasePath', 'LookUpName', 'Empty', 'AddPreFormattedText', 'ParseVariableString', function($compile, CreateDialog, GetJob, Wait, GenerateForm, LogViewerStatusForm, AddTable, AddTextarea, LogViewerOptionsForm, EnvTable, - GetBasePath, LookUpName, Empty) { + GetBasePath, LookUpName, Empty, AddPreFormattedText, ParseVariableString) { return function(params) { var parent_scope = params.scope, url = params.url, @@ -41,21 +41,19 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator']) AddTable({ scope: scope, form: LogViewerOptionsForm, id: 'options-form-container', status_icon: status_icon }); if (data.result_stdout) { - AddTextarea({ - container_id: 'stdout-form-container', - val: data.result_stdout, - fld_id: 'stdout-textarea' + AddPreFormattedText({ + id: 'stdout-form-container', + val: data.result_stdout }); } else { - $('#logview-tabs li:eq(2)').hide(); + $('#logview-tabs li:eq(1)').hide(); } if (data.result_traceback) { - AddTextarea({ - container_id: 'traceback-form-container', - val: data.result_traceback, - fld_id: 'traceback-textarea' + AddPreFormattedText({ + id: 'traceback-form-container', + val: data.result_traceback }); } else { @@ -68,6 +66,28 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator']) vars: data.job_env }); }*/ + + if (data.extra_vars) { + AddTextarea({ + container_id: 'variables-container', + fld_id: 'variables', + val: ParseVariableString(data.extra_vars) + }); + } + else { + $('#logview-tabs li:eq(4)').hide(); + } + + if (data.source_vars) { + AddTextarea({ + container_id: 'source-container', + fld_id: 'source-variables', + val: ParseVariableString(data.source_vars) + }); + } + else { + $('#logview-tabs li:eq(5)').hide(); + } if (!Empty(scope.credential)) { LookUpName({ @@ -81,7 +101,7 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator']) LookUpName({ scope: scope, scope_var: 'inventory', - url: GetBasePath('inventories') + scope.inventory + '/' + url: GetBasePath('inventory') + scope.inventory + '/' }); } @@ -115,8 +135,8 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator']) rows = Math.floor((h - u) / 20); rows -= 3; rows = (rows < 6) ? 6 : rows; - $('#stdout-textarea').attr({ rows: rows }); - $('#traceback-textarea').attr({ rows: rows }); + $('#logviewer-modal-dialog #variables').attr({ rows: rows }); + $('#logviewer-modal-dialog #source-variables').attr({ rows: rows }); }; elem = angular.element(document.getElementById('logviewer-modal-dialog')); @@ -237,12 +257,22 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator']) fld_id = params.fld_id, html; html = "
\n" + - "" + + "" + "
\n"; $('#' + container_id).empty().html(html); }; }]) + .factory('AddPreFormattedText', [function() { + return function(params) { + var id = params.id, + val = params.val, + html; + html = "
" + val + "
\n"; + $('#' + id).empty().html(html); + }; + }]) + .factory('EnvTable', [ function() { return function(params) { var id = params.id, diff --git a/awx/ui/static/js/helpers/Variables.js b/awx/ui/static/js/helpers/Variables.js index da6f3a6f04..c01d768fce 100644 --- a/awx/ui/static/js/helpers/Variables.js +++ b/awx/ui/static/js/helpers/Variables.js @@ -3,8 +3,6 @@ * * VariablesHelper * - * Show the CodeMirror variable editor and allow - * toggle between JSON and YAML * */ @@ -35,6 +33,7 @@ angular.module('VariablesHelper', ['Utilities']) $log.info('Attempt to parse extra_vars as JSON failed. Attempting to parse as YAML'); try { json_obj = jsyaml.safeLoad(variables); + json_obj = SortVariables(json_obj); result = jsyaml.safeDump(json_obj); } catch(e2) { diff --git a/awx/ui/static/js/lists/CompletedJobs.js b/awx/ui/static/js/lists/CompletedJobs.js index e39e6efddb..294f8f419b 100644 --- a/awx/ui/static/js/lists/CompletedJobs.js +++ b/awx/ui/static/js/lists/CompletedJobs.js @@ -22,7 +22,7 @@ angular.module('CompletedJobsDefinition', []) fields: { id: { label: 'Job ID', - linkTo: '/#/jobs/{{ completed_job.id }}', + ngClick:"viewJobLog(completed_job.id)", key: true, desc: true, searchType: 'int', @@ -30,13 +30,15 @@ angular.module('CompletedJobsDefinition', []) }, status: { label: 'Status', + columnClass: 'col-md-2 col-sm-2 col-xs-2', awToolTip: "{{ completed_job.status_tip }}", awTipPlacement: "top", dataTitle: "{{ completed_job.status_popover_title }}", icon: 'icon-job-{{ completed_job.status }}', iconOnly: true, - awPopOver: "{{ completed_job.status_popover }}", - dataPlacement: 'right', + ngClick:"viewJobLog(completed_job.id)", + /*awPopOver: "{{ completed_job.status_popover }}", + dataPlacement: 'right',*/ searchType: 'select', searchOptions: [ { name: "Success", value: "successful" }, @@ -50,8 +52,8 @@ angular.module('CompletedJobsDefinition', []) searchType: 'int', searchOnly: true }, - modified: { - label: 'Completed On', + finished: { + label: 'Finished On', link: false, searchable: false, filter: "date:'MM/dd/yy HH:mm:ss'", @@ -66,9 +68,7 @@ angular.module('CompletedJobsDefinition', []) name: { label: 'Name', columnClass: 'col-md-3 col-xs-5', - ngHref: 'nameHref', - sourceModel: 'template', - sourceField: 'name' + ngClick: "viewJobLog(completed_job.id, completed_job.nameHref)" }, failed: { label: 'Job failed?', @@ -85,7 +85,7 @@ angular.module('CompletedJobsDefinition', []) refresh: { mode: 'all', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refreshJobs()" } }, @@ -93,8 +93,8 @@ angular.module('CompletedJobsDefinition', []) submit: { icon: 'icon-rocket', mode: 'all', - ngClick: 'relaunch(completed_job.id)', - awToolTip: 'Relaunch the job', + ngClick: 'relaunchJob(completed_job.id)', + awToolTip: 'Relaunch using the same parameters', dataPlacement: 'top' }, "delete": { @@ -105,11 +105,12 @@ angular.module('CompletedJobsDefinition', []) }, dropdown: { type: 'DropDown', + ngShow: "completed_job.type === 'job'", label: 'View', icon: 'fa-search-plus', 'class': 'btn-default btn-xs', options: [ - { ngHref: '/#/jobs/{{ completed_job.id }}', label: 'Status' }, + //{ 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/JobEvents.js b/awx/ui/static/js/lists/JobEvents.js index ec52f1f173..8050e50d4c 100644 --- a/awx/ui/static/js/lists/JobEvents.js +++ b/awx/ui/static/js/lists/JobEvents.js @@ -21,12 +21,12 @@ angular.module('JobEventsListDefinition', []) filterBy: '{ show: true }', navigationLinks: { - details: { - href: '/#/jobs/{{ job_id }}', - label: 'Status', - icon: 'icon-zoom-in', - ngShow: 'job_id !== null' - }, + //details: { + // href: '/#/jobs/{{ job_id }}', + // label: 'Status', + // icon: 'icon-zoom-in', + // ngShow: 'job_id !== null' + //}, events: { href: '/#/jobs/{{ job_id }}/job_events', label: 'Events', diff --git a/awx/ui/static/js/lists/JobHosts.js b/awx/ui/static/js/lists/JobHosts.js index 025063456b..a78cbf0383 100644 --- a/awx/ui/static/js/lists/JobHosts.js +++ b/awx/ui/static/js/lists/JobHosts.js @@ -20,12 +20,12 @@ angular.module('JobHostDefinition', []) navigationLinks: { ngHide: 'host_id !== null', - details: { - href: "/#/jobs/{{ job_id }}", - label: 'Status', - icon: 'icon-zoom-in', - ngShow: "job_id !== null" - }, + //details: { + // href: "/#/jobs/{{ job_id }}", + // label: 'Status', + // icon: 'icon-zoom-in', + // ngShow: "job_id !== null" + //}, events: { href: "/#/jobs/{{ job_id }}/job_events", label: 'Events', diff --git a/awx/ui/static/js/lists/QueuedJobs.js b/awx/ui/static/js/lists/QueuedJobs.js index 7a4c5cd6db..3ed336d514 100644 --- a/awx/ui/static/js/lists/QueuedJobs.js +++ b/awx/ui/static/js/lists/QueuedJobs.js @@ -22,11 +22,22 @@ angular.module('QueuedJobsDefinition', []) fields: { id: { label: 'Job ID', + ngClick:"viewJobLog(queued_job.id)", key: true, desc: true, searchType: 'int', columnClass: 'col-md-1 col-sm-2 col-xs-2' }, + status: { + label: 'Status', + columnClass: 'col-md-2 col-sm-2 col-xs-2', + awToolTip: "{{ queued_job.status_tip }}", + awTipPlacement: "top", + dataTitle: "{{ queued_job.status_popover_title }}", + icon: 'icon-job-{{ queued_job.status }}', + iconOnly: true, + ngClick:"viewJobLog(queued_job.id)" + }, inventory: { label: 'Inventory ID', searchType: 'int', @@ -48,9 +59,7 @@ angular.module('QueuedJobsDefinition', []) name: { label: 'Name', columnClass: 'col-sm-3 col-xs-5', - ngHref: 'nameHref', - sourceModel: 'template', - sourceField: 'name' + ngClick: "viewJobLog(queued_job.id, queued_job.nameHref)" } }, @@ -59,22 +68,16 @@ angular.module('QueuedJobsDefinition', []) refresh: { mode: 'all', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refreshJobs()" } }, fieldActions: { - status: { - mode: 'all', - iconClass: 'fa icon-job-{{ queued_job.status }}', - awToolTip: "{{ queued_job.statusToolTip }}", - dataPlacement: 'top' - }, submit: { icon: 'icon-rocket', mode: 'all', - ngClick: 'relaunch(queued_job.id)', - awToolTip: 'Launch another instance of the job', + ngClick: 'relaunchJob(queued_job.id)', + awToolTip: 'Relaunch using the same parameters', dataPlacement: 'top' }, cancel: { diff --git a/awx/ui/static/js/lists/RunningJobs.js b/awx/ui/static/js/lists/RunningJobs.js index 811afaefd9..83b7605573 100644 --- a/awx/ui/static/js/lists/RunningJobs.js +++ b/awx/ui/static/js/lists/RunningJobs.js @@ -22,18 +22,29 @@ angular.module('RunningJobsDefinition', []) fields: { id: { label: 'Job ID', + ngClick:"viewJobLog(running_job.id)", key: true, desc: true, searchType: 'int', columnClass: 'col-md-1 col-sm-2 col-xs-2' }, + status: { + label: 'Status', + columnClass: 'col-md-2 col-sm-2 col-xs-2', + awToolTip: "{{ running_job.status_tip }}", + awTipPlacement: "top", + dataTitle: "{{ running_job.status_popover_title }}", + icon: 'icon-job-{{ running_job.status }}', + iconOnly: true, + ngClick:"viewJobLog(running_job.id)" + }, inventory: { label: 'Inventory ID', searchType: 'int', searchOnly: true }, - modified: { - label: 'Last Updated', + started: { + label: 'Started On', link: false, searchable: false, filter: "date:'MM/dd/yy HH:mm:ss'", @@ -48,9 +59,7 @@ angular.module('RunningJobsDefinition', []) name: { label: 'Name', columnClass: 'col-md-3 col-xs-5', - ngHref: 'nameHref', - sourceModel: 'template', - sourceField: 'name' + ngClick: "viewJobLog(running_job.id, running_job.nameHref)" } }, @@ -59,25 +68,16 @@ angular.module('RunningJobsDefinition', []) refresh: { mode: 'all', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refreshJobs()" } }, fieldActions: { - status: { - mode: 'all', - awToolTip: "{{ running_job.status_tip }}", - awTipPlacement: "top", - dataTitle: "{{ running_job.status_popover_title }}", - iconClass: 'fa icon-job-{{ running_job.status }}', - awPopOver: "{{ running_job.status_popover }}", - dataPlacement: 'left' - }, submit: { icon: 'icon-rocket', mode: 'all', - ngClick: 'relaunch(running_job.id)', - awToolTip: 'Launch another instance of the job', + ngClick: 'relaunchJob(running_job.id)', + awToolTip: 'Relaunch using the same parameters', dataPlacement: 'top' }, cancel: { @@ -88,11 +88,12 @@ angular.module('RunningJobsDefinition', []) }, dropdown: { type: 'DropDown', + ngShow: "running_job.type === 'job'", label: 'View', icon: 'fa-search-plus', 'class': 'btn-default btn-xs', options: [ - { ngHref: '/#/jobs/{{ running_job.id }}', label: 'Status' }, + //{ 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 index 6b0d77b0f2..e9bee7c964 100644 --- a/awx/ui/static/js/lists/ScheduledJobs.js +++ b/awx/ui/static/js/lists/ScheduledJobs.js @@ -53,7 +53,7 @@ angular.module('ScheduledJobsDefinition', []) refresh: { mode: 'all', awToolTip: "Refresh the page", - ngClick: "refresh()" + ngClick: "refreshJobs()" } }, diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 721a1813e9..6423eb3a31 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -64,9 +64,14 @@ body.modal-open { .nowrap { white-space: nowrap; } .capitalize { text-transform: capitalize; } .grey-txt { color: @grey; } -.red-txt { color: @red; } a.red-txt:hover { color: @red; } //make red links (for things like cancel) .text-center { text-align: center !important; } +.red-txt, +a.red-txt:visited, +a.red-txt:hover, +a.red-txt:active { + color: @red; +} /* Used on inventory groups/hosts lists for long names */ .ellipsis { @@ -973,12 +978,9 @@ input[type="checkbox"].checkbox-no-label { color: @green; } - .icon-job-pending:before, .icon-job-running:before, .icon-job-success:before, .icon-job-successful:before, - .icon-job-waiting:before, - .icon-job-new:before, .icon-job-changed:before { content: "\f111"; } @@ -989,12 +991,16 @@ input[type="checkbox"].checkbox-no-label { content: "\f06a"; } - .icon-job-pending, + .icon-job-pending:before, + .icon-job-waiting:before, + .icon-job-new:before, + .icon-job-none:before { + content: "\f10c"; + } + .icon-job-running, .icon-job-success, - .icon-job-successful, - .icon-job-waiting, - .icon-job-new { + .icon-job-successful { color: @green; } @@ -1013,15 +1019,14 @@ input[type="checkbox"].checkbox-no-label { color: @red; } - .icon-job-none { + .icon-job-none, + .icon-job-pending, + .icon-job-waiting, + .icon-job-new { color: @grey; opacity: 0.45; } - .icon-job-none:before { - content: "\f10c"; - } - .icon-schedule-enabled-true:before { content: "\f04c"; } @@ -1048,14 +1053,11 @@ input[type="checkbox"].checkbox-no-label { .pagination li a { font-size: 12px; } - .list-table-container { + /*.list-table-container { min-height: 338px; - } + }*/ } - .job-list-target { - min-height: 445px; - } - + /* Inventory job status badge */ .failures-true { background-color: @red; diff --git a/awx/ui/static/less/jquery-ui-overrides.less b/awx/ui/static/less/jquery-ui-overrides.less index bcb1d8de1e..086a0eb1fa 100644 --- a/awx/ui/static/less/jquery-ui-overrides.less +++ b/awx/ui/static/less/jquery-ui-overrides.less @@ -88,3 +88,29 @@ table.ui-datepicker-calendar { .ui-dialog-content.ui-widget-content { padding-top: 20px; } + +.ui-widget-content { + a, + a:visited, + a:active { + color: @blue; + text-decoration: none; + } + + a:hover, + a:focus { + color: @blue-dark; + text-decoration: none; + } + + .red-txt, + a.red-txt:visited, + a.red-txt:hover, + a.red-txt:active { + color: @red; + } + + .dropdown-menu>li>a { + color: @black; + } +} diff --git a/awx/ui/static/less/log-viewer.less b/awx/ui/static/less/log-viewer.less index ab9824baf4..2f93c77d4e 100644 --- a/awx/ui/static/less/log-viewer.less +++ b/awx/ui/static/less/log-viewer.less @@ -10,7 +10,13 @@ #logviewer-modal-dialog { textarea { - overflow: auto; + overflow: scroll; + } + pre { + overflow: scroll; + word-wrap: normal; + word-break: normal; + white-space: pre-wrap; } } diff --git a/awx/ui/static/lib/ansible/InventoryTree.js b/awx/ui/static/lib/ansible/InventoryTree.js index 60c5a2cb56..52f60dbbe3 100644 --- a/awx/ui/static/lib/ansible/InventoryTree.js +++ b/awx/ui/static/lib/ansible/InventoryTree.js @@ -162,9 +162,7 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P } }) .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get inventory tree for: ' + inventory_id + '. GET returned: ' + status }); }); @@ -309,10 +307,10 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P html += "
\n"; if (target.id === 1) { - html += "

Are you sure you want to move group " + inbound.name + " to the top level?

"; + html += "
Are you sure you want to move group " + inbound.name + " to the top level?
"; } else if (inbound.parent === 0) { - html += "

Are you sure you want to move group " + inbound.name + " from the top level and make it a child of " + - target.name + "?

"; + html += "
Are you sure you want to move group " + inbound.name + " from the top level and make it a child of " + + target.name + "?
"; } else { html += "
\n"; html += "

Would you like to copy or move group " + inbound.name + " to group " + target.name + "?

\n"; @@ -502,7 +500,7 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P html += "

Copy Host

\n"; html += "
\n"; html += "
\n"; - html += "

Are you sure you want to copy host " + host.name + ' to group ' + target.name + '?

'; + html += "
Are you sure you want to copy host " + host.name + ' to group ' + target.name + '?
'; html += "
\n"; html += "
\n"; html += "No\n"; diff --git a/awx/ui/static/lib/ansible/Utilities.js b/awx/ui/static/lib/ansible/Utilities.js index 84fd39dec3..4a65dac2c8 100644 --- a/awx/ui/static/lib/ansible/Utilities.js +++ b/awx/ui/static/lib/ansible/Utilities.js @@ -79,6 +79,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) return function (hdr, msg, cls, action, secondAlert, disableButtons) { var scope = $rootScope.$new(), e; if (secondAlert) { + $('#alert2-modal-msg').attr({ "class": "alert" }); scope.alertHeader2 = hdr; scope.alertBody2 = msg; scope.alertClass2 = (cls) ? cls : 'alert-danger'; //default alert class is alert-danger @@ -102,9 +103,11 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } }); } else { + $('#alert-modal-msg').attr({ "class": "alert" }); scope.alertHeader = hdr; scope.alertBody = msg; scope.alertClass = (cls) ? cls : 'alert-danger'; //default alert class is alert-danger + //console.log('msg: ' + msg + ' cls: ' + cls); e = angular.element(document.getElementById('alert-modal')); $compile(e)(scope); $('#alert-modal').modal({ diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js index 8f9b6e6e51..17f7905d16 100644 --- a/awx/ui/static/lib/ansible/generator-helpers.js +++ b/awx/ui/static/lib/ansible/generator-helpers.js @@ -264,6 +264,7 @@ angular.module('GeneratorHelpers', []) html += "" : ""; + if (field.awPopOver) { + html += "aw-pop-over=\"" + field.awPopOver + "\" "; + html += (field.dataPlacement) ? "data-placement=\"" + field.dataPlacement + "\" " : ""; + } + html += ">"; } // Add icon: @@ -563,7 +565,7 @@ angular.module('GeneratorHelpers', []) //} // close the link - if ((field.key || field.link || field.linkTo || field.ngClick || field.ngHref) && + if ((field.key || field.link || field.linkTo || field.ngClick || field.ngHref || field.awToolTip || field.awPopOver) && options.mode !== 'lookup' && options.mode !== 'select' && !field.noLink && !field.ngBindHtml) { html += ""; } diff --git a/awx/ui/static/partials/job_events.html b/awx/ui/static/partials/job_events.html new file mode 100644 index 0000000000..b9a842f6ae --- /dev/null +++ b/awx/ui/static/partials/job_events.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/awx/ui/static/partials/job_host_summaries.html b/awx/ui/static/partials/job_host_summaries.html new file mode 100644 index 0000000000..b9ca5b8eea --- /dev/null +++ b/awx/ui/static/partials/job_host_summaries.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/awx/ui/static/partials/jobs.html b/awx/ui/static/partials/jobs.html index 4ecc3e92e7..a3e6f3bb01 100644 --- a/awx/ui/static/partials/jobs.html +++ b/awx/ui/static/partials/jobs.html @@ -33,6 +33,7 @@
-
+
+
\ No newline at end of file diff --git a/awx/ui/static/partials/logviewer.html b/awx/ui/static/partials/logviewer.html index 6469c6d752..2fafebf3f7 100644 --- a/awx/ui/static/partials/logviewer.html +++ b/awx/ui/static/partials/logviewer.html @@ -5,6 +5,8 @@
  • Standard Out
  • Traceback
  • Options
  • +
  • Extra Vars
  • +
  • Source Vars
  • @@ -19,5 +21,11 @@
    +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 6db7986a3a..06227ff9e0 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -331,7 +331,7 @@