Inventory refactor: add job status summary fly-out on host status. Click on a status and a second dialog appears for the specific job. Dialog can be resized and moved- built using jquery and styled to match TB dialog. Fixed home/groups and home/hosts pages to appear more inventory edit page. Home/groups page now allows you to start and inventory sync as well. Fixed tool-tip consistency. Click page forward/back now employs the spinner and should stop overclicking, which was resulting in page numbers < 0.

This commit is contained in:
chris Houseknecht 2014-01-22 04:53:18 -05:00
parent 2213268096
commit 7269c7bd06
62 changed files with 15517 additions and 177 deletions

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

File diff suppressed because one or more lines are too long

View File

@ -60,6 +60,7 @@ angular.module('ansible', [
'JobEventsListDefinition',
'JobEventDataDefinition',
'JobHostDefinition',
'JobSummaryDefinition',
'ParseHelper',
'ChildrenHelper',
'EventsHelper',

View File

@ -95,7 +95,8 @@ Home.$inject=['$scope', '$compile', '$routeParams', '$rootScope', '$location', '
function HomeGroups ($location, $routeParams, HomeGroupList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
GetBasePath, SearchInit, PaginateInit, FormatDate, GetHostsStatusMsg, GetSyncStatusMsg, ViewUpdateStatus, Stream, GroupsEdit) {
GetBasePath, SearchInit, PaginateInit, FormatDate, GetHostsStatusMsg, GetSyncStatusMsg, ViewUpdateStatus, Stream, GroupsEdit, Wait,
Alert, Rest, Empty, InventoryUpdate, Find) {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -111,35 +112,39 @@ function HomeGroups ($location, $routeParams, HomeGroupList, GenerateList, Proce
scope.removePostRefresh();
}
scope.removePostRefresh = scope.$on('PostRefresh', function() {
var msg, update_status, last_update;
var hosts_status, update_status, last_update, stat;
for (var i=0; i < scope.home_groups.length; i++) {
scope['home_groups'][i]['inventory_name'] = scope['home_groups'][i]['summary_fields']['inventory']['name'];
last_update = (scope.home_groups[i].summary_fields.inventory_source.last_updated == null) ? null :
FormatDate(new Date(scope.home_groups[i].summary_fields.inventory_source.last_updated));
// Set values for Failed Hosts column
scope.home_groups[i].failed_hosts = scope.home_groups[i].hosts_with_active_failures + ' / ' + scope.home_groups[i].total_hosts;
msg = GetHostsStatusMsg({
stat = GetSyncStatusMsg({
status: scope.home_groups[i].summary_fields.inventory_source.status
}); // from helpers/Groups.js
hosts_status = GetHostsStatusMsg({
active_failures: scope.home_groups[i].hosts_with_active_failures,
total_hosts: scope.home_groups[i].total_hosts,
inventory_id: scope.home_groups[i].inventory,
group_id: scope.home_groups[i].id
});
scope['home_groups'][i].status_class = stat['class'],
scope['home_groups'][i].status_tooltip = stat['tooltip'],
scope['home_groups'][i].launch_tooltip = stat['launch_tip'],
scope['home_groups'][i].launch_class = stat['launch_class'],
scope['home_groups'][i].hosts_status_tip = hosts_status['tooltip'],
scope['home_groups'][i].show_failures = hosts_status['failures'],
scope['home_groups'][i].hosts_status_class = hosts_status['class'],
update_status = GetSyncStatusMsg({ status: scope.home_groups[i].summary_fields.inventory_source.status });
scope.home_groups[i].failed_hosts_tip = msg['tooltip'];
scope.home_groups[i].failed_hosts_link = msg['url'];
scope.home_groups[i].failed_hosts_class = msg['class'];
scope.home_groups[i].status = update_status['status'];
//scope.home_groups[i].failed_hosts_tip = msg['tooltip'];
//scope.home_groups[i].failed_hosts_link = msg['url'];
//scope.home_groups[i].failed_hosts_class = msg['class'];
scope.home_groups[i].status = scope.home_groups[i].summary_fields.inventory_source.status;
scope.home_groups[i].source = (scope.home_groups[i].summary_fields.inventory_source) ?
scope.home_groups[i].summary_fields.inventory_source.source : null;
scope.home_groups[i].last_updated = last_update;
scope.home_groups[i].status_badge_class = update_status['class'];
scope.home_groups[i].status_badge_tooltip = update_status['tooltip'];
//scope.home_groups[i].last_updated = last_update;
//scope.home_groups[i].status_badge_class = update_status['class'];
//scope.home_groups[i].status_badge_tooltip = update_status['tooltip'];
}
});
@ -224,16 +229,52 @@ function HomeGroups ($location, $routeParams, HomeGroupList, GenerateList, Proce
ViewUpdateStatus({ scope: scope, tree_id: id })
};
// Launch inventory sync
scope.updateGroup = function(id) {
var group = Find({ list: scope.home_groups, key: 'id', val: id});
if (group) {
if (Empty(group.source)) {
// if no source, do nothing.
}
else if (group.status == 'updating') {
Alert('Update in Progress', 'The inventory update process is currently running for group <em>' +
scope.home_groups[i].name + '</em>. Use the Refresh button to monitor the status.', 'alert-info');
}
else {
Wait('start');
Rest.setUrl(group.related.inventory_source);
Rest.get()
.success( function(data, status, headers, config) {
InventoryUpdate({
scope: scope,
url: data.related.update,
group_name: data.summary_fields.group.name,
group_source: data.source,
tree_id: group.id,
group_id: group.id
});
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source +
' POST returned status: ' + status });
});
}
}
}
scope.refresh = function() { scope.search(list.iterator, null, false, true); }
}
HomeGroups.$inject = [ '$location', '$routeParams', 'HomeGroupList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'GetHostsStatusMsg', 'GetSyncStatusMsg', 'ViewUpdateStatus',
'Stream', 'GroupsEdit'
'Stream', 'GroupsEdit', 'Wait', 'Alert', 'Rest', 'Empty', 'InventoryUpdate', 'Find'
];
function HomeHosts ($location, $routeParams, HomeHostList, GenerateList, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
GetBasePath, SearchInit, PaginateInit, FormatDate, SetHostStatus, ToggleHostEnabled, HostsEdit, Stream, Find) {
GetBasePath, SearchInit, PaginateInit, FormatDate, SetStatus, ToggleHostEnabled, HostsEdit, Stream, Find, ShowJobSummary) {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -251,7 +292,8 @@ function HomeHosts ($location, $routeParams, HomeHostList, GenerateList, Process
scope.removePostRefresh = scope.$on('PostRefresh', function() {
for (var i=0; i < scope.hosts.length; i++) {
scope['hosts'][i]['inventory_name'] = scope['hosts'][i]['summary_fields']['inventory']['name'];
SetHostStatus(scope['hosts'][i]);
//SetHostStatus(scope['hosts'][i]);
SetStatus({ scope: scope, host: scope['hosts'][i] });
}
});
@ -287,6 +329,7 @@ function HomeHosts ($location, $routeParams, HomeHostList, GenerateList, Process
LoadBreadCrumbs();
scope.showActivity = function() { Stream(); }
scope.toggle_host_enabled = function(id, sources) { ToggleHostEnabled({ host_id: id, external_source: sources, scope: scope }); }
scope.editHost = function(host_id, host_name) {
@ -296,9 +339,13 @@ function HomeHosts ($location, $routeParams, HomeHostList, GenerateList, Process
}
}
scope.showJobSummary = function(job_id) {
ShowJobSummary({ job_id: job_id });
}
}
HomeHosts.$inject = [ '$location', '$routeParams', 'HomeHostList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller',
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetHostStatus', 'ToggleHostEnabled', 'HostsEdit', 'Stream',
'Find'
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetStatus', 'ToggleHostEnabled', 'HostsEdit', 'Stream',
'Find', 'ShowJobSummary'
];

View File

@ -304,7 +304,7 @@ function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateLis
GetSyncStatusMsg, InjectHosts, HostsReload, GroupsAdd, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty,
Rest, ProcessErrors, InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find,
HostsCreate, EditInventoryProperties, HostsEdit, HostsDelete, ToggleHostEnabled, CopyMoveGroup, CopyMoveHost,
Stream, GetBasePath)
Stream, GetBasePath, ShowJobSummary)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -510,6 +510,10 @@ function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateLis
var url = GetBasePath('activity_stream') + '?group__inventory__id=' + $scope.inventory_id;
Stream({ inventory_name: $scope.inventory_name, url: url });
}
$scope.showJobSummary = function(job_id) {
ShowJobSummary({ job_id: job_id });
}
//Load tree data for the first time
BuildTree({ scope: $scope, inventory_id: $scope.inventory_id, refresh: false });
@ -520,6 +524,6 @@ InventoriesEdit.$inject = [ '$scope', '$location', '$routeParams', '$compile', '
'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsAdd', 'GroupsEdit', 'GroupsDelete',
'Breadcrumbs', 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren',
'ViewUpdateStatus', 'GroupsCancelUpdate', 'Find', 'HostsCreate', 'EditInventoryProperties', 'HostsEdit',
'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost', 'Stream', 'GetBasePath'
'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost', 'Stream', 'GetBasePath', 'ShowJobSummary'
];

View File

@ -93,5 +93,5 @@ angular.module('HostFormDefinition', [])
}
}); //UserForm
});

View File

@ -0,0 +1,50 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* JobSummary.js
*
* Display job status info in a dialog
*
*/
angular.module('JobSummaryDefinition', [])
.value(
'JobSummary', {
editTitle: '{{ id }} - {{ name }}',
name: 'jobs',
well: false,
fields: {
status: {
//label: 'Job Status',
type: 'custom',
control: '<div class=\"job-detail-status\"><span style="padding-right: 15px; font-weight: bold;">Status</span> ' +
'<i class=\"fa icon-job-\{\{ status \}\}\"></i> \{\{ status \}\}</div>',
readonly: true
},
created: {
label: 'Created On',
type: 'text',
readonly: true
},
result_stdout: {
label: 'Standard Out',
type: 'textarea',
readonly: true,
xtraWide: true,
rows: "\{\{ stdout_rows \}\}",
"class": 'nowrap mono-space resizable',
ngShow: "result_stdout != ''"
},
result_traceback: {
label: 'Traceback',
type: 'textarea',
xtraWide: true,
readonly: true,
rows: "\{\{ traceback_rows \}\}",
"class": 'nowrap mono-space resizable',
ngShow: "result_traceback != ''"
}
}
});

View File

@ -316,7 +316,7 @@ angular.module('JobFormDefinition', [])
readonly: true,
xtraWide: true,
rows: "\{\{ stdout_rows \}\}",
"class": 'nowrap',
"class": 'nowrap mono-space',
ngShow: "result_stdout != ''"
},
result_traceback: {
@ -325,7 +325,7 @@ angular.module('JobFormDefinition', [])
xtraWide: true,
readonly: true,
rows: "\{\{ traceback_rows \}\}",
"class": 'nowrap',
"class": 'nowrap mono-space',
ngShow: "result_traceback != ''"
}
},

View File

@ -51,9 +51,85 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
}
}])
.factory('SetStatus', ['SetEnabledMsg', 'Empty', function(SetEnabledMsg, Empty) {
return function(params) {
var scope = params.scope;
var host = params.host;
var html, title;
function setMsg(host) {
if (host.has_active_failures == true || (host.has_active_failures == false && host.last_job !== null)) {
if (host.has_active_failures === true) {
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
host.active_failures = 'failed';
}
else {
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
host.active_failures = 'success';
}
if (host.summary_fields.recent_jobs.length > 0) {
// build html table of job status info
var jobs = host.summary_fields.recent_jobs.sort(
function(a,b) {
// reverse numerical order
return -1 * (a - b);
});
title = "Recent Jobs";
html = "<table class=\"table table-condensed\">\n";
html += "<thead>\n";
html += "<tr>\n";
html += "<th>ID</td>\n";
html += "<th>Status</td>\n";
html += "<th>Name</td>\n";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
for (var j=0; j < jobs.length; j++) {
var job = jobs[j];
html += "<tr>\n";
html += "<td><a href=\"/#/jobs/" + job.id + "\">" + job.id + "</a></td>\n";
html += "<td><a ng-click=\"showJobSummary(" + job.id + ")\"><i class=\"fa icon-job-" + job.status + "\"></i> " + job.status + "</a></td>\n";
html += "<td>" + job.name + "</td>\n";
html += "</tr>\n";
}
html += "</tbody>\n";
html += "</table>\n";
}
else {
title = 'No job data';
html = '<p>No recent job data available for this host.</p>';
}
}
else if (host.has_active_failures == false && host.last_job == null) {
host.has_active_failures = 'none';
host.badgeToolTip = "No job data available.";
host.active_failures = 'n/a';
}
host.job_status_html = html;
host.job_status_title = title;
}
if (!Empty(host)) {
// update single host
setMsg(host);
SetEnabledMsg(host);
}
else {
// update all hosts
for (var i=0; i < scope.hosts.length; i++) {
setMsg(scope.hosts[i]);
SetEnabledMsg(scope.hosts[i]);
}
}
}
}])
.factory('HostsReload', [ '$routeParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'SearchInit', 'PaginateInit', 'Wait', 'SetHostStatus',
function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateInit, Wait, SetHostStatus) {
.factory('HostsReload', [ '$routeParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'SearchInit', 'PaginateInit', 'Wait',
'SetHostStatus', 'SetStatus',
function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateInit, Wait, SetHostStatus, SetStatus) {
return function(params) {
var scope = params.scope;
@ -74,9 +150,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
for (var i=0; i < scope.hosts.length; i++) {
//Set tooltip for host enabled flag
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
//SetEnabledMsg(scope.hosts[i]);
SetHostStatus(scope.hosts[i]);
//SetHostStatus(scope.hosts[i]);
}
SetStatus({ scope: scope });
Wait('stop');
scope.$emit('HostReloadComplete');
});
@ -347,9 +423,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetEnabledMsg',
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus',
function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetEnabledMsg) {
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus) {
return function(params) {
var parent_scope = params.scope;
@ -371,7 +447,6 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
scope.parseType = 'yaml';
ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
if (scope.hostLoadedRemove) {
scope.hostLoadedRemove();
@ -441,7 +516,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
host.name = scope.name;
host.enabled = scope.enabled;
host.enabled_flag = scope.enabled;
SetEnabledMsg(host);
SetStatus({ scope: parent_scope, host: host });
// Close modal
Wait('stop');
$('#form-modal').modal('hide');

View File

@ -423,7 +423,9 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
Wait('stop');
Alert('Update Started', 'Your request to start the inventory sync process was submitted. Monitor progress ' +
'by clicking the <i class="fa fa-refresh fa-lg"></i> button.', 'alert-info');
scope.removeHostReloadComplete();
if (scope.removeHostReloadComplete) {
scope.removeHostReloadComplete();
}
});
if (scope.removeUpdateSubmitted) {
@ -431,10 +433,14 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
}
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function(e, action) {
if (action == 'started') {
// Cancel the update process
scope.selected_tree_id = tree_id;
scope.selected_group_id = group_id;
scope.refreshGroups();
if (scope.refreshGroups) {
scope.selected_tree_id = tree_id;
scope.selected_group_id = group_id;
scope.refreshGroups();
}
else {
scope.$emit('HostReloadComplete');
}
}
});

View File

@ -7,7 +7,8 @@
*
*/
angular.module('JobsHelper', [ ])
angular.module('JobsHelper', ['Utilities', 'FormGenerator', 'JobSummaryDefinition'])
.factory('JobStatusToolTip', [ function() {
return function(status) {
var toolTip;
@ -37,4 +38,97 @@ angular.module('JobsHelper', [ ])
}
return toolTip;
}
}]);
}])
.factory('ShowJobSummary', ['Rest', 'Wait', 'GetBasePath', 'FormatDate', 'ProcessErrors', 'GenerateForm', 'JobSummary',
function(Rest, Wait, GetBasePath, FormatDate, ProcessErrors, GenerateForm, JobSummary) {
return function(params) {
// Display status info in a modal dialog- called from inventory edit page
var job_id = params.job_id;
var generator = GenerateForm;
var form = JobSummary;
// Using jquery dialog for its expandable property
var html = "<div id=\"status-modal-dialog\" title=\"Job " + job_id + "\"><div id=\"form-container\" style=\"width: 100%;\"></div></div>\n";
$('#inventory-modal-container').empty().append(html);
var scope = generator.inject(form, { mode: 'edit', id: 'form-container', breadCrumbs: false, related: false });
// Set modal dimensions based on viewport width
var ww = $(document).width();
var wh = $('body').height();
var x, y, maxrows;
if (ww > 1199) {
// desktop
x = 675;
y = (750 > wh) ? wh - 20 : 750;
maxrows = 20;
}
else if (ww <= 1199 && ww >= 768) {
x = 550;
y = (620 > wh) ? wh - 15 : 620;
maxrows = 15;
}
else {
x = (ww - 20);
y = (500 > wh) ? wh : 500;
maxrows = 10;
}
// Create the modal
$('#status-modal-dialog').dialog({
buttons: { "OK": function() { $( this ).dialog( "close" ); } },
modal: true,
width: x,
height: y,
autoOpen: false,
create: function (e, ui) {
// fix the close button
$('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button').empty().attr({ 'class': 'close' }).text('x');
// fix the OK button
$('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-buttonset button:first')
.attr({ 'class': 'btn btn-primary' });
},
resizeStop: function(e, ui) {
// for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100%
var dialog = $('.ui-dialog[aria-describedby="status-modal-dialog"]');
var content = dialog.find('#status-modal-dialog');
content.width( dialog.width() - 28 );
},
close: function(e, ui) {
// Destroy on close
$('#status-modal-dialog').dialog('destroy');
$('#inventory-modal-container').empty();
}
});
function calcRows (content) {
var n = content.match(/\n/g);
var rows = (n) ? n.length : 1;
return (rows > maxrows) ? 20 : rows;
}
Wait('start');
var url = GetBasePath('jobs') + job_id + '/';
Rest.setUrl(url);
Rest.get()
.success( function(data, status, headers, config) {
scope.id = data.id;
scope.name = data.name;
scope.status = data.status;
scope.result_stdout = data.result_stdout;
scope.result_traceback = data.result_traceback;
scope['stdout_rows'] = calcRows(scope['result_stdout']);
scope['traceback_rows'] = calcRows(scope['result_traceback']);
var cDate = new Date(data.created);
scope.created = FormatDate(cDate);
Wait('stop');
$('#status-modal-dialog').dialog('open');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Attempt to load job failed. GET returned status: ' + status });
});
}
}]);

View File

@ -14,8 +14,8 @@
*
*/
angular.module('PaginateHelper', ['RefreshHelper', 'ngCookies'])
.factory('PaginateInit', [ 'Refresh', '$cookieStore', function(Refresh, $cookieStore) {
angular.module('PaginateHelper', ['RefreshHelper', 'ngCookies', 'Utilities'])
.factory('PaginateInit', [ 'Refresh', '$cookieStore', 'Wait', function(Refresh, $cookieStore, Wait) {
return function(params) {
var scope = params.scope;
@ -51,6 +51,7 @@ angular.module('PaginateHelper', ['RefreshHelper', 'ngCookies'])
scope.nextSet = function(set, iterator) {
if (scope[iterator + 'NextUrl']) {
scope[iterator + 'Page']++;
Wait('start');
Refresh({ scope: scope, set: set, iterator: iterator, url: scope[iterator + 'NextUrl'] });
}
};
@ -58,6 +59,7 @@ angular.module('PaginateHelper', ['RefreshHelper', 'ngCookies'])
scope.prevSet = function(set, iterator) {
if (scope[iterator + 'PrevUrl']) {
scope[iterator + 'Page']--;
Wait('start');
Refresh({ scope: scope, set: set, iterator: iterator, url: scope[iterator + 'PrevUrl'] });
}
};
@ -70,11 +72,10 @@ angular.module('PaginateHelper', ['RefreshHelper', 'ngCookies'])
scope[iterator + 'Page'] = 0;
var new_url = url.replace(/\?page_size\=\d+/,'');
console.log('new_url: ' + new_url);
var connect = (/\/$/.test(new_url)) ? '?' : '&';
new_url += (scope[iterator + 'SearchParams']) ? connect + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + 'PageSize' ] :
connect + 'page_size=' + scope[iterator + 'PageSize' ];
console.log('new_url: ' + new_url);
Wait('start');
Refresh({ scope: scope, set: set, iterator: iterator, url: new_url });
}
}

View File

@ -41,7 +41,6 @@ angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities'])
}
})
.error ( function(data, status, headers, config) {
Wait('stop');
//scope[iterator + 'SearchSpin'] = true;
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status });

View File

@ -41,7 +41,6 @@ angular.module('RefreshHelper', ['RestServices', 'Utilities'])
scope.$emit('PostRefresh');
})
.error ( function(data, status, headers, config) {
Wait('stop');
//scope[iterator + 'SearchSpin'] = false;
scope[iterator + 'HoldInput'] = false;
ProcessErrors(scope, data, status, null,

View File

@ -33,7 +33,7 @@ angular.module('HomeGroupListDefinition', [])
columnClass: 'col-lg-3 col-md3 col-sm-2',
linkTo: "\{\{ '/#/inventories/' + group.inventory + '/' \}\}"
},
failed_hosts: {
/*failed_hosts: {
label: 'Failed Hosts',
ngHref: "\{\{ group.failed_hosts_link \}\}",
badgeIcon: "\{\{ 'fa icon-failures-' + group.failed_hosts_class \}\}",
@ -46,8 +46,8 @@ angular.module('HomeGroupListDefinition', [])
searchable: false,
excludeModal: true,
sortField: "hosts_with_active_failures"
},
status: {
},*/
/*status: {
label: 'Status',
ngClick: "viewUpdateStatus(\{\{ group.id \}\})",
searchType: 'select',
@ -65,14 +65,14 @@ angular.module('HomeGroupListDefinition', [])
{ name: "updating", value: "updating" }],
sourceModel: 'inventory_source',
sourceField: 'status'
},
last_updated: {
},*/
/*last_updated: {
label: 'Last<br>Updated',
sourceModel: 'inventory_source',
sourceField: 'last_updated',
searchable: false,
nosort: false
},
},*/
source: {
label: 'Source',
searchType: 'select',
@ -113,8 +113,55 @@ angular.module('HomeGroupListDefinition', [])
searchOnly: true
}
},
fieldActions: {
sync_status: {
mode: 'all',
ngClick: "viewUpdateStatus(group.id, group.group_id)",
awToolTip: "\{\{ group.status_tooltip \}\}",
ngClass: "group.status_class",
dataPlacement: "top"
},
failed_hosts: {
mode: 'all',
awToolTip: "{{ group.hosts_status_tip }}",
dataPlacement: "top",
ngHref: "/#/inventories/{{ group.inventory }}/",
iconClass: "{{ 'fa icon-failures-' + group.hosts_status_class }}"
},
group_update: {
//label: 'Sync',
mode: 'all',
ngClick: 'updateGroup(\{\{ group.id \}\})',
awToolTip: "\{\{ group.launch_tooltip \}\}",
ngShow: "(group.status !== 'running' && group.status !== 'pending' && group.status !== 'updating')",
ngClass: "group.launch_class",
dataPlacement: "top"
},
cancel: {
//label: 'Cancel',
mode: 'all',
ngClick: "cancelUpdate(\{\{ group.id \}\})",
awToolTip: "Cancel sync process",
'class': 'red-txt',
ngShow: "(group.status == 'running' || group.status == 'pending' || group.status == 'updating')",
dataPlacement: "top"
},
edit: {
label: 'Edit',
mode: 'all',
ngClick: "editGroup(group.id)",
awToolTip: 'Edit group',
dataPlacement: "top"
}
},
actions: {
refresh: {
mode: 'all',
awToolTip: "Refresh the page",
ngClick: "refresh()"
},
stream: {
ngClick: "showActivity()",
awToolTip: "View Activity Stream",

View File

@ -33,30 +33,6 @@ angular.module('HomeHostListDefinition', [])
columnClass: 'col-lg-3 col-md3 col-sm-2',
linkTo: "\{\{ '/#/inventories/' + host.inventory \}\}"
},
active_failures: {
label: 'Job Status',
ngHref: "\{\{ host.activeFailuresLink \}\}",
awToolTip: "\{\{ host.badgeToolTip \}\}",
dataPlacement: 'top',
badgeNgHref: '\{\{ host.activeFailuresLink \}\}',
badgeIcon: "\{\{ 'fa icon-failures-' + host.has_active_failures \}\}",
badgePlacement: 'left',
badgeToolTip: "\{\{ host.badgeToolTip \}\}",
badgeTipPlacement: 'top',
searchable: false,
nosort: true
},
enabled_flag: {
label: 'Enabled',
badgeIcon: "\{\{ 'fa icon-enabled-' + host.enabled \}\}",
badgePlacement: 'left',
badgeToolTip: "\{\{ host.enabledToolTip \}\}",
badgeTipPlacement: "top",
badgeTipWatch: "host.enabledToolTip",
ngClick: "toggle_host_enabled(\{\{ host.id \}\}, \{\{ host.has_inventory_sources \}\})",
searchable: false,
showValue: false
},
enabled: {
label: 'Disabled?',
searchSingleValue: true,
@ -83,7 +59,35 @@ angular.module('HomeHostListDefinition', [])
searchOnly: true
}
},
fieldActions: {
enabled_flag: {
//label: 'Enabled',
iconClass: "{{ 'fa icon-enabled-' + host.enabled }}",
dataPlacement: 'top',
awToolTip: "{{ host.enabledToolTip }}",
dataTipWatch: "host.enabledToolTip",
ngClick: "toggleHostEnabled(host.id, host.has_inventory_sources)"
},
active_failures: {
//label: 'Job Status',
//ngHref: "\{\{'/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + inventory_id \}\}",
awPopOver: "{{ host.job_status_html }}",
dataTitle: "{{ host.job_status_title }}",
awToolTip: "{{ host.badgeToolTip }}",
awTipPlacement: 'top',
dataPlacement: 'left',
iconClass: "{{ 'fa icon-failures-' + host.has_active_failures }}"
},
edit: {
label: 'Edit',
ngClick: "editHost(host.id)",
icon: 'icon-edit',
awToolTip: 'Edit host',
dataPlacement: 'top'
}
},
actions: {
stream: {
ngClick: "showActivity()",

View File

@ -4,7 +4,6 @@
* Inventories.js
* List view object for Inventories data model.
*
*
*/
angular.module('InventoriesListDefinition', [])
.value(
@ -31,50 +30,22 @@ angular.module('InventoriesListDefinition', [])
sourceField: 'name',
excludeModal: true
},
/*failed_hosts: {
label: 'Failures',
ngHref: "\{\{ inventory.failed_hosts_link \}\}",
badgeIcon: "\{\{ 'fa icon-failures-' + inventory.failed_hosts_class \}\}",
badgeNgHref: "\{\{ inventory.failed_hosts_link \}\}",
badgePlacement: 'left',
badgeToolTip: "\{\{ inventory.failed_hosts_tip \}\}",
badgeTipPlacement: 'top',
awToolTip: "\{\{ inventory.failed_hosts_tip \}\}",
dataPlacement: "top",
searchable: false,
excludeModal: true,
sortField: "hosts_with_active_failures"
},
status: {
label: 'Status',
ngHref: "\{\{ inventory.status_link \}\}",
badgeIcon: "\{\{ 'fa fa-cloud icon-cloud-' + inventory.status_class \}\}",
badgeNgHref: "\{\{ inventory.status_link \}\}",
badgePlacement: 'left',
badgeTipPlacement: 'top',
badgeToolTip: "\{\{ inventory.status_tip \}\}",
awToolTip: "\{\{ inventory.status_tip \}\}",
dataPlacement: "top",
searchable: false,
excludeModal: true,
sortField: "inventory_sources_with_failures"
},*/
has_inventory_sources: {
label: 'Cloud sourced',
label: 'Cloud sourced?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
searchOnly: true
},
has_active_failures: {
label: 'Failed hosts',
label: 'Failed hosts?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
searchOnly: true
},
inventory_sources_with_failures: {
label: 'Sync failures',
label: 'Sync failures?',
searchType: 'gtzero',
searchValue: 'true',
searchOnly: true

View File

@ -47,37 +47,32 @@ angular.module('InventoryHostsDefinition', [])
searchOnly: true
},
has_active_failures: {
label: 'Has failed jobs?',
label: 'Failed jobs?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
searchOnly: true
}
/* ,
has_inventory_sources: {
label: 'Has external source?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
searchOnly: true
}*/
},
fieldActions: {
enabled_flag: {
//label: 'Enabled',
iconClass: "\{\{ 'fa icon-enabled-' + host.enabled \}\}",
iconClass: "{{ 'fa icon-enabled-' + host.enabled }}",
dataPlacement: 'top',
awToolTip: "\{\{ host.enabledToolTip \}\}",
awToolTip: "{{ host.enabledToolTip }}",
dataTipWatch: "host.enabledToolTip",
ngClick: "toggleHostEnabled(\{\{ host.id \}\}, \{\{ host.has_inventory_sources \}\})"
ngClick: "toggleHostEnabled(host.id, host.has_inventory_sources)"
},
active_failures: {
//label: 'Job Status',
ngHref: "\{\{'/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + inventory_id \}\}",
awToolTip: "\{\{ host.badgeToolTip \}\}",
dataPlacement: 'top',
iconClass: "\{\{ 'fa icon-failures-' + host.has_active_failures \}\}"
//ngHref: "\{\{'/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + inventory_id \}\}",
awPopOver: "{{ host.job_status_html }}",
dataTitle: "{{ host.job_status_title }}",
awToolTip: "{{ host.badgeToolTip }}",
awTipPlacement: 'top',
dataPlacement: 'left',
iconClass: "{{ 'fa icon-failures-' + host.has_active_failures }}"
},
edit: {
//label: 'Edit',

View File

@ -127,10 +127,6 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti
url += 'inventories/' + obj.id + '/';
break;
default:
console.log('here');
console.log('url: ' + url);
console.log('base: ' + obj.base);
console.log('id: ' + obj.id);
url += obj.base + 's/' + obj.id + '/';
}
return url;

View File

@ -7,9 +7,6 @@
*
*/
@import "animations.less";
@black: #171717;
@white: #FFF;
@warning: #FF9900;
@ -28,6 +25,11 @@
@tip-background: #0088CC;
@tip-color: #fff;
@import "animations.less";
@import "jquery-ui-overrides.less";
html, body { height: 100%; }
html {
@ -280,7 +282,7 @@ td.actions {
border-color: #ccc;
}
.ssh-key-field {
.ssh-key-field, .mono-space {
font-family: Fixed, monospace;
}
@ -1297,12 +1299,6 @@ tr td button i {
font-weight: bold;
}
.ui-dialog-titlebar.ui-widget-header {
color: #0088cc;
background-color: #F0F0F0;
background-image: none;
}
/* Activity Stream Widget */

View File

@ -0,0 +1,69 @@
/*********************************************
* Copyright (c) 2014 AnsibleWorks, Inc.
*
* jquery-ui-overrides.less
*
* Additions to the custom-theme to make things
* look closer to Twitter Bootstrap
*
*/
/* Modal dialog */
.ui-dialog-title {
font-size: 22px;
color: @blue;
font-weight: bold;
line-height: normal;
}
.ui-dialog {
.close {
font-size: 18px;
font-weight: bold;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1;
opacity: .7;
text-shadow: 0 1px 0 @white;
}
.ui-widget-header {
border-radius: 0;
border: none;
border-bottom: 1px solid #A9A9A9;
height: 55px;
}
.ui-dialog-titlebar {
padding-bottom: 0;
padding-top: 12px;
}
.ui-dialog-titlebar .ui-state-default {
background-image: none;
background-color: @white;
border-color: @white;
color: #A9A9A9;
}
.mono-space {
font-family: Fixed, monospace;
}
textarea.resizable {
resize: vertical;
}
.ui-resizable-se {
right: 5px;
bottom: 5px;
background-position: -80px -224px;
color: @black;
}
}
.ui-dialog-buttonset {
button.btn.btn-primary.ui-state-hover,
button.btn.btn-primary.ui-state-active {
background-image: none;
color: @white;
background-color: @blue-dark;
border-color: #285e8e;
text-decoration: none;
font-weight: normal;
}
}

View File

@ -8,7 +8,7 @@
var INTEGER_REGEXP = /^\-?\d*$/;
angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'JobsHelper'])
// awpassmatch: Add to password_confirm field. Will test if value
// matches that of 'input[name="password"]'
.directive('awpassmatch', function() {
@ -277,7 +277,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
* Include the standard TB data-XXX attributes to controll the pop-over's appearance. We will
* default placement to the left, delay to 0 seconds, content type to HTML, and title to 'Help'.
*/
.directive('awPopOver', function() {
.directive('awPopOver', ['$compile', function($compile) {
return function(scope, element, attrs) {
var placement = (attrs.placement != undefined && attrs.placement != null) ? attrs.placement : 'left';
var title = (attrs.title != undefined && attrs.title != null) ? attrs.title : 'Help';
@ -289,26 +289,34 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
var e = $(this);
$('.help-link, .help-link-white').each( function(index) {
if (me != $(this).attr('id')) {
$(this).popover('hide');
$(this).popover('hide');
}
});
$('.popover').each(function(index) {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$(this).popover('toggle');
$('.popover').each(function(index) {
$compile($(this))(scope); //make nested directives work!
});
});
$(document).bind('keydown', function(e) {
if (e.keyCode === 27) {
$(element).popover('hide');
$('.popover').each(function(index) {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$(element).popover('hide');
$('.popover').each(function(index) {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
}
});
}
})
}])
//
// Enable jqueryui slider widget on a numeric input field

View File

@ -60,7 +60,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
this.modal = (options.modal) ? true : false;
this.setForm(form);
if (options.html) {
if (options.html) {
element.html(options.html);
}
else {

View File

@ -148,16 +148,6 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "<div class=\"row\">\n";
if (list.name != 'groups') {
// // Inventory groups
// html += "<div class=\"inventory-title col-lg-5\">" + list.editTitle + "</div>\n";
//}
//else if (list.name == 'hosts') {
// html += "<div class=\"col-lg-4\">\n";
// html += "<span class=\"hosts-title\">{{ selected_group_name }}</span>";
// html += "</div><!-- col-lg-4 -->";
// html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: 'col-lg-5',
// searchWidgets: list.searchWidgets });
//}
if (options.searchSize) {
html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: options.searchSize,
searchWidgets: list.searchWidgets });
@ -347,9 +337,13 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "<a ";
html += (fAction.href) ? "href=\"" + fAction.href + "\" " : "";
html += (fAction.ngHref) ? "ng-href=\"" + fAction.ngHref + "\" " : "";
html += (action == 'cancel') ? " class=\"cancel red-txt\" " : "";
html += (action == 'cancel') ? "class=\"cancel red-txt\" " : "";
html += (fAction.awPopOver) ? "aw-pop-over=\"" + fAction.awPopOver + "\" " : "";
html += (fAction.dataPlacement) ? Attr(fAction, 'dataPlacement') : "";
html += (fAction.dataTitle) ? Attr(fAction, 'dataTitle') : "";
for (itm in fAction) {
if (itm != 'ngHref' && itm != 'href' && itm != 'label' && itm != 'icon' && itm != 'class' && itm != 'iconClass') {
if (itm != 'ngHref' && itm != 'href' && itm != 'label' && itm != 'icon' && itm != 'class' &&
itm != 'iconClass' && itm != "dataPlacement" && itm != "awPopOver" && itm != "dataTitle") {
html += Attr(fAction, itm);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,4 @@
<div class="tab-pane" id="home">
<div ng-cloak id="htmlTemplate"></div>
<div id="inventory-modal-container"></div>
</div>

View File

@ -4,12 +4,11 @@
<meta charset="utf-8">
<title>AnsibleWorks AWX</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/custom-theme/jquery-ui-1.10.3.custom.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/custom-theme/jquery-ui-1.10.3.custom.min.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.min.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/font-awesome.min.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/select2.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/select2-bootstrap.css" />
{% if settings.USE_MINIFIED_JS %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/awx.min.css" />
@ -75,6 +74,7 @@
<script src="{{ STATIC_URL }}js/forms/HostGroups.js"></script>
<script src="{{ STATIC_URL }}js/forms/InventoryStatus.js"></script>
<script src="{{ STATIC_URL }}js/forms/ActivityDetail.js"></script>
<script src="{{ STATIC_URL }}js/forms/JobSummary.js"></script>
<script src="{{ STATIC_URL }}js/lists/Users.js"></script>
<script src="{{ STATIC_URL }}js/lists/Organizations.js"></script>
<script src="{{ STATIC_URL }}js/lists/Admins.js"></script>
@ -383,11 +383,13 @@
</div>
</div><!-- site footer -->
<script src="{{ STATIC_URL }}lib/jquery/jquery-ui-1.10.3.custom.min.js"></script>
<!-- <script src="{{ STATIC_URL }}lib/jquery/jquery-ui-1.10.3.custom.min.js"></script> -->
<script src="{{ STATIC_URL }}lib/twitter/bootstrap.min.js"></script>
<script src="{{ STATIC_URL }}lib/js-yaml/js-yaml.min.js"></script>
<script src="{{ STATIC_URL }}lib/md5/jquery.md5.js"></script>
<script src="{{ STATIC_URL }}lib/select2/select2.js"></script>
<script src="{{ STATIC_URL }}lib/jquery/jquery-ui-1.10.4.custom.min.js"></script>
<script>
$('a[data-toggle="tab"]').on('show.bs.tab', function (e) {