diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js
index 732a6625d6..3fd08a0b21 100644
--- a/awx/ui/static/js/app.js
+++ b/awx/ui/static/js/app.js
@@ -94,7 +94,10 @@ angular.module('ansible', [
'Timezones',
'SchedulesHelper',
'QueuedJobsDefinition',
- 'JobsListDefinition'
+ 'JobsListDefinition',
+ 'LogViewerStatusDefinition',
+ 'LogViewerHelper',
+ 'LogViewerOptionsDefinition'
])
.constant('AngularScheduler.partials', $basePath + 'lib/angular-scheduler/lib/')
diff --git a/awx/ui/static/js/controllers/Projects.js b/awx/ui/static/js/controllers/Projects.js
index 919b7a5b07..d2cf9c9f18 100644
--- a/awx/ui/static/js/controllers/Projects.js
+++ b/awx/ui/static/js/controllers/Projects.js
@@ -12,7 +12,7 @@
function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, ProjectList, GenerateList, LoadBreadCrumbs,
Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit, ProjectUpdate,
- ProjectStatus, FormatDate, Refresh, Wait, Stream, GetChoices, Empty, Find) {
+ ProjectStatus, FormatDate, Refresh, Wait, Stream, GetChoices, Empty, Find, LogViewer) {
ClearScope();
@@ -72,7 +72,7 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
break;
}
- if (project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') {
+ if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') {
$scope.projects[i].statusTip = 'Canceled. Click for details';
}
@@ -206,10 +206,15 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
});
} else if (project.related.last_update) {
Wait('start');
+ LogViewer({
+ scope: $scope,
+ url: project.related.last_update
+ });
+ /*
ProjectStatus({
project_id: id,
last_update: project.related.last_update
- });
+ });*/
} else {
Alert('No Updates Available', 'There is no SCM update information available for this project. An update has not yet been ' +
' completed. If you have not already done so, start an update for this project.', 'alert-info');
@@ -354,7 +359,8 @@ function ProjectsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'ProjectList', 'GenerateList',
'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'GetBasePath',
- 'SelectionInit', 'ProjectUpdate', 'ProjectStatus', 'FormatDate', 'Refresh', 'Wait', 'Stream', 'GetChoices', 'Empty', 'Find'
+ 'SelectionInit', 'ProjectUpdate', 'ProjectStatus', 'FormatDate', 'Refresh', 'Wait', 'Stream', 'GetChoices', 'Empty', 'Find',
+ 'LogViewer'
];
diff --git a/awx/ui/static/js/forms/LogViewerOptions.js b/awx/ui/static/js/forms/LogViewerOptions.js
new file mode 100644
index 0000000000..d23b365977
--- /dev/null
+++ b/awx/ui/static/js/forms/LogViewerOptions.js
@@ -0,0 +1,102 @@
+/*********************************************
+ * Copyright (c) 2014 AnsibleWorks, Inc.
+ *
+ * LogViewerOptions.js
+ *
+ * Form definition for LogViewer.js helper
+ *
+ */
+angular.module('LogViewerOptionsDefinition', [])
+ .value('LogViewerOptionsForm', {
+
+ name: 'status',
+ well: false,
+
+ fields: {
+ "job_args": {
+ label: "Arguments",
+ type: "text",
+ readonly: true
+ },
+ "job_cwd": {
+ label: "CWD",
+ type: "text",
+ readonly: true
+ },
+ "job_type": {
+ label: "Job Type",
+ type: "text",
+ readonly: true
+ },
+ "inventory": {
+ label: "Inventory",
+ type: "text",
+ readonly: true
+ },
+ "project": {
+ label: "Project",
+ type: "text",
+ readonly: true
+ },
+ "playbook": {
+ label: "Playbook",
+ type: "text",
+ readonly: true
+ },
+ "credential": {
+ label: "Credential",
+ type: "text",
+ readonly: true
+ },
+ "cloud credential": {
+ label: "Cloud Cred.",
+ type: "text",
+ readonly: true
+ },
+ "forks": {
+ label: "Forks",
+ type: "text",
+ readonly: true
+ },
+ "limit": {
+ label: "Limit",
+ type: "text",
+ readonly: true
+ },
+ "verbosity": {
+ label: "Verbosity",
+ type: "text",
+ readonly: true
+ },
+ "job_tags": {
+ label: "Job Tags",
+ type: "text",
+ readonly: true
+ },
+ "source": {
+ label: "Source",
+ type: "text",
+ readonly: true
+ },
+ "source_path": {
+ label: "Source Path",
+ type: "text",
+ readonly: true
+ },
+ "source_regions":{
+ label: "Regions",
+ type: "text",
+ readonly: true
+ },
+ "overwrite": {
+ label: "Overwrite Vars",
+ type: "text",
+ readonly: true
+ },
+ "inventory_source": {
+ label: "Inv Source",
+ type: "text",
+ readonly: true
+ }
+ }
+ });
\ No newline at end of file
diff --git a/awx/ui/static/js/forms/LogViewerStatus.js b/awx/ui/static/js/forms/LogViewerStatus.js
new file mode 100644
index 0000000000..7b634998fc
--- /dev/null
+++ b/awx/ui/static/js/forms/LogViewerStatus.js
@@ -0,0 +1,68 @@
+/*********************************************
+ * Copyright (c) 2014 AnsibleWorks, Inc.
+ *
+ * LogViewerStatus.js
+ *
+ * Form definition for LogViewer.js helper
+ *
+ */
+angular.module('LogViewerStatusDefinition', [])
+ .value('LogViewerStatusForm', {
+
+ name: 'status',
+ well: false,
+
+ fields: {
+ "name": {
+ label: "Name",
+ type: "text",
+ readonly: true,
+ },
+ "created": {
+ label: "Created",
+ type: "text",
+ readonly: true
+ },
+ "modified": {
+ label: "Modified",
+ type: "text",
+ readonly: true
+ },
+ "unified_job_template": {
+ label: "Job Template",
+ type: "text",
+ readonly: true
+ },
+ "launch_type": {
+ label: "Launch Type",
+ type: "text",
+ readonly: true
+ },
+ "status": {
+ label: "Status",
+ type: "text",
+ readonly: true
+ },
+ "license_error": {
+ label: "License Error",
+ type: "text",
+ readonly: true
+ },
+ "started": {
+ label: "Started",
+ type: "text",
+ readonly: true
+ },
+ "finished": {
+ label: "Finished",
+ type: "text",
+ readonly: true
+ },
+ "elapsed": {
+ label: "Elapsed",
+ type: "text",
+ readonly: true
+ }
+ }
+
+ });
\ No newline at end of file
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index ddb4f31b66..a57ad173b4 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -12,7 +12,7 @@
angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'GroupListDefinition', 'SearchHelper',
'PaginationHelpers', 'ListGenerator', 'AuthService', 'GroupsHelper', 'InventoryHelper', 'SelectionHelper',
'JobSubmissionHelper', 'RefreshHelper', 'PromptDialog', 'CredentialsListDefinition', 'InventoryTree',
- 'InventoryStatusDefinition', 'VariablesHelper', 'SchedulesListDefinition', 'SourceFormDefinition'])
+ 'InventoryStatusDefinition', 'VariablesHelper', 'SchedulesListDefinition', 'SourceFormDefinition', 'LogViewerHelper'])
.factory('GetSourceTypeOptions', ['Rest', 'ProcessErrors', 'GetBasePath',
function (Rest, ProcessErrors, GetBasePath) {
@@ -48,15 +48,24 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
])
-.factory('ViewUpdateStatus', ['Rest', 'ProcessErrors', 'GetBasePath', 'ShowUpdateStatus', 'Alert', 'Wait', 'Empty', 'Find',
- function (Rest, ProcessErrors, GetBasePath, ShowUpdateStatus, Alert, Wait, Empty, Find) {
+.factory('ViewUpdateStatus', ['Rest', 'ProcessErrors', 'GetBasePath', 'Alert', 'Wait', 'Empty', 'Find', 'LogViewer',
+ function (Rest, ProcessErrors, GetBasePath, Alert, Wait, Empty, Find, LogViewer) {
return function (params) {
var scope = params.scope,
tree_id = params.tree_id,
- group_id = params.group_id,
group = Find({ list: scope.groups, key: 'id', val: tree_id });
+ if (scope.removeSourceReady) {
+ scope.removeSourceReady();
+ }
+ scope.removeSourceReady = scope.$on('SourceReady', function(e, url) {
+ LogViewer({
+ scope: scope,
+ url: url
+ });
+ });
+
if (group) {
if (Empty(group.source)) {
Alert('Missing Configuration', 'The selected group is not configured for inventory sync. ' +
@@ -70,22 +79,12 @@ angular.module('GroupsHelper', ['RestServices', 'Utilities', 'ListGenerator', 'G
Rest.get()
.success(function (data) {
var url = (data.related.current_update) ? data.related.current_update : data.related.last_update;
- ShowUpdateStatus({
- group_name: data.summary_fields.group.name,
- last_update: url,
- license_error: ((data.summary_fields.last_update && data.summary_fields.last_update.license_error) ? true : false),
- tree_id: tree_id,
- group_id: group_id,
- parent_scope: scope
- });
+ scope.$emit('SourceReady', url);
})
.error(function (data, status) {
- Wait('stop');
- ProcessErrors(scope, data, status, null, {
- hdr: 'Error!',
+ ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source +
- ' POST returned status: ' + status
- });
+ ' POST returned status: ' + status });
});
}
}
diff --git a/awx/ui/static/js/helpers/LogViewer.js b/awx/ui/static/js/helpers/LogViewer.js
new file mode 100644
index 0000000000..817234c485
--- /dev/null
+++ b/awx/ui/static/js/helpers/LogViewer.js
@@ -0,0 +1,237 @@
+/*********************************************
+ * Copyright (c) 2014 AnsibleWorks, Inc.
+ *
+ * LogViewer.js
+ *
+ */
+
+'use strict';
+
+angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator'])
+
+ .factory('LogViewer', ['$compile', 'CreateDialog', 'GetJob', 'Wait', 'GenerateForm', 'LogViewerStatusForm', 'AddTable', 'AddTextarea',
+ 'LogViewerOptionsForm', 'EnvTable', 'GetBasePath', 'LookUpName', 'Empty',
+ function($compile, CreateDialog, GetJob, Wait, GenerateForm, LogViewerStatusForm, AddTable, AddTextarea, LogViewerOptionsForm, EnvTable,
+ GetBasePath, LookUpName, Empty) {
+ return function(params) {
+ var parent_scope = params.scope,
+ url = params.url,
+ scope = parent_scope.$new();
+
+ if (scope.removeModalReady) {
+ scope.removeModalReady();
+ }
+ scope.removeModalReady = scope.$on('ModalReady', function() {
+ Wait('stop');
+ $('#logviewer-modal-dialog').dialog('open');
+ });
+
+ if (scope.removeJobReady) {
+ scope.removeJobReady();
+ }
+ scope.removeJobReady = scope.$on('JobReady', function(e, data) {
+ var key, resizeText, elem;
+
+ for (key in data) {
+ scope[key] = data[key];
+ }
+
+ AddTable({ scope: scope, form: LogViewerStatusForm, id: 'status-form-container' });
+ AddTable({ scope: scope, form: LogViewerOptionsForm, id: 'options-form-container' });
+
+ if (data.result_stdout) {
+ AddTextarea({
+ container_id: 'stdout-form-container',
+ val: data.result_stdout,
+ fld_id: 'stdout-textarea'
+ });
+ }
+ else {
+ $('#logview-tabs li:eq(2)').hide();
+ }
+
+ if (data.result_traceback) {
+ AddTextarea({
+ container_id: 'traceback-form-container',
+ val: data.result_traceback,
+ fld_id: 'traceback-textarea'
+ });
+ }
+ else {
+ $('#logview-tabs li:eq(2)').hide();
+ }
+
+ if (data.job_env) {
+ EnvTable({
+ id: 'env-form-container',
+ vars: data.job_env
+ });
+ }
+
+ if (!Empty(scope.credential)) {
+ LookUpName({
+ scope: scope,
+ scope_var: 'credential',
+ url: GetBasePath('credentials') + scope.credential + '/'
+ });
+ }
+
+ if (!Empty(scope.inventory)) {
+ LookUpName({
+ scope: scope,
+ scope_var: 'inventory',
+ url: GetBasePath('inventories') + scope.inventory + '/'
+ });
+ }
+
+ if (!Empty(scope.project)) {
+ LookUpName({
+ scope: scope,
+ scope_var: 'project',
+ url: GetBasePath('projects') + scope.project + '/'
+ });
+ }
+
+ if (!Empty(scope.cloud_credential)) {
+ LookUpName({
+ scope: scope,
+ scope_var: 'cloud_credential',
+ url: GetBasePath('credentials') + scope.cloud_credential + '/'
+ });
+ }
+
+ if (!Empty(scope.inventory_source)) {
+ LookUpName({
+ scope: scope,
+ scope_var: 'inventory_source',
+ url: GetBasePath('inventory_sources') + scope.inventory_source + '/'
+ });
+ }
+
+ resizeText = function() {
+ var u = $('#logview-tabs').outerHeight() + 25,
+ h = $('#logviewer-modal-dialog').innerHeight(),
+ rows = Math.floor((h - u) / 20);
+ rows -= 3;
+ rows = (rows < 6) ? 6 : rows;
+ $('#stdout-textarea').attr({ rows: rows });
+ $('#traceback-textarea').attr({ rows: rows });
+ };
+
+ elem = angular.element(document.getElementById('logviewer-modal-dialog'));
+ $compile(elem)(scope);
+
+ CreateDialog({
+ scope: scope,
+ width: 600,
+ height: 675,
+ minWidth: 450,
+ callback: 'ModalReady',
+ id: 'logviewer-modal-dialog',
+ onResizeStop: resizeText,
+ onOpen: function() {
+ $('#logview-tabs a:first').tab('show');
+ resizeText();
+ }
+ });
+ });
+
+ GetJob({
+ url: url,
+ scope: scope
+ });
+
+ scope.modalOK = function() {
+ $('#logviewer-modal-dialog').dialog('close');
+ };
+ };
+ }])
+
+ .factory('GetJob', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) {
+ return function(params) {
+ var url = params.url,
+ scope = params.scope;
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function(data){
+ scope.$emit('JobReady', data);
+ })
+ .error(function(data, status) {
+ ProcessErrors(scope, data, status, null, { hdr: 'Error!',
+ msg: 'Failed to retrieve ' + url + '. GET returned: ' + status });
+ });
+ };
+ }])
+
+ .factory('LookUpName', ['Rest', 'ProcessErrors', 'Empty', function(Rest, ProcessErrors, Empty) {
+ return function(params) {
+ var url = params.url,
+ scope_var = params.scope_var,
+ scope = params.scope;
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function(data) {
+ if (!Empty(data.name)) {
+ scope[scope_var] = data.name;
+ }
+ })
+ .error(function(data, status) {
+ ProcessErrors(scope, data, status, null, { hdr: 'Error!',
+ msg: 'Failed to retrieve ' + url + '. GET returned: ' + status });
+ });
+ };
+ }])
+
+ .factory('AddTable', ['Empty', function(Empty) {
+ return function(params) {
+ var form = params.form,
+ id = params.id,
+ scope = params.scope,
+ fld, html;
+ html = "
\n";
+ for (fld in form.fields) {
+ if (!Empty(scope[fld])) {
+ html += "" + form.fields[fld].label + " | " +
+ "";
+ if (fld === "credential" || fld === "project" || fld === "inventory" || fld === "cloud_credential" ||
+ fld === "inventory_source") {
+ html += "{{ " + fld + " }}";
+ }
+ else {
+ html += scope[fld];
+ }
+ html += " |
\n";
+ }
+ }
+ html += "
\n";
+ $('#' + id).empty().html(html);
+ };
+ }])
+
+ .factory('AddTextarea', [ function() {
+ return function(params) {
+ var container_id = params.container_id,
+ val = params.val,
+ fld_id = params.fld_id,
+ html;
+ html = "\n" +
+ "" +
+ "
\n";
+ $('#' + container_id).empty().html(html);
+ };
+ }])
+
+ .factory('EnvTable', [ function() {
+ return function(params) {
+ var id = params.id,
+ vars = params.vars,
+ key, html;
+ html = "\n";
+ for (key in vars) {
+ html += "" + key + " | " +
+ "" + vars[key] + " |
\n";
+ }
+ html += "
\n";
+ $('#' + id).empty().html(html);
+ };
+ }]);
\ 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 328b8dc80c..2a4d6c8464 100644
--- a/awx/ui/static/js/lists/Schedules.js
+++ b/awx/ui/static/js/lists/Schedules.js
@@ -23,11 +23,18 @@ angular.module('SchedulesListDefinition', [])
name: {
key: true,
label: 'Name',
- ngClick: "editSchedule(schedule.id)"
+ ngClick: "editSchedule(schedule.id)",
+ columnClass: "col-md-5 col-sm-3 col-xs-3"
},
dtstart: {
label: 'Start',
- searchable: false
+ searchable: false,
+ columnClass: "col-md-2 col-sm-3 col-xs-3"
+ },
+ next_run: {
+ label: 'Next Run',
+ searchable: false,
+ columnClass: "col-md-2 col-sm-3 col-xs-3"
},
dtend: {
label: 'End',
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index 993d848478..4dc36584c9 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -32,6 +32,7 @@
@import "jquery-ui-overrides.less";
@import "codemirror.less";
@import "angular-scheduler.less";
+@import "log-viewer.less";
html, body { height: 100%; }
@@ -305,20 +306,6 @@ td.actions {
border-top-color: #696969;
}
-.dropdown-toggle,
-
-/*
-.dropdown-toggle:hover,
-.btn-default:visited,
-.btn-default:hover,
-.btn-default:active
- {
- color: #333;
- background-color: #bbb;
- border-color: #bbb;
-}
-*/
-
.btn-light {
color: #333;
background-color: #ddd;
diff --git a/awx/ui/static/less/log-viewer.less b/awx/ui/static/less/log-viewer.less
new file mode 100644
index 0000000000..ab9824baf4
--- /dev/null
+++ b/awx/ui/static/less/log-viewer.less
@@ -0,0 +1,23 @@
+/*********************************************
+ * Copyright (c) 2014 AnsibleWorks, Inc.
+ *
+ * log-viewer.css
+ *
+ * custom styles for LogViewer.js helper
+ *
+ */
+
+#logviewer-modal-dialog {
+
+ textarea {
+ overflow: auto;
+ }
+}
+
+table.logviewer-status {
+ margin-top: 20px;
+
+ .fld-label {
+ font-weight: bold;
+ }
+}
\ No newline at end of file
diff --git a/awx/ui/static/lib/ansible/Modal.js b/awx/ui/static/lib/ansible/Modal.js
index 58e9ab147d..e36bd30c2c 100644
--- a/awx/ui/static/lib/ansible/Modal.js
+++ b/awx/ui/static/lib/ansible/Modal.js
@@ -163,6 +163,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
textareaId = params.textareaId,
modalId = params.modalId,
formId = params.formId,
+ parse = (params.parse === undefined) ? true : params.parse,
textarea,
formHeight, model, windowHeight, offset, rows;
@@ -189,6 +190,8 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
rows--;
textarea.attr('rows', rows);
}
- ParseTypeChange({ scope: scope, field_id: textareaId, onReady: waitStop });
+ if (parse) {
+ ParseTypeChange({ scope: scope, field_id: textareaId, onReady: waitStop });
+ }
};
}]);
\ No newline at end of file
diff --git a/awx/ui/static/partials/inventory-edit.html b/awx/ui/static/partials/inventory-edit.html
index 8927f6f7d2..93f7889259 100644
--- a/awx/ui/static/partials/inventory-edit.html
+++ b/awx/ui/static/partials/inventory-edit.html
@@ -49,6 +49,6 @@
-
+
\ No newline at end of file
diff --git a/awx/ui/static/partials/logviewer.html b/awx/ui/static/partials/logviewer.html
new file mode 100644
index 0000000000..1620074395
--- /dev/null
+++ b/awx/ui/static/partials/logviewer.html
@@ -0,0 +1,27 @@
+
+
\ No newline at end of file
diff --git a/awx/ui/static/partials/projects.html b/awx/ui/static/partials/projects.html
index a034df92bc..59059d79f9 100644
--- a/awx/ui/static/partials/projects.html
+++ b/awx/ui/static/partials/projects.html
@@ -1,4 +1,4 @@
\ No newline at end of file
diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html
index be087d8aed..6db7986a3a 100644
--- a/awx/ui/templates/ui/index.html
+++ b/awx/ui/templates/ui/index.html
@@ -98,6 +98,8 @@
+
+
@@ -147,6 +149,7 @@
+