From c7f096f16b1a3c9c0f76f22b7fd952a31b9b8d45 Mon Sep 17 00:00:00 2001
From: chouseknecht
Date: Wed, 2 Oct 2013 17:26:47 -0400
Subject: [PATCH] AC-503 latest UI work on cloud inventory
---
awx/ui/static/css/bootstrap.css | 2 +-
awx/ui/static/js/controllers/Groups.js | 3 +-
awx/ui/static/js/controllers/Home.js | 1 -
awx/ui/static/js/helpers/Groups.js | 24 +++++-
awx/ui/static/js/helpers/JobSubmission.js | 86 ++++++++++++++++++-
awx/ui/static/js/helpers/inventory.js | 79 +++++++++++++----
awx/ui/static/js/lists/InventorySummary.js | 24 ++++--
awx/ui/static/less/ansible-ui.less | 41 ++++++++-
awx/ui/static/lib/ansible/form-generator.js | 2 +-
.../static/lib/ansible/generator-helpers.js | 64 +++++++++-----
awx/ui/static/lib/ansible/list-generator.js | 26 ++++--
11 files changed, 286 insertions(+), 66 deletions(-)
diff --git a/awx/ui/static/css/bootstrap.css b/awx/ui/static/css/bootstrap.css
index bbda4eed4a..e34f1e1710 100644
--- a/awx/ui/static/css/bootstrap.css
+++ b/awx/ui/static/css/bootstrap.css
@@ -11,7 +11,7 @@
/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
article,
-aside,
+aside,.table
details,
figcaption,
figure,
diff --git a/awx/ui/static/js/controllers/Groups.js b/awx/ui/static/js/controllers/Groups.js
index abb803b8df..7f1ef84a9c 100644
--- a/awx/ui/static/js/controllers/Groups.js
+++ b/awx/ui/static/js/controllers/Groups.js
@@ -163,7 +163,8 @@ function InventoryGroups ($scope, $rootScope, $compile, $location, $log, $routeP
scope.groupDeleteHide = true;
scope.createButtonShow = false;
scope.group_id = null;
- InventoryStatus();
+ scope.inventory_name = node.attr('name');
+ InventoryStatus({ scope: scope });
$('#tree-form').show();
}
diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js
index ae9ffdf623..afaecfbb4a 100644
--- a/awx/ui/static/js/controllers/Home.js
+++ b/awx/ui/static/js/controllers/Home.js
@@ -27,7 +27,6 @@ function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, C
$rootScope.$on('WidgetLoaded', function() {
// Once all the widget report back 'loaded', turn off Wait widget
- console.log('got here!');
loadedCount++;
if ( loadedCount == waitCount ) {
Wait('stop');
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index f56e45d06d..1ee89bea74 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -9,7 +9,7 @@
angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'GroupListDefinition',
'SearchHelper', 'PaginateHelper', 'ListGenerator', 'AuthService', 'GroupsHelper',
- 'InventoryHelper', 'SelectionHelper'
+ 'InventoryHelper', 'SelectionHelper', 'JobSubmissionHelper'
])
.factory('getSourceTypeOptions', [ function() {
@@ -233,9 +233,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}])
.factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
- 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshGroupName', 'ParseTypeChange', 'getSourceTypeOptions',
+ 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshGroupName', 'ParseTypeChange', 'getSourceTypeOptions', 'InventoryUpdate',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
- GetBasePath, RefreshGroupName, ParseTypeChange, getSourceTypeOptions) {
+ GetBasePath, RefreshGroupName, ParseTypeChange, getSourceTypeOptions, InventoryUpdate) {
return function(params) {
var group_id = params.group_id;
@@ -348,7 +348,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope[fld] = data[fld];
master[fld] = scope[fld];
}
- }
+ }
+ scope['group_update_url'] = data.related['update'];
})
.error( function(data, status, headers, config) {
scope.source = null;
@@ -564,6 +565,21 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
scope[form.name + '_form'].$setDirty();
}
+
+ // Start the update process
+ scope.updateGroup = function() {
+ if (scope['source'] == null || scope['source'] == '') {
+ Alert('Missing Configuration', 'The selected group is not configured for updates. You must first edit the group, provide Source settings, ' +
+ 'and then run an update.', 'alert-info');
+ }
+ else {
+ InventoryUpdate({
+ scope: scope,
+ group_id: group_id,
+ url: scope['group_update_url']
+ });
+ }
+ }
// Cancel
scope.formReset = function() {
diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js
index 653b0574c7..14dd7aed1d 100644
--- a/awx/ui/static/js/helpers/JobSubmission.js
+++ b/awx/ui/static/js/helpers/JobSubmission.js
@@ -5,7 +5,7 @@
*
*/
angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition',
- 'LookUpHelper', 'ProjectFormDefinition', 'JobSubmissionHelper'])
+ 'LookUpHelper', 'ProjectFormDefinition', 'JobSubmissionHelper', 'GroupFormDefinition'])
.factory('PromptPasswords', ['CredentialForm', 'JobTemplateForm', 'ProjectsForm', '$compile', 'Rest', '$location', 'ProcessErrors', 'GetBasePath',
'Alert',
@@ -350,7 +350,89 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
- { hdr: 'Error!', msg: 'Failed to get job template details. GET returned status: ' + status });
+ { hdr: 'Error!', msg: 'Failed to get project update details: ' + url + ' GET status: ' + status });
+ });
+ };
+ }])
+
+
+ // Sumbit Inventory Update request
+ .factory('InventoryUpdate',['PromptPasswords', '$compile', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'GroupForm',
+ function(PromptPasswords, $compile, Rest, $location, GetBasePath, ProcessErrors, Alert, GroupForm) {
+ return function(params) {
+
+ var scope = params.scope;
+ var inventory_id = params.inventory_id;
+ var url = params.url;
+
+ if (scope.removeUpdateSubmitted) {
+ scope.removeUpdateSubmitted();
+ }
+ scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() {
+ // Refresh the project list after update request submitted
+ scope.refresh();
+ });
+
+ if (scope.removeInventorySubmit) {
+ scope.removeInventorySubmit();
+ }
+ scope.removeInventorySubmit = scope.$on('InventorySubmit', function(e, passwords_needed_to_update, extra_html) {
+ // After the call to update, kick off the job.
+ PromptPasswords({
+ scope: scope,
+ passwords: passwords_needed_to_update,
+ start_url: url,
+ form: GroupForm,
+ extra_html: extra_html
+ });
+ });
+
+ // Check to see if we have permission to perform the update and if any passwords are needed
+ Rest.setUrl(url);
+ Rest.get()
+ .success( function(data, status, headers, config) {
+ if (data.can_update) {
+ var extra_html = '';
+ /*
+ for (var i=0; i < scope.projects.length; i++) {
+ if (scope.projects[i].id == project_id) {
+ extra_html += "\n";
+ if (scope.projects[i].scm_username) {
+ extra_html += "\n";
+ }
+ break;
+ }
+ }
+ extra_html += "
";
+ */
+ scope.$emit('InventorySubmit', data.passwords_needed_to_update, extra_html);
+ }
+ else {
+ Alert('Permission Denied', 'You do not have access to run the update. Please contact your system administrator.',
+ 'alert-danger');
+ }
+ })
+ .error( function(data, status, headers, config) {
+ ProcessErrors(scope, data, status, null,
+ { hdr: 'Error!', msg: 'Failed to get inventory_source details. ' + url + 'GET status: ' + status });
});
};
}]);
diff --git a/awx/ui/static/js/helpers/inventory.js b/awx/ui/static/js/helpers/inventory.js
index 9682f9a80e..3f7809ffb2 100644
--- a/awx/ui/static/js/helpers/inventory.js
+++ b/awx/ui/static/js/helpers/inventory.js
@@ -669,42 +669,82 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}
}])
- .factory('InventoryStatus', [ 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventorySummary', 'GenerateList',
- function(Rest, Aler, ProcessErrors, GetBasePath, FormatDate, InventorySummary, GenerateList) {
+ .factory('InventoryStatus', [ '$rootScope', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventorySummary',
+ 'GenerateList', 'ClearScope',
+ function($rootScope, Rest, Aler, ProcessErrors, GetBasePath, FormatDate, InventorySummary, GenerateList, ClearScope) {
return function(params) {
//Build a summary of a given inventory
-
+
+ ClearScope('tree-form');
+
$('#tree-form').hide().empty();
var view = GenerateList;
var scope = view.inject(InventorySummary, { mode: 'summary', id: 'tree-form', breadCrumbs: false });
- scope['groups'] = [];
+ var total;
+ var cnt=0;
+ var groups = new Array();
+
+ if (scope.RemoveGroupsLoaded) {
+ scope.RemoveGroupsLoaded();
+ }
+ scope.RemoveGroupsLoaded = scope.$on('GroupsLoaded', function() {
+ // Using this sort of indirect method of updating scope['groups'] works. Pushing data directly into
+ // scope['groups'] directly causes it to grow exponentially with duplicates each time user navigates away
+ // and comes back -despite emptying/resetting/initializing.
+ scope['groups'] = groups;
+ if (!scope.$$phase) {
+ scope.$apply();
+ }
+ });
function checkSource(url) {
Rest.setUrl(url);
Rest.get()
.success( function(data, status, headers, config) {
- //console.log(data);
- var last_update = (data.last_updated == null) ? 'n/a' : FormatDate(new Date(data.last_updated));
+
+ var last_update = (data.last_updated == null) ? '' : FormatDate(new Date(data.last_updated));
var source = 'Manual';
+ var stat;
+
+ switch (data.status) {
+ case 'never updated':
+ stat = 'never';
+ break;
+ case 'none':
+ stat = 'na';
+ break;
+ default:
+ stat = data.status;
+ }
+
switch(data.source) {
- case 'file':
- source = 'File';
- break;
- case 'ec2':
- source = 'Amazon EC2';
- break;
- case 'rackspace':
- source = 'Rackspace';
- break;
- }
- scope['groups'].push({
+ case 'file':
+ source = 'File';
+ break;
+ case 'ec2':
+ source = 'Amazon EC2';
+ break;
+ case 'rackspace':
+ source = 'Rackspace';
+ break;
+ }
+
+ groups.push({
name: data.summary_fields.group.name,
description: data.summary_fields.group.description,
failures: data.summary_fields.group.hosts_with_active_failures,
source: source,
last_update: last_update,
- status: data.status
+ status: stat,
+ has_active_failures: data.summary_fields.group.has_active_failures
});
+
+ cnt++;
+
+ if (cnt >= total) {
+ scope.$emit('GroupsLoaded');
+ }
+
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
@@ -716,11 +756,12 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
Rest.setUrl(url);
Rest.get()
.success( function(data, status, headers, config) {
+ total = data.count;
for (var i=0; i < data.results.length; i++) {
if (data.results[i].related.inventory_source) {
checkSource(data.results[i].related.inventory_source);
}
- }
+ }
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
diff --git a/awx/ui/static/js/lists/InventorySummary.js b/awx/ui/static/js/lists/InventorySummary.js
index 905320fa56..3f8745b136 100644
--- a/awx/ui/static/js/lists/InventorySummary.js
+++ b/awx/ui/static/js/lists/InventorySummary.js
@@ -12,26 +12,40 @@ angular.module('InventorySummaryDefinition', [])
name: 'groups',
iterator: 'group',
- editTitle: 'Inventory Summary',
+ editTitle: 'Inventory Summary: {{ inventory_name }}',
+ showTitle: true,
+ well: false,
index: false,
hover: true,
fields: {
name: {
key: true,
- label: 'Name'
+ label: 'Group',
+ noLink: true,
+ badges: [
+ { //Active Failures
+ icon: "\{\{ 'icon-failures-' + group.has_active_failures \}\}",
+ toolTip: 'Indicates if inventory contains hosts with active failures',
+ toolTipPlacement: 'bottom'
+ },
+ { //Cloud Status
+ icon: "\{\{ 'icon-cloud-' + group.status \}\}",
+ toolTip: 'Indicates if inventory contains hosts with active failures',
+ toolTipPlacement: 'bottom'
+ }]
},
failures: {
- label: 'Hosts Failures'
+ label: 'Active
Failures'
},
source: {
label: 'Source'
},
last_update: {
- label: 'Last Update'
+ label: 'Last
Updated'
},
status: {
- label: 'Update Status'
+ label: 'Update
Status'
}
},
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index 1b6d5d5cba..3acfc60036 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -10,6 +10,7 @@
@black: #171717;
@warning: #FF9900;
@red: #FF0000;
+@green: #5bb75b;
@blue: #1778c3; /* logo blue */
@blue-link: #0088cc;
@grey: #A9A9A9;
@@ -591,6 +592,19 @@ select.field-mini-height {
background-color: #dff0d8;
}
+.table-summary thead > tr > th,
+.table-summary tbody > tr > th,
+.table-summary tfoot > tr > th,
+.table-summary thead > tr > td,
+.table-summary tbody > tr > td,
+.table-summary tfoot > tr > td {
+ border-top: 1px solid #ccc;
+}
+
+.table-summary thead > tr > th {
+ border-bottom: 1px solid #ccc;
+}
+
/* Jobs pages */
.job-error,
@@ -646,6 +660,31 @@ select.field-mini-height {
content: "\f111";
}
+
+/* Cloud inventory status. i.e. inventory_source.status values */
+
+ .icon-cloud-na:before,
+ .icon-cloud-never:before,
+ .icon-cloud-updating:before,
+ .icon-cloud-failed:before,
+ .icon-cloud-success:before {
+ content: "\f0c2";
+ }
+ .icon-cloud-na {
+ color: #e3e3e3;
+ }
+ .icon-cloud-never {
+ color: #888;
+ }
+ .icon-cloud-updating,
+ .icon-cloud-success {
+ color: #5bb75b;
+ }
+ .icon-cloud-failed {
+ color: @red;
+ }
+
+
.field-success {
color: #5bb75b;
}
@@ -813,7 +852,7 @@ select.field-mini-height {
background-color: #ccc;
height: 1px;
margin-top: 5px;
- margin-bottom: 30px;
+ margin-bottom: 15px;
}
}
diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js
index 0cc4c70e50..20ccb59ad0 100644
--- a/awx/ui/static/lib/ansible/form-generator.js
+++ b/awx/ui/static/lib/ansible/form-generator.js
@@ -1220,7 +1220,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "\n";
html += "\n";
html += "