From 6bf8f9cd980ef6addf3c3f4ecf8a62be819c983d Mon Sep 17 00:00:00 2001 From: chouseknecht Date: Thu, 3 Oct 2013 05:26:16 -0400 Subject: [PATCH] AC-503 latest cloud inventory changes --- awx/ui/static/js/controllers/Groups.js | 6 + awx/ui/static/js/helpers/Groups.js | 112 ++++++++++++++- awx/ui/static/js/helpers/JobSubmission.js | 53 +++----- awx/ui/static/js/helpers/inventory.js | 101 -------------- awx/ui/static/js/helpers/search.js | 12 +- awx/ui/static/js/lists/InventorySummary.js | 70 ++++++++-- awx/ui/static/less/ansible-ui.less | 31 ++--- .../static/lib/ansible/generator-helpers.js | 4 +- awx/ui/static/lib/ansible/list-generator.js | 128 ++++++++++-------- 9 files changed, 293 insertions(+), 224 deletions(-) diff --git a/awx/ui/static/js/controllers/Groups.js b/awx/ui/static/js/controllers/Groups.js index 7f1ef84a9c..482683417e 100644 --- a/awx/ui/static/js/controllers/Groups.js +++ b/awx/ui/static/js/controllers/Groups.js @@ -128,6 +128,12 @@ function InventoryGroups ($scope, $rootScope, $compile, $location, $log, $routeP var type = node.attr('type'); var url; + if ($rootScope.timer) { + // Kill any linger timers from the inventory summary page + clearInterval($rootScope.timer); + $rootScope.timer = null; + } + scope['nodeSelectValue'] = n; scope['selectedNode'] = node; scope['selectedNodeName'] = node.attr('name'); diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 1ee89bea74..fe9778c143 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', 'JobSubmissionHelper' + 'InventoryHelper', 'SelectionHelper', 'JobSubmissionHelper', 'RefreshHelper' ]) .factory('getSourceTypeOptions', [ function() { @@ -56,7 +56,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' GetBasePath('inventory') + inventory_id + '/groups/'; SelectionInit({ scope: scope, list: list, url: url }); - var finish = scope.formModalAction; + scope.formModalAction = function() { var groups = []; for (var j=0; j < scope.selected.length; j++) { @@ -93,6 +93,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' } } } + + var finish = scope.formModalAction; if (scope.PostRefreshRemove) { scope.PostRefreshRemove(); @@ -123,6 +125,112 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' }]) + .factory('InventoryStatus', [ '$rootScope', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventorySummary', + 'GenerateList', 'ClearScope', 'SearchInit', 'PaginateInit', 'Refresh', 'InventoryUpdate', + function($rootScope, Rest, Alert, ProcessErrors, GetBasePath, FormatDate, InventorySummary, GenerateList, ClearScope, SearchInit, + PaginateInit, Refresh, InventoryUpdate) { + return function(params) { + //Build a summary of a given inventory + + ClearScope('tree-form'); + + $('#tree-form').hide().empty(); + var view = GenerateList; + var list = InventorySummary; + var scope = view.inject(InventorySummary, { mode: 'summary', id: 'tree-form', breadCrumbs: false }); + var defaultUrl = GetBasePath('inventory') + scope['inventory_id'] + '/inventory_sources/'; + + if (scope.PostRefreshRemove) { + scope.PostRefreshRemove(); + } + scope.PostRefreshRemove = scope.$on('PostRefresh', function() { + for (var i=0; i < scope.groups.length; i++) { + var last_update = (scope.groups[i].last_updated == null) ? '' : FormatDate(new Date(scope.groups[i].last_updated)); + var source = 'Manual'; + var stat; + var stat_class; + + switch (scope.groups[i].status) { + case 'never updated': + stat = 'never'; + stat_class = 'never'; + break; + case 'none': + stat = 'n/a'; + stat_class = 'na'; + break; + default: + stat = scope.groups[i].status; + stat_class = stat; + } + + switch (scope.groups[i].source) { + case 'file': + source = 'File'; + break; + case 'ec2': + source = 'Amazon EC2'; + break; + case 'rackspace': + source = 'Rackspace'; + break; + } + scope.groups[i].status = stat; + scope.groups[i].source = source; + scope.groups[i].last_updated = last_update; + scope.groups[i].status_class = stat_class; + } + }); + + SearchInit({ scope: scope, set: 'groups', list: list, url: defaultUrl }); + PaginateInit({ scope: scope, list: list, url: defaultUrl }); + scope.search(list.iterator); + + scope.refresh = function() { + scope['groupSearchSpin'] = true; + scope['groupLoading'] = true; + Refresh({ scope: scope, set: 'groups', iterator: 'group', url: scope['current_url'] }); + } + + // Start the update process + scope.updateGroup = function(id) { + for (var i=0; i < scope.groups.length; i++) { + if (scope.groups[i].id == id) { + if (scope.groups[i].source == "" || scope.groups[i].source == null) { + 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 if (scope.groups[i].status == 'updating') { + Alert('Update in Progress', 'The inventory update process is currently running for group ' + + scope.groups[i].summary_fields.group.name + '. Use the Refresh button to monitor the status.', 'alert-info'); + } + else { + if (scope.groups[i].source == 'Amazon EC2') { + scope.sourceUsernameLabel = 'Access Key ID'; + scope.sourcePasswordLabel = 'Secret Access Key'; + scope.sourcePasswordConfirmLabel = 'Confirm Secret Access Key'; + } + else { + scope.sourceUsernameLabel = 'Username'; + scope.sourcePasswordLabel = 'Password'; + scope.sourcePasswordConfirmLabel = 'Confirm Password'; + } + InventoryUpdate({ + scope: scope, + group_id: id, + url: scope.groups[i].related.update, + group_name: scope.groups[i].summary_fields.group.name, + group_source: scope.groups[i].source + }); + } + break; + } + } + } + } + }]) + + .factory('GroupsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', 'ParseTypeChange', 'GroupsEdit', function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index 14dd7aed1d..26bc1a2b2a 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', 'GroupFormDefinition']) + 'LookUpHelper', 'ProjectFormDefinition', 'JobSubmissionHelper', 'GroupFormDefinition', 'GroupsHelper' ]) .factory('PromptPasswords', ['CredentialForm', 'JobTemplateForm', 'ProjectsForm', '$compile', 'Rest', '$location', 'ProcessErrors', 'GetBasePath', 'Alert', @@ -107,7 +107,9 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential fld = passwords[i]; scope[fld] = ''; html += "
\n"; - html += "' + "\n"; + html += "\n"; html += "
\n"; html += "\n"; - html += "' + "\n"; + html += "\n"; html += "
\n"; html += "\n"; - extra_html += "\n"; - extra_html += "
\n"; - extra_html += "\n"; - extra_html += "\n"; - extra_html += "
\n"; - extra_html += "Starting inventory update for the " + params.group_name + + " group. Please provide the " + params.group_source + " credentials:
\n"; scope.$emit('InventorySubmit', data.passwords_needed_to_update, extra_html); } else { diff --git a/awx/ui/static/js/helpers/inventory.js b/awx/ui/static/js/helpers/inventory.js index 3f7809ffb2..04ba32fcab 100644 --- a/awx/ui/static/js/helpers/inventory.js +++ b/awx/ui/static/js/helpers/inventory.js @@ -667,106 +667,5 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi } return newData; } - }]) - - .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 }); - 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) { - - 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; - } - - 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: 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, - { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST status: ' + status }); - }); - } - - var url = GetBasePath('inventory') + scope['inventory_id'] + '/groups/'; - 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, - { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST status: ' + status }); - }); - } }]); diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js index 6bf83023f0..f599c1712e 100644 --- a/awx/ui/static/js/helpers/search.js +++ b/awx/ui/static/js/helpers/search.js @@ -125,6 +125,11 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].sourceModel + '__' + list.fields[scope[iterator + 'SearchField']].sourceField + '__'; } + else if ( (list.fields[scope[iterator + 'SearchField']].searchType == 'select') && + (scope[iterator + 'SearchSelectValue'].value == '' || + scope[iterator + 'SearchSelectValue'].value == null) ) { + scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField']; + } else { scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__'; } @@ -138,6 +143,11 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero' ) { scope[iterator + 'SearchParams'] += 'gt=0'; } + else if ( (list.fields[scope[iterator + 'SearchField']].searchType == 'select') && + (scope[iterator + 'SearchSelectValue'].value == '' || + scope[iterator + 'SearchSelectValue'].value == null) ) { + scope[iterator + 'SearchParams'] += '='; + } else { scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType'] + '='; } @@ -145,7 +155,7 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) if ( list.fields[scope[iterator + 'SearchField']].searchType && (list.fields[scope[iterator + 'SearchField']].searchType == 'boolean' || list.fields[scope[iterator + 'SearchField']].searchType == 'select') ) { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value; + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value; } else { //if ( list.fields[scope[iterator + 'SearchField']].searchType == undefined || diff --git a/awx/ui/static/js/lists/InventorySummary.js b/awx/ui/static/js/lists/InventorySummary.js index 3f8745b136..782d74237a 100644 --- a/awx/ui/static/js/lists/InventorySummary.js +++ b/awx/ui/static/js/lists/InventorySummary.js @@ -23,36 +23,88 @@ angular.module('InventorySummaryDefinition', []) key: true, label: 'Group', noLink: true, + ngBind: "group.summary_fields.group.name", + sourceModel: 'group', + sourceField: 'name', badges: [ { //Active Failures - icon: "\{\{ 'icon-failures-' + group.has_active_failures \}\}", + icon: "\{\{ 'icon-failures-' + group.summary_fields.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', + icon: "\{\{ 'icon-cloud-' + group.status_class \}\}", + toolTip: 'Indicates status of inventory update process', toolTipPlacement: 'bottom' }] }, failures: { - label: 'Active
Failures' + label: 'Active
Failures', + ngBind: "group.summary_fields.group.hosts_with_active_failures", + sourceModel: 'group', + sourceField: 'hosts_with_active_failures', + searchField: 'group__has_active_failures', + searchType: 'boolean', + searchOptions: [{ name: "yes", value: 1 }, { name: "no", value: 0 }] }, source: { - label: 'Source' + label: 'Source', + searchType: 'select', + searchOptions: [ + { name: "Amazon EC2", value: "ec2" }, + { name: "Local Script", value: "file" }, + { name: "Manual", value: "" }, + { name: "Rackspace", value: "rackspace" }] }, - last_update: { - label: 'Last
Updated' + last_updated: { + label: 'Last
Updated', + searchable: false }, status: { - label: 'Update
Status' + label: 'Update
Status', + searchType: 'select', + searchOptions: [ + { name: "failed", value: "failed" }, + { name: "never", value: "never updated" }, + { name: "n/a", value: "none" }, + { name: "successful", value: "successful" }, + { name: "updating", value: "updating" }] } }, - actions: { + actions: { + refresh: { + awRefresh: true, + mode: 'all' + }, + help: { + awPopOver: + "
\n" + + "
failed
Errors were encountered with the most recent inventory update.
\n" + + "
n/a
The group is not configured for inventory update.
\n" + + "
never
The inventory update has never run for the group.
\n" + + "
successful
The most recent inventory update ran to completion without incident.
\n" + + "
updating
The inventory update is currently running.
\n" + + "
\n", + dataPlacement: 'left', + dataContainer: 'body', + icon: "icon-question-sign", + mode: 'all', + 'class': 'btn-xs btn-info btn-help pull-right', + awToolTip: 'Click for help', + dataTitle: 'Update Status', + iconSize: 'large' + } }, fieldActions: { + group_update: { + label: 'Update', + icon: 'icon-cloud-download', + "class": 'btn-xs btn-success', + ngClick: 'updateGroup(\{\{ group.id \}\})', + awToolTip: 'Perform an update on this group' + } } }); \ No newline at end of file diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 3acfc60036..d0319614f8 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -667,7 +667,7 @@ select.field-mini-height { .icon-cloud-never:before, .icon-cloud-updating:before, .icon-cloud-failed:before, - .icon-cloud-success:before { + .icon-cloud-successful:before { content: "\f0c2"; } .icon-cloud-na { @@ -779,6 +779,16 @@ select.field-mini-height { /* Inventory-> Groups */ + .inventory-passwd-msg { + font-size: 14px; + margin-bottom: 25px; + margin-left: 10px; + } + + .groups-issue { + margin-bottom: 10px; + } + .inventory-content { padding: 15px; border: 1px solid #ddd; @@ -792,7 +802,6 @@ select.field-mini-height { } .tree-view-container { - min-height: 650px; padding: 0 0 10px 0; .col-lg-4 { @@ -821,23 +830,9 @@ select.field-mini-height { } } - /* - #tree-form:before { - content: ""; - border-color: transparent transparent #e3e3e3 transparent; - border-style: solid; - border-width: 15px; - width: 0; - height: 0; - position: relative; - top: -34px; - left: 28px; - } - */ - #tree-form { display: none; - padding: 15px 10px 0 10px; + padding: 15px 10px 10px 10px; margin-top: 5px; border: 1px solid #e3e3e3; background-color: #e3e3e3; @@ -845,7 +840,7 @@ select.field-mini-height { .form-title { color: #888; - padding-left: 10px; + padding-left: 0; } hr { diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js index 273fd0381d..24bc0fa461 100644 --- a/awx/ui/static/lib/ansible/generator-helpers.js +++ b/awx/ui/static/lib/ansible/generator-helpers.js @@ -462,8 +462,8 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) for ( var fld in form.fields) { if (form.fields[fld].searchable == undefined || form.fields[fld].searchable == true) { html += "
  • " - + form.fields[fld].label + "
  • \n"; + html += fld + "','" + form.fields[fld].label.replace(/\/g,' ') + "')\">" + + form.fields[fld].label.replace(/\/g,' ') + "\n"; } } html += "\n"; diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index b5673f1d70..cca55168bb 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -177,58 +177,80 @@ angular.module('ListGenerator', ['GeneratorHelpers']) html += "
    \n"; html += "
    \n"; } - - if (options.mode !== 'summary') { - - if ( options.mode == 'lookup' || options.id != undefined ) { - html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: 'col-lg-8' }); - } - else { - html += SearchWidget({ iterator: list.iterator, template: list, mini: true }); - } - if (options.mode != 'lookup') { - //actions - var base = $location.path().replace(/^\//,'').split('/')[0]; - html += "
    \n"; - for (action in list.actions) { - if (list.actions[action].mode == 'all' || list.actions[action].mode == options.mode) { - if ( (list.actions[action].basePaths == undefined) || - (list.actions[action].basePaths && list.actions[action].basePaths.indexOf(base) > -1) ) { - html += this.button(list.actions[action], action); - } - } - } - - if (list.name == 'inventories' && options.mode !== 'select') { - html += "\n"; - } - - //select instructions - if (options.mode == 'select' && list.selectInstructions) { - var btn = { - awPopOver: list.selectInstructions, - dataPlacement: 'left', - dataContainer: 'body', - icon: "icon-question-sign", - 'class': 'btn-sm btn-help btn-info', - awToolTip: 'Click for help', - dataTitle: 'Help', - iconSize: 'large' - }; - html += this.button(btn, 'select'); - } - } - else { - html += "
    \n"; - } - - html += "
    \n"; - html += "
    \n"; + /* + if (list.editTitle.match(/^Inventory Summary/)) { + html += "
    \n"; + html += "
    \n"; + html += "\n"; + html += "
    \n"; + html += "
    \n"; + } + */ + + if (options.mode == 'summary') { + html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: 'col-lg-6' }); } + else if (options.mode == 'lookup' || options.id != undefined) { + html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: 'col-lg-8' }); + } + else { + html += SearchWidget({ iterator: list.iterator, template: list, mini: true }); + } + + if (options.mode != 'lookup') { + //actions + var base = $location.path().replace(/^\//,'').split('/')[0]; + + html += "
    \n"; + for (action in list.actions) { + if (list.actions[action].mode == 'all' || list.actions[action].mode == options.mode) { + if ( (list.actions[action].basePaths == undefined) || + (list.actions[action].basePaths && list.actions[action].basePaths.indexOf(base) > -1) ) { + html += this.button(list.actions[action], action); + } + } + } + + if (list.name == 'inventories' && options.mode !== 'select') { + html += "\n"; + } + + //select instructions + if (options.mode == 'select' && list.selectInstructions) { + var btn = { + awPopOver: list.selectInstructions, + dataPlacement: 'left', + dataContainer: 'body', + icon: "icon-question-sign", + 'class': 'btn-sm btn-help btn-info', + awToolTip: 'Click for help', + dataTitle: 'Help', + iconSize: 'large' + }; + html += this.button(btn, 'select'); + } + } + else { + html += "
    \n"; + } + + html += "
    \n"; + html += "
    \n"; + // table header row html += ""; } - else if (options.mode == 'edit') { + else if (options.mode == 'edit' || options.mode == 'summary') { // Row level actions html += "
    "; for (action in list.fieldActions) { @@ -362,9 +384,7 @@ angular.module('ListGenerator', ['GeneratorHelpers']) html += PaginateWidget({ set: list.name, iterator: list.iterator, mini: true, mode: 'lookup' }); } else { - if (options.mode !== 'summary') { - html += PaginateWidget({ set: list.name, iterator: list.iterator, mini: true }); - } + html += PaginateWidget({ set: list.name, iterator: list.iterator, mini: true }); } return html;