AC-570 Added host enabled flag on Hosts page, allowing one-click editing for manually managed hosts. Fixed one more bug with prepending '*' to labels of required fileds.

This commit is contained in:
Chris Houseknecht
2013-11-06 08:09:02 +00:00
parent 1497d128f3
commit 49a8de4efe
7 changed files with 130 additions and 53 deletions

View File

@@ -13,7 +13,7 @@ function InventoryHosts ($scope, $rootScope, $compile, $location, $log, $routePa
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit,
RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt, RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt,
GetBasePath, HostsList, HostsAdd, HostsEdit, HostsDelete, GetBasePath, HostsList, HostsAdd, HostsEdit, HostsDelete,
HostsReload, BuildTree, EditHostGroups, InventoryHostsHelp, HelpDialog) HostsReload, BuildTree, EditHostGroups, InventoryHostsHelp, HelpDialog, Wait)
{ {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope. //scope.
@@ -33,11 +33,11 @@ function InventoryHosts ($scope, $rootScope, $compile, $location, $log, $routePa
// buildAllGroups emits from TreeSelector.js after the inventory object is ready // buildAllGroups emits from TreeSelector.js after the inventory object is ready
if (scope.loadBreadCrumbsRemove) { if (scope.loadBreadCrumbsRemove) {
scope.loadBreadCrumbsRemove(); scope.loadBreadCrumbsRemove();
} }
scope.loadBreadCrumbsRemove = scope.$on('buildAllGroups', function(e, inventory_name) { scope.loadBreadCrumbsRemove = scope.$on('buildAllGroups', function(e, inventory_name) {
LoadBreadCrumbs({ path: '/inventories/' + id, title: inventory_name }); LoadBreadCrumbs({ path: '/inventories/' + id, title: inventory_name });
}); });
scope.filterHosts = function() { scope.filterHosts = function() {
HostsReload({ scope: scope, inventory_id: scope['inventory_id'], group_id: scope['group_id'] }); HostsReload({ scope: scope, inventory_id: scope['inventory_id'], group_id: scope['group_id'] });
@@ -75,6 +75,39 @@ function InventoryHosts ($scope, $rootScope, $compile, $location, $log, $routePa
scope.allJobs = function(id) { scope.allJobs = function(id) {
$location.url('/jobs/?job_host_summaries__host=' + id); $location.url('/jobs/?job_host_summaries__host=' + id);
} }
scope.toggle_host_enabled = function(id, sources) {
var host;
var i;
if (!sources) {
// Host is not managed by an external source
Wait('start');
for (i=0; i < scope.hosts.length; i++) {
if (scope.hosts[i].id == id) {
scope.hosts[i].enabled = (scope.hosts[i].enabled) ? false : true;
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
host = scope.hosts[i];
break;
}
}
Rest.setUrl(GetBasePath('hosts') + id + '/');
Rest.put(host)
.success( function(data, status, headers, config) {
Wait('stop');
})
.error( function(data, status, headers, config) {
scope.hosts[i].enabled = (scope.hosts[i].enabled) ? false : true;
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
Wait('stop');
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to update host. PUT returned status: ' + status });
});
}
else {
Alert('External Host', 'This host is part of an external inventory. It can only be enabled or disabled by the ' +
'inventory sync process.', 'alert-info');
}
}
scope.allHostSummaries = function(id, name, inventory_id) { scope.allHostSummaries = function(id, name, inventory_id) {
LoadBreadCrumbs({ path: '/hosts/' + id, title: name, altPath: '/inventories/' + inventory_id + '/hosts', LoadBreadCrumbs({ path: '/hosts/' + id, title: name, altPath: '/inventories/' + inventory_id + '/hosts',
@@ -118,15 +151,15 @@ function InventoryHosts ($scope, $rootScope, $compile, $location, $log, $routePa
scope.group_id = group; scope.group_id = group;
scope.helpCount++; scope.helpCount++;
if (scope.group_id == null) { if (scope.group_id == null) {
scope.groupTitle = 'All Hosts'; scope.groupTitle = 'All Hosts';
scope.hostAddHide = true; scope.hostAddHide = true;
scope.hostCreateHide = true; scope.hostCreateHide = true;
scope.hostDeleteHide = true; scope.hostDeleteHide = true;
} }
else { else {
scope.hostAddHide = false; scope.hostAddHide = false;
scope.hostCreateHide = false; scope.hostCreateHide = false;
scope.hostDeleteHide = false; scope.hostDeleteHide = false;
} }
scope['hostDeleteDisabled'] = true; scope['hostDeleteDisabled'] = true;
scope['hostDeleteDisabledClass'] = 'disabled'; scope['hostDeleteDisabledClass'] = 'disabled';
@@ -135,11 +168,11 @@ function InventoryHosts ($scope, $rootScope, $compile, $location, $log, $routePa
// Load the tree. See TreeSelector.js // Load the tree. See TreeSelector.js
BuildTree({ BuildTree({
scope: scope, scope: scope,
inventory_id: id, inventory_id: id,
emit_on_select: 'refreshHost', emit_on_select: 'refreshHost',
target_id: 'search-tree-container' target_id: 'search-tree-container'
}); });
} }
@@ -147,6 +180,6 @@ InventoryHosts.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$lo
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit',
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt',
'GetBasePath', 'HostsList', 'HostsAdd', 'HostsEdit', 'HostsDelete', 'GetBasePath', 'HostsList', 'HostsAdd', 'HostsEdit', 'HostsDelete',
'HostsReload', 'BuildTree', 'EditHostGroups', 'InventoryHostsHelp', 'HelpDialog' 'HostsReload', 'BuildTree', 'EditHostGroups', 'InventoryHostsHelp', 'HelpDialog', 'Wait'
]; ];

View File

@@ -39,10 +39,22 @@ angular.module('HostFormDefinition', [])
addRequired: false, addRequired: false,
editRequired: false editRequired: false
}, },
inventory: { enabled: {
type: 'hidden', label: 'Enabled?',
includeOnEdit: true, type: 'radio',
includeOnAdd: true addRequired: false,
editRequired: false,
options: [
{ label: 'Yes', value: 'true'},
{ label: 'No', value: 'false' }
],
"default": "true",
awPopOver: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that " +
"are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
dataTitle: 'Host Enabled',
dataPlacement: 'right',
dataContainer: '#form-modal .modal-content',
ngDisabled: 'has_inventory_sources == true'
}, },
variables: { variables: {
label: 'Variables', label: 'Variables',
@@ -62,6 +74,11 @@ angular.module('HostFormDefinition', [])
dataTitle: 'Host Variables', dataTitle: 'Host Variables',
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: '#form-modal .modal-content' dataContainer: '#form-modal .modal-content'
},
inventory: {
type: 'hidden',
includeOnEdit: true,
includeOnAdd: true
} }
}, },

View File

@@ -34,19 +34,16 @@ angular.module('InventoryHostsFormDefinition', [])
searchable: false, searchable: false,
nosort: true nosort: true
}, },
/*inventory_sources: { enabled_flag: {
label: 'External<br>Source?', label: 'Enabled',
ngHref: "\{\{ host.has_inv_source_link \}\}", badgeIcon: "\{\{ 'icon-enabled-' + host.enabled \}\}",
badgeNgHref: "\{\{ host.has_inv_source_link \}\}",
badgeIcon: "\{\{ 'icon-cloud-' + host.has_inventory_sources \}\}",
badgePlacement: 'left', badgePlacement: 'left',
badgeToolTip: "\{\{ host.has_inv_source_tip \}\}", badgeToolTip: "\{\{ host.enabledToolTip \}\}",
awToolTip: "\{\{ host.has_inv_source_tip \}\}", badgeTipPlacement: "top",
dataPlacement: 'top', ngClick: "toggle_host_enabled(\{\{ host.id \}\}, \{\{ host.has_inventory_sources \}\})",
badgeTipPlacement: 'top',
searchable: false, searchable: false,
nosort: true showValue: false
},*/ },
groups: { groups: {
label: 'Groups', label: 'Groups',
searchable: true, searchable: true,
@@ -54,6 +51,13 @@ angular.module('InventoryHostsFormDefinition', [])
sourceField: 'name', sourceField: 'name',
nosort: true nosort: true
}, },
enabled: {
label: 'Disabled?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'false',
searchOnly: true
},
has_active_failures: { has_active_failures: {
label: 'Has failed jobs?', label: 'Has failed jobs?',
searchSingleValue: true, searchSingleValue: true,
@@ -108,7 +112,7 @@ angular.module('InventoryHostsFormDefinition', [])
type: 'DropDown', type: 'DropDown',
label: 'Jobs', label: 'Jobs',
icon: 'icon-zoom-in', icon: 'icon-zoom-in',
"class": "btn-default btn-sm", "class": "btn-default btn-xs",
options: [ options: [
{ ngClick: "allJobs(\{\{ host.id \}\})", label: 'All', ngShow: 'host.last_job' }, { ngClick: "allJobs(\{\{ host.id \}\})", label: 'All', ngShow: 'host.last_job' },
{ ngClick: "allHostSummaries(\{\{ host.id \}\},'\{\{ host.name \}\}', \{\{ inventory_id \}\})", label: 'All summaries', { ngClick: "allHostSummaries(\{\{ host.id \}\},'\{\{ host.name \}\}', \{\{ inventory_id \}\})", label: 'All summaries',
@@ -125,7 +129,7 @@ angular.module('InventoryHostsFormDefinition', [])
"delete": { "delete": {
ngClick: "deleteHost(\{\{ host.id \}\},'\{\{ host.name \}\}')", ngClick: "deleteHost(\{\{ host.id \}\},'\{\{ host.name \}\}')",
icon: 'icon-trash', icon: 'icon-trash',
"class": 'btn-sm btn-danger', "class": 'btn-xs btn-danger',
awToolTip: 'Delete host' awToolTip: 'Delete host'
} }
} }

View File

@@ -155,6 +155,8 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
data['variables'] = JSON.stringify(json_data, undefined, '\t'); data['variables'] = JSON.stringify(json_data, undefined, '\t');
} }
data['enabled'] = (scope['enabled'] == "true") ? true : false;
Rest.setUrl(defaultUrl); Rest.setUrl(defaultUrl);
Rest.post(data) Rest.post(data)
.success( function(data, status, headers, config) { .success( function(data, status, headers, config) {
@@ -261,6 +263,8 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
relatedSets[set] = { url: related[set], iterator: form.related[set].iterator }; relatedSets[set] = { url: related[set], iterator: form.related[set].iterator };
} }
} }
scope['enabled'] = (data['enabled']) ? "true" : "false";
master['enabled'] = scope['enabled'];
scope.variable_url = data.related.variable_data; scope.variable_url = data.related.variable_data;
scope.$emit('hostLoaded'); scope.$emit('hostLoaded');
}) })
@@ -309,6 +313,8 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
data['variables'] = JSON.stringify(json_data, undefined, '\t'); data['variables'] = JSON.stringify(json_data, undefined, '\t');
} }
data['enabled'] = (scope['enabled'] == "true") ? true : false;
Rest.setUrl(defaultUrl); Rest.setUrl(defaultUrl);
Rest.put(data) Rest.put(data)
.success( function(data, status, headers, config) { .success( function(data, status, headers, config) {
@@ -430,22 +436,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
} }
} }
scope.hosts[i].groups = scope.hosts[i].groups.replace(/\, $/,''); scope.hosts[i].groups = scope.hosts[i].groups.replace(/\, $/,'');
if (scope.hosts[i].has_inventory_sources) {
scope.hosts[i].inventory_sources = 'yes';
scope.hosts[i].has_inv_source_link = '/#/inventories/' + scope['inventory_id'] + '/groups/?has_external_source=true';
scope.hosts[i].has_inv_source_tip = 'Has an external source. Click to view inventory source details.';
}
else {
scope.hosts[i].inventory_sources = 'no';
scope.hosts[i].has_inv_source_link = '/#/inventories/' + scope['inventory_id'] + '/groups';
scope.hosts[i].has_inv_source_tip = 'Has no external source.';
}
} }
// Add the value displayed in Job Status column
for (var i=0; i < scope.hosts.length; i++) { for (var i=0; i < scope.hosts.length; i++) {
// Add the value displayed in Job Status column
scope.hosts[i].activeFailuresLink = '/#/hosts/' + scope.hosts[i].id + '/job_host_summaries/?inventory=' + scope['inventory_id'] + scope.hosts[i].activeFailuresLink = '/#/hosts/' + scope.hosts[i].id + '/job_host_summaries/?inventory=' + scope['inventory_id'] +
'&host_name=' + escape(scope.hosts[i].name); '&host_name=' + escape(scope.hosts[i].name);
if (scope.hosts[i].has_active_failures == true) { if (scope.hosts[i].has_active_failures == true) {
@@ -460,7 +454,19 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
else if (scope.hosts[i].has_active_failures == false && scope.hosts[i].last_job !== null) { else if (scope.hosts[i].has_active_failures == false && scope.hosts[i].last_job !== null) {
scope.hosts[i].badgeToolTip = "Most recent job successful. Click to view jobs."; scope.hosts[i].badgeToolTip = "Most recent job successful. Click to view jobs.";
scope.hosts[i].active_failures = 'success'; scope.hosts[i].active_failures = 'success';
} }
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
if (scope.hosts[i].has_inventory_sources) {
// Inventory sync managed, so not clickable
scope.hosts[i].enabledToolTip = (scope.hosts[i].enabled) ? 'Ready! Availabe to running jobs.' :
'Out to lunch! This host is not available to running jobs.';
}
else {
// Clickable
scope.hosts[i].enabledToolTip = (scope.hosts[i].enabled) ? 'Ready! Available to running jobs. Click to toggle.' :
'Out to lunch! Host not available to running jobs. Click to toggle.';
}
} }
if (group_id == null || group_id == undefined) { if (group_id == null || group_id == undefined) {

View File

@@ -814,6 +814,22 @@ input[type="checkbox"].checkbox-no-label {
color: @red; color: @red;
} }
.icon-enabled-true:before {
content: "\f046";
}
.icon-enabled-true {
color: @green;
}
.icon-enabled-false:before {
content: "\f096";
}
.icon-enabled-false{
color: @red;
}
/* Inventory cloud sourced? indicator */ /* Inventory cloud sourced? indicator */
.icon-cloud-true:before { .icon-cloud-true:before {
content: "\f111"; content: "\f111";

View File

@@ -104,16 +104,16 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
// Prepend an asterisk to required field label // Prepend an asterisk to required field label
$('.form-control[required], input[type="radio"][required]').each(function() { $('.form-control[required], input[type="radio"][required]').each(function() {
if ( Empty($(this).attr('aw-required-when')) ) { if ( Empty($(this).attr('aw-required-when')) ) {
var label = $(this).parent().parent().find('label'); var label = $(this).parent().parent().find('label').first();
if ($(this).attr('type') == 'radio') { if ($(this).attr('type') == 'radio') {
label = $(this).parent().parent().parent().find('label'); label = $(this).parent().parent().parent().find('label').first();
} }
if (label.length > 0) { if (label.length > 0) {
var span = label.children('span'); var span = label.children('span');
if (span.length > 0 && !span.first().hasClass('prepend-asterisk')) { if (span.length > 0 && !span.first().hasClass('prepend-asterisk')) {
span.first().addClass('prepend-asterisk'); span.first().addClass('prepend-asterisk');
} }
else if (!label.first().hasClass('prepend-asterisk')) { else if (span.length <= 0 && !label.first().hasClass('prepend-asterisk')) {
label.first().addClass('prepend-asterisk'); label.first().addClass('prepend-asterisk');
} }
} }
@@ -817,6 +817,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += (field.readonly) ? "disabled " : ""; html += (field.readonly) ? "disabled " : "";
html += (options.mode == 'edit' && field.editRequired) ? "required " : ""; html += (options.mode == 'edit' && field.editRequired) ? "required " : "";
html += (options.mode == 'add' && field.addRequired) ? "required " : ""; html += (options.mode == 'add' && field.addRequired) ? "required " : "";
html += (field.ngDisabled) ? this.attr(field,'ngDisabled') : "";
html += " > " + field.options[i].label + "\n"; html += " > " + field.options[i].label + "\n";
html += "</label>\n"; html += "</label>\n";
} }

View File

@@ -373,7 +373,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
if (options.mode == 'select' && (options.selectButton == undefined || options.selectButton == true)) { if (options.mode == 'select' && (options.selectButton == undefined || options.selectButton == true)) {
html += "<div class=\"navigation-buttons\">\n"; html += "<div class=\"navigation-buttons\">\n";
html += " <button class=\"btn btn-small btn-primary pull-right\" aw-tool-tip=\"Complete your selection\" " + html += " <button class=\"btn btn-sm btn-primary pull-right\" aw-tool-tip=\"Complete your selection\" " +
"ng-click=\"finishSelection()\" ng-disabled=\"selected.length == 0\"><i class=\"icon-check\"></i> Select</button>\n"; "ng-click=\"finishSelection()\" ng-disabled=\"selected.length == 0\"><i class=\"icon-check\"></i> Select</button>\n";
html += "</div>\n"; html += "</div>\n";
} }