Refactor Inventories > Inventory Manage module (#2013)

* refactor InventoryManage modules, resolves #1748, #1354, #1754, #1960, #1961, #1962, #1963, #1965

* refactor InventoryManage modules, resolves #1748, #1354, #1754, #1960, #1961, #1962, #1963, #1965

* instantiate lookupfields with correct values / endpoint queries #1979

* fix groups/host list sort and remove some unnecessary service abstractions #1979

* fix childless delete wording

* add host > toggle enabled/disabled

* fix breadcrumb indexing

* remove extra required asterisk from inventory script lookup field

* fix delete prompt styling

* remove group badge linking, add toolip

* full width host var form, fix add/edit group inventory source vars

* restrict copy/moving multiselect to one item

* break word on host/group names and support multiple rows of breakcrumbs

* hide trailing breadcrumb slash at root group view

* fix failing unit test dependency path, hook up system tracking

* fix ec2 group by label rendering in group > edit view, fix add/edit/adhoc breadcrumb display when breadcrumbs are 2+ rows

* fix select spacing on copy/move groups

* limit group/host/copyMove search to current context

* refresh status icons on page/search event

* move status indicators to left side of lists

* fix typo in select2 init

* fix assorted typos

* $track by statement needs to tail | filterBy:xyz

* fix JSHint failure

* PR feedback, hook up filter for hosts with active failures, hook up status tooltips/popovers
This commit is contained in:
Leigh 2016-05-24 19:16:00 -04:00
parent 0f742fb7a7
commit bef61f4003
73 changed files with 1832 additions and 5154 deletions

View File

@ -1,14 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../shared/template-url/template-url.factory';
export default {
route: '/adhoc',
name: 'inventoryManage.adhoc',
templateUrl: templateUrl('adhoc/adhoc'),
controller: 'adhocController'
};

View File

@ -1,10 +0,0 @@
import route from './adhoc.route';
import adhocController from './adhoc.controller';
import form from './adhoc.form';
export default angular.module('adhoc', [])
.controller('adhocController', adhocController)
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}])
.factory('adhocForm', form);

View File

@ -47,7 +47,6 @@ import browserData from './browser-data/main';
import dashboard from './dashboard/main';
import moment from './shared/moment/main';
import templateUrl from './shared/template-url/main';
import adhoc from './adhoc/main';
import login from './login/main';
import activityStream from './activity-stream/main';
import standardOut from './standard-out/main';
@ -67,7 +66,6 @@ import './shared/Modal';
import './shared/prompt-dialog';
import './shared/directives';
import './shared/filters';
import './shared/InventoryTree';
import './shared/Socket';
import './shared/features/main';
import './login/authenticationServices/pendo/ng-pendo';
@ -99,7 +97,6 @@ var tower = angular.module('Tower', [
dashboard.name,
moment.name,
templateUrl.name,
adhoc.name,
login.name,
activityStream.name,
footer.name,
@ -169,7 +166,6 @@ var tower = angular.module('Tower', [
'StreamWidget',
'JobsHelper',
'InventoryGroupsHelpDefinition',
'InventoryTree',
'CredentialsHelper',
'StreamListDefinition',
'HomeGroupListDefinition',
@ -224,6 +220,7 @@ var tower = angular.module('Tower', [
// route to the details pane of /job/:id/host-event/:eventId if no other child specified
$urlRouterProvider.when('/jobs/*/host-event/*', '/jobs/*/host-event/*/details');
// $urlRouterProvider.otherwise("/home");
$urlRouterProvider.otherwise(function($injector){
var $state = $injector.get("$state");

View File

@ -61,18 +61,10 @@ export default
label: 'Source',
type: 'select',
ngOptions: 'source.label for source in source_type_options track by source.value',
ngChange: 'sourceChange()',
ngChange: 'sourceChange(source)',
addRequired: false,
editRequired: false
},
source_path: {
label: 'Script Path',
ngShow: "source && source.value == 'file'",
type: 'text',
awRequiredWhen: {
reqExpression: "sourcePathRequired",
init: "false"
}
editRequired: false,
ngModel: 'source'
},
credential: {
label: 'Cloud Credential',
@ -147,7 +139,6 @@ export default
},
inventory_script: {
label : "Custom Inventory Script",
labelClass: 'prepend-asterisk',
type: 'lookup',
ngShow: "source && source.value === 'custom'",
sourceModel: 'inventory_script',
@ -157,7 +148,8 @@ export default
editRequired: true,
ngRequired: "source && source.value === 'custom'",
},
extra_vars: {
custom_variables: {
id: 'custom_variables',
label: 'Environment Variables', //"{{vars_label}}" ,
ngShow: "source && source.value=='custom' ",
type: 'textarea',
@ -176,9 +168,10 @@ export default
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n",
dataContainer: 'body'
},
source_vars: {
ec2_variables: {
id: 'ec2_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value == 'file' || source.value == 'ec2')",
ngShow: "source && source.value == 'ec2'",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
addRequired: false,
@ -200,11 +193,11 @@ export default
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
inventory_variables: {
vmware_variables: {
id: 'vmware_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value == 'vmware' || " +
"source.value == 'openstack')",
ngShow: "source && source.value == 'vmware'",
type: 'textarea',
addRequired: false,
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
@ -226,6 +219,32 @@ export default
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
openstack_variables: {
id: 'openstack_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && source.value == 'openstack'",
type: 'textarea',
addRequired: false,
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
editRequird: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: "Source Variables",
dataPlacement: 'right',
awPopOver: "<p>Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration " +
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/openstack.yml\" target=\"_blank\">" +
"view openstack.yml in the Ansible github repo.</a></p>" +
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body'
},
checkbox_group: {
label: 'Update Options',
type: 'checkbox_group',
@ -295,12 +314,12 @@ export default
},
buttons: {
save: {
ngClick: 'formSave()'
},
cancel: {
ngClick: 'formCancel()'
},
save: {
ngClick: 'saveGroup()'
}
}
},
related: {

View File

@ -60,7 +60,7 @@ export default
addRequired: false,
editRequird: false,
rows: 6,
"class": "modal-input-xlarge Form-textArea",
"class": "modal-input-xlarge Form-textArea Form-formGroup--fullWidth",
"default": "---",
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
"JSON:<br />\n" +

File diff suppressed because it is too large Load Diff

View File

@ -217,489 +217,6 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
};
}])
.factory('InjectHosts', ['generateList', 'InventoryHosts', 'HostsReload',
function(GenerateList, InventoryHosts, HostsReload) {
return function(params) {
var group_scope = params.group_scope,
host_scope = params.host_scope,
inventory_id = params.inventory_id,
group_id = params.group_id,
pageSize = params.pageSize,
generator = GenerateList;
// Inject the list html
generator.inject(InventoryHosts, { scope: host_scope, mode: 'edit', id: 'host-list-container', searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12' });
// Load data
HostsReload({ scope: host_scope, group_id: group_id, inventory_id: inventory_id, parent_scope: group_scope, pageSize: pageSize });
};
}])
.factory('HostsList', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostList', 'generateList',
'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'HostsAdd', 'HostsReload', 'SelectionInit',
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostList, GenerateList, Prompt, SearchInit,
PaginateInit, ProcessErrors, GetBasePath, HostsAdd, HostsReload, SelectionInit) {
return function(params) {
var inventory_id = params.inventory_id,
group_id = params.group_id,
list = HostList,
generator = GenerateList,
defaultUrl, scope;
list.iterator = 'subhost'; //Override the iterator and name so the scope of the modal dialog
list.name = 'subhosts'; //will not conflict with the parent scope
scope = generator.inject(list, {
id: 'form-modal-body',
mode: 'select',
selectButton: false
});
defaultUrl = GetBasePath('inventory') + inventory_id + '/hosts/?not__groups__id=' + scope.group_id;
scope.formModalActionLabel = 'Select';
scope.formModalHeader = 'Add Existing Hosts';
scope.formModalCancelShow = true;
SelectionInit({ scope: scope, list: list, url: GetBasePath('groups') + group_id + '/hosts/' });
if (scope.removeModalClosed) {
scope.removeModalClosed();
}
scope.removeModalClosed = scope.$on('modalClosed', function() {
// if the modal closed, assume something got changed and reload the host list
HostsReload(params);
});
$('.popover').popover('hide'); //remove any lingering pop-overs
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
$('#form-modal').modal({ backdrop: 'static', keyboard: false });
SearchInit({ scope: scope, set: 'subhosts', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl, mode: 'lookup' });
scope.search(list.iterator);
if (!scope.$$phase) {
scope.$digest();
}
scope.createHost = function() {
$('#form-modal').modal('hide');
HostsAdd({ scope: params.scope, inventory_id: inventory_id, group_id: group_id });
};
};
}])
.factory('HostsCreate', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'ToJSON',
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, HostsReload, ParseTypeChange, Wait, ToJSON) {
return function(params) {
var parent_scope = params.scope,
inventory_id = parent_scope.inventory_id,
group_id = parent_scope.selected_group_id,
defaultUrl = GetBasePath('groups') + group_id + '/hosts/',
form = HostForm,
generator = GenerateForm,
scope = generator.inject(form, {mode: 'add', modal: true, related: false}),
master={};
scope.formModalActionLabel = 'Save';
scope.formModalHeader = 'Create New Host';
scope.formModalCancelShow = true;
scope.parseType = 'yaml';
ParseTypeChange({ scope: scope, field_id: 'host_variables' });
if (scope.removeHostsReload) {
scope.removeHostsReload();
}
scope.removeHostsReload = scope.$on('hostsReload', function() {
HostsReload(params);
});
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
//$('#form-modal').unbind('hidden');
//$('#form-modal').on('hidden', function () { scope.$emit('hostsReload'); });
generator.reset();
master={};
if (!scope.$$phase) {
scope.$digest();
}
if (scope.removeHostSaveComplete) {
scope.removeHostSaveComplete();
}
scope.removeHostSaveComplete = scope.$on('HostSaveComplete', function() {
Wait('stop');
$('#form-modal').modal('hide');
HostsReload({
scope: parent_scope,
group_id: parent_scope.selected_group_id,
tree_id: parent_scope.selected_tree_id,
inventory_id: parent_scope.inventory_id
});
});
// Save
scope.formModalAction = function() {
Wait('start');
var fld, data={};
scope.formModalActionDisabled = true;
data.variables = ToJSON(scope.parseType, scope.variables, true);
for (fld in form.fields) {
if (fld !== 'variables') {
data[fld] = scope[fld];
}
}
data.inventory = inventory_id;
Rest.setUrl(defaultUrl);
Rest.post(data)
.success( function() {
scope.$emit('HostSaveComplete');
})
.error( function(data, status) {
Wait('stop');
scope.formModalActionDisabled = false;
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to add new host. POST returned status: ' + status });
});
};
// Cancel
scope.formReset = function() {
// Defaults
generator.reset();
};
scope.cancelModal = function() {
};
};
}])
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis',
'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize',
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON,
ParseVariableString, CreateDialog, TextareaResize) {
return function(params) {
var parent_scope = params.host_scope,
group_scope = params.group_scope,
host_id = params.host_id,
inventory_id = params.inventory_id,
mode = params.mode, // 'add' or 'edit'
selected_group_id = params.selected_group_id,
generator = GenerateForm,
form = HostForm,
defaultUrl,
scope = parent_scope.$new(),
master = {},
relatedSets = {},
buttons, url, form_scope;
form_scope =
generator.inject(HostForm, { mode: 'edit', id: 'host-modal-dialog', related: false, scope: scope });
generator.reset();
buttons = [{
label: "Cancel",
onClick: function() {
scope.cancelModal();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "host-cancel-button"
},{
label: "Save",
onClick: function() {
scope.saveModal();
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "host-save-button"
}];
CreateDialog({
scope: scope,
buttons: buttons,
width: 675,
height: 750,
minWidth: 400,
title: 'Host Properties',
id: 'host-modal-dialog',
closeOnEscape: false,
form: form_scope.host_form,
onClose: function() {
Wait('stop');
scope.codeMirror.destroy();
$('#host-modal-dialog').empty();
},
onResizeStop: function() {
TextareaResize({
scope: scope,
textareaId: 'host_variables',
modalId: 'host-modal-dialog',
formId: 'host_form'
});
},
beforeDestroy: function() {
if (scope.codeMirror) {
scope.codeMirror.destroy();
}
$('#host-modal-dialog').empty();
},
onOpen: function() {
$('#host_name').focus();
},
callback: 'HostEditDialogReady'
});
scope.parseType = 'yaml';
if (scope.hostVariablesLoadedRemove) {
scope.hostVariablesLoadedRemove();
}
scope.hostVariablesLoadedRemove = scope.$on('hostVariablesLoaded', function() {
$('#host-modal-dialog').dialog('open');
setTimeout(function() {
TextareaResize({
scope: scope,
textareaId: 'host_variables',
modalId: 'host-modal-dialog',
formId: 'host_form',
parse: true
});
}, 300);
//ParseTypeChange({ scope: scope, field_id: 'host_variables', onReady: callback });
});
Wait('start');
// Retrieve detail record and prepopulate the form
if (mode === 'edit') {
defaultUrl = GetBasePath('hosts') + host_id + '/';
Rest.setUrl(defaultUrl);
Rest.get()
.success( function(data) {
var set, fld, related;
for (fld in form.fields) {
if (data[fld]) {
scope[fld] = data[fld];
master[fld] = scope[fld];
}
}
related = data.related;
for (set in form.related) {
if (related[set]) {
relatedSets[set] = { url: related[set], iterator: form.related[set].iterator };
}
}
scope.variable_url = data.related.variable_data;
scope.has_inventory_sources = data.has_inventory_sources;
scope.$emit('hostVariablesLoaded');
})
.error( function(data, status) {
ProcessErrors(parent_scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve host: ' + host_id + '. GET returned status: ' + status });
});
}
else {
if (selected_group_id) {
// adding hosts to a group
url = GetBasePath('groups') + selected_group_id + '/';
} else {
// adding hosts to the top-level (inventory)
url = GetBasePath('inventory') + inventory_id + '/';
}
// Add mode
Rest.setUrl(url);
Rest.get()
.success( function(data) {
scope.has_inventory_sources = data.has_inventory_sources;
scope.enabled = true;
scope.variables = '---';
defaultUrl = data.related.hosts;
scope.$emit('hostVariablesLoaded');
})
.error( function(data, status) {
ProcessErrors(parent_scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve group: ' + selected_group_id + '. GET returned status: ' + status });
});
}
if (scope.removeSaveCompleted) {
scope.removeSaveCompleted();
}
scope.removeSaveCompleted = scope.$on('saveCompleted', function() {
try {
$('#host-modal-dialog').dialog('close');
}
catch(err) {
// ignore
}
if (group_scope && group_scope.refreshHosts) {
group_scope.refreshHosts();
}
if (parent_scope.refreshHosts) {
parent_scope.refreshHosts();
}
scope.$destroy();
});
// Save changes to the parent
scope.saveModal = function() {
Wait('start');
var fld, data={};
try {
data.variables = ToJSON(scope.parseType, scope.variables, true);
for (fld in form.fields) {
data[fld] = scope[fld];
}
data.inventory = inventory_id;
Rest.setUrl(defaultUrl);
if (mode === 'edit') {
Rest.put(data)
.success( function() {
scope.$emit('saveCompleted');
})
.error( function(data, status) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status });
});
}
else {
Rest.post(data)
.success( function() {
scope.$emit('saveCompleted');
})
.error( function(data, status) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to create host. POST returned status: ' + status });
});
}
}
catch(e) {
// ignore. ToJSON will have already alerted the user
}
};
// Cancel
scope.formReset = function() {
generator.reset();
for (var fld in master) {
scope[fld] = master[fld];
}
scope.parseType = 'yaml';
};
scope.cancelModal = function() {
try {
$('#host-modal-dialog').dialog('close');
}
catch(err) {
// ignore
}
scope.$destroy();
};
};
}])
.factory('HostsDelete', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'Wait',
function($rootScope, $location, $log, $stateParams, Rest, Alert, Prompt, ProcessErrors, GetBasePath, HostsReload, Wait) {
return function(params) {
// Remove the selected host from the current group by disassociating
var action_to_take, body,
scope = params.parent_scope,
host_id = params.host_id,
host_name = params.host_name,
group,
url_list = [];
if (scope.selected_group_id) {
//group = Find({ list: parent_scope.groups, key: 'id', val: parent_scope.selected_group_id });
//getChildren(group.id);
url_list.push(GetBasePath('groups') + scope.selected_group_id + '/hosts/');
}
else {
url_list.push(GetBasePath('inventory') + scope.inventory.id + '/hosts/');
}
if (scope.removeHostsReload) {
scope.removeHostsReload();
}
scope.removeHostsReload = scope.$on('hostsReload', function() {
$('#prompt-modal').modal('hide');
scope.refreshHosts();
});
$('#prompt-modal').on('hidden.bs.modal', function(){ Wait('stop'); });
action_to_take = function() {
var count=0, i;
Wait('start');
if (scope.removeHostRemoved) {
scope.removeHostRemoved();
}
scope.removeHostRemoved = scope.$on('hostRemoved', function(){
count++;
if (count === url_list.length) {
Wait('start');
scope.$emit('hostsReload');
}
});
for(i=0; i < url_list.length; i++) {
Rest.setUrl(url_list[i]);
Rest.post({ id: host_id, disassociate: 1 })
.success( function() {
scope.$emit('hostRemoved');
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Attempt to delete ' + host_name + ' failed. DELETE returned status: ' + status });
});
}
};
body = (group) ? '<div class=\"Prompt-bodyQuery\"><p>Are you sure you want to remove the host below from group ' + group.name + '?' +
' It will still be part of the inventory and available in All Hosts.</p></div><div class=\"Prompt-bodyTarget\">' + host_name + '</div>' :
'<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + host_name + '</div>';
Prompt({
hdr: 'Delete Host',
body: body,
action: action_to_take,
actionText: 'DELETE'
});
};
}])
.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList', 'SearchInit',
'PaginateInit',
function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit) {
@ -933,304 +450,4 @@ return function(params) {
};
}])
.factory('EditHostGroups', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'GenerateForm', 'Prompt',
'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait',
function($rootScope, $location, $log, $stateParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload,
ParseTypeChange, Wait) {
return function(params) {
var host_id = params.host_id,
inventory_id = params.inventory_id,
generator = GenerateForm,
actions = [],
i, html, defaultUrl, scope, postAction;
html = "<div class=\"row host-groups\">\n";
html += "<div class=\"col-lg-6\">\n";
html += "<label>Available Groups:</label>\n";
html += "<select multiple class=\"form-control\" name=\"available-groups\" ng-model=\"selectedGroups\" ng-change=\"leftChange()\" " +
"ng-options=\"avail_group.name for avail_group in available_groups\"></select>\n";
html += "</div>\n";
html += "<div class=\"col-lg-6\">\n";
html += "<label>Belongs to Groups:</label>\n";
html += "<select multiple class=\"form-control\" name=\"selected-groups\" ng-model=\"assignedGroups\" ng-change=\"rightChange()\" " +
"ng-options=\"host_group.name for host_group in host_groups\"></select>\n";
html += "</div>\n";
html += "</div>\n";
html += "<div class=\"row host-group-buttons\">\n";
html += "<div class=\"col-lg-12\">\n";
html += "<button type=\"button\" ng-click=\"moveLeft()\" class=\"btn btn-sm btn-primary left-button\" ng-disabled=\"leftButtonDisabled\">" +
"<i class=\"icon-arrow-left\"></i></button>\n";
html += "<button type=\"button\" ng-click=\"moveRight()\" class=\"btn btn-sm btn-primary right-button\" ng-disabled=\"rightButtonDisabled\">" +
"<i class=\"icon-arrow-right\"></i></button>\n";
html += "<p>(move selected groups)</p>\n";
html += "</div>\n";
html += "</div>\n";
defaultUrl = GetBasePath('hosts') + host_id + '/';
scope = generator.inject(null, { mode: 'edit', modal: true, related: false, html: html });
for (i=0; i < scope.hosts.length; i++) {
if (scope.hosts[i].id === host_id) {
scope.host = scope.hosts[i];
}
}
scope.selectedGroups = null;
scope.assignedGroups = null;
scope.leftButtonDisabled = true;
scope.rightButtonDisabled = true;
scope.formModalActionLabel = 'Save';
//scope.formModalHeader = 'Host Groups';
scope.formModalHeader = scope.host.name + ' - <span class=\"subtitle\">Groups</span>';
scope.formModalCancelShow = true;
scope.formModalActionDisabled = true;
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
if (scope.hostGroupChangeRemove) {
scope.hostGroupChangeRemove();
}
scope.hostGroupChangeRemove = scope.$on('hostGroupChange', function() {
actions.pop();
if (actions.length === 0) {
postAction = function() {
setTimeout(function() { Wait('stop'); }, 500);
};
HostsReload({ scope: scope, inventory_id: inventory_id, group_id: scope.group_id , action: postAction });
}
});
// Save changes
scope.formModalAction = function() {
var i, j, found;
$('#form-modal').modal('hide');
Wait('start');
// removed host from deleted groups
for (i=0; i < scope.original_groups.length; i++) {
found = false;
for (j=0; j < scope.host_groups.length; j++) {
if (scope.original_groups[i].id === scope.host_groups[j].id) {
found = true;
}
}
if (!found) {
// group was removed
actions.push({ group_id: scope.original_groups[i].id , action: 'delete' });
Rest.setUrl(GetBasePath('groups') + scope.original_groups[i].id + '/hosts/');
Rest.post({ id: host_id, disassociate: 1 })
.success( function() {
scope.$emit('hostGroupChange');
})
.error( function(data, status) {
scope.$emit('hostGroupChange');
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Attempt to remove host from group ' + scope.original_groups[i].name +
' failed. POST returned status: ' + status });
});
}
}
// add host to new groups
for (i=0; i < scope.host_groups.length; i++) {
found = false;
for (j=0; j < scope.original_groups.length; j++) {
if (scope.original_groups[j].id === scope.host_groups[i].id) {
found = true;
}
}
if (!found) {
// group was added
actions.push({ group_id: scope.host_groups[i].id , action: 'add' });
Rest.setUrl(GetBasePath('groups') + scope.host_groups[i].id + '/hosts/');
Rest.post(scope.host)
.success( function() {
scope.$emit('hostGroupChange');
})
.error( function(data, status) {
scope.$emit('hostGroupChange');
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Attempt to add host to group ' + scope.host_groups[i].name +
' failed. POST returned status: ' + status });
});
}
}
};
scope.leftChange = function() {
// Select/deselect on available groups list
if (scope.selectedGroups !== null && scope.selectedGroups.length > 0) {
scope.assignedGroups = null;
scope.leftButtonDisabled = true;
scope.rightButtonDisabled = false;
}
else {
scope.rightButtonDisabled = true;
}
};
scope.rightChange = function() {
// Select/deselect made on host groups list
if (scope.assignedGroups !== null && scope.assignedGroups.length > 0) {
scope.selectedGroups = null;
scope.leftButtonDisabled = false;
scope.rightButtonDisabled = true;
}
else {
scope.leftButtonDisabled = true;
}
};
scope.moveLeft = function() {
// Remove selected groups from the list of assigned groups
var i, j, found, placed;
for (i=0; i < scope.assignedGroups.length; i++){
for (j=0 ; j < scope.host_groups.length; j++) {
if (scope.host_groups[j].id === scope.assignedGroups[i].id) {
scope.host_groups.splice(j,1);
break;
}
}
}
for (i=0; i < scope.assignedGroups.length; i++){
found = false;
for (j=0; j < scope.available_groups.length && !found; j++){
if (scope.available_groups[j].id === scope.assignedGroups[i].id) {
found=true;
}
}
if (!found) {
placed = false;
for (j=0; j < scope.available_groups.length && !placed; j++){
if (j === 0 && scope.assignedGroups[i].name.toLowerCase() < scope.available_groups[j].name.toLowerCase()) {
// prepend to the beginning of the array
placed=true;
scope.available_groups.unshift(scope.assignedGroups[i]);
}
else if (j + 1 < scope.available_groups.length) {
if (scope.assignedGroups[i].name.toLowerCase() > scope.available_groups[j].name.toLowerCase() &&
scope.assignedGroups[i].name.toLowerCase() < scope.available_groups[j + 1].name.toLowerCase() ) {
// insert into the middle of the array
placed = true;
scope.available_groups.splice(j + 1, 0, scope.assignedGroups[i]);
}
}
}
if (!placed) {
// append to the end of the array
scope.available_groups.push(scope.assignedGroups[i]);
}
}
}
scope.assignedGroups = null;
scope.leftButtonDisabled = true;
scope.rightButtonDisabled = true;
scope.formModalActionDisabled = false;
};
scope.moveRight = function() {
// Remove selected groups from list of available groups
var i, j, found, placed;
for (i=0; i < scope.selectedGroups.length; i++){
for (j=0 ; j < scope.available_groups.length; j++) {
if (scope.available_groups[j].id === scope.selectedGroups[i].id) {
scope.available_groups.splice(j,1);
break;
}
}
}
for (i=0; i < scope.selectedGroups.length; i++){
found = false;
for (j=0; j < scope.host_groups.length && !found; j++){
if (scope.host_groups[j].id === scope.selectedGroups[i].id) {
found=true;
}
}
if (!found) {
placed = false;
for (j=0; j < scope.host_groups.length && !placed; j++) {
if (j === 0 && scope.selectedGroups[i].name.toLowerCase() < scope.host_groups[j].name.toLowerCase()) {
// prepend to the beginning of the array
placed=true;
scope.host_groups.unshift(scope.selectedGroups[i]);
}
else if (j + 1 < scope.host_groups.length) {
if (scope.selectedGroups[i].name.toLowerCase() > scope.host_groups[j].name.toLowerCase() &&
scope.selectedGroups[i].name.toLowerCase() < scope.host_groups[j + 1].name.toLowerCase() ) {
// insert into the middle of the array
placed = true;
scope.host_groups.splice(j + 1, 0, scope.selectedGroups[i]);
}
}
}
if (!placed) {
// append to the end of the array
scope.host_groups.push(scope.selectedGroups[i]);
}
}
}
scope.selectedGroups = null;
scope.leftButtonDisabled = true;
scope.rightButtonDisabled = true;
scope.formModalActionDisabled = false;
};
// Load the host's current list of groups
scope.host_groups = [];
scope.original_groups = [];
scope.available_groups = [];
Rest.setUrl(scope.host.related.groups + '?order_by=name');
Rest.get()
.success( function(data) {
var i, j, found;
for (i=0; i < data.results.length; i++) {
scope.host_groups.push({
id: data.results[i].id,
name: data.results[i].name,
description: data.results[i].description
});
scope.original_groups.push({
id: data.results[i].id,
name: data.results[i].name,
description: data.results[i].description
});
}
for (i=0; i < scope.inventory_groups.length; i++) {
found = false;
for (j=0; j < scope.host_groups.length; j++) {
if (scope.inventory_groups[i].id === scope.host_groups[j].id) {
found = true;
}
}
if (!found) {
scope.available_groups.push(scope.inventory_groups[i]);
}
}
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to get current groups for host: ' + host_id + '. GET returned: ' + status });
});
if (scope.removeHostsReload) {
scope.removeHostsReload();
}
scope.removeHostsReload = scope.$on('hostsReload', function() {
HostsReload(params);
});
if (!scope.$$phase) {
scope.$digest();
}
};
}]);
}]);

View File

@ -156,126 +156,7 @@ export default
};
}
])
.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,
generator = GenerateForm,
form = JobSummary,
scope, ww, wh, x, y, maxrows, url, html;
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);
scope = generator.inject(form, { mode: 'edit', id: 'form-container', related: false });
// Set modal dimensions based on viewport width
ww = $(document).width();
wh = $('body').height();
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,
closeOnEscape: false,
create: function () {
// 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 () {
// 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"]'),
titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(),
buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(),
content = dialog.find('#status-modal-dialog');
content.width(dialog.width() - 28);
content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) });
},
close: function () {
// Destroy on close
$('.tooltip').each(function () {
// Remove any lingering tooltip <div> elements
$(this).remove();
});
$('.popover').each(function () {
// remove lingering popover <div> elements
$(this).remove();
});
$('#status-modal-dialog').dialog('destroy');
$('#inventory-modal-container').empty();
},
open: function () {
Wait('stop');
}
});
function calcRows(content) {
var n = content.match(/\n/g),
rows = (n) ? n.length : 1;
return (rows > maxrows) ? 20 : rows;
}
Wait('start');
url = GetBasePath('jobs') + job_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
var cDate;
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);
cDate = new Date(data.created);
scope.created = FormatDate(cDate);
$('#status-modal-dialog').dialog('open');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Attempt to load job failed. GET returned status: ' + status });
});
};
}
])
.factory('JobsListUpdate', ['Rest', function(Rest) {
return function(params) {
var scope = params.scope,

View File

@ -81,217 +81,4 @@ export default
});
};
}
])
.factory('EditInventoryProperties', ['InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList',
'GetBasePath', 'ParseTypeChange', 'SaveInventory', 'Wait', 'Store', 'SearchInit', 'ParseVariableString', 'CreateDialog', 'TextareaResize',
function (InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LookUpInit, OrganizationList, GetBasePath, ParseTypeChange, SaveInventory,
Wait, Store, SearchInit, ParseVariableString, CreateDialog, TextareaResize) {
return function (params) {
var parent_scope = params.scope,
inventory_id = params.inventory_id,
generator = GenerateForm,
form = InventoryForm,
master = {},
//PreviousSearchParams = Store('CurrentSearchParams'),
buttons,
scope = parent_scope.$new();
form.well = false;
var form_scope =
generator.inject(form, {
mode: 'edit',
showButtons: false,
showActions: false,
id: 'inventory-edit-modal-dialog',
related: false,
scope: scope
});
/* Reset form properties. Otherwise it screws up future requests of the Inventories detail page */
form.well = true;
buttons = [{
label: "Cancel",
onClick: function() {
scope.cancelModal();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "inventory-edit-cancel-button"
},{
label: "Save",
onClick: function() {
scope.saveModal();
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "inventory-edit-save-button"
}];
CreateDialog({
scope: scope,
buttons: buttons,
width: 675,
height: 750,
minWidth: 400,
title: 'Inventory Properties',
id: 'inventory-edit-modal-dialog',
closeOnEscape: false,
form: form_scope.inventory_form,
onClose: function() {
Wait('stop');
scope.codeMirror.destroy();
$('#inventory-edit-modal-dialog').empty();
},
onResizeStop: function() {
TextareaResize({
scope: scope,
textareaId: 'inventory_variables',
modalId: 'inventory-edit-modal-dialog',
formId: 'inventory_form'
});
},
beforeDestroy: function() {
if (scope.codeMirror) {
scope.codeMirror.destroy();
}
$('#inventory-edit-modal-dialog').empty();
},
onOpen: function() {
$('#inventory_name').focus();
setTimeout(function() {
TextareaResize({
scope: scope,
textareaId: 'inventory_variables',
modalId: 'inventory-edit-modal-dialog',
formId: 'inventory_form',
parse: true
});
}, 300);
},
callback: 'InventoryEditDialogReady'
});
scope.parseType = 'yaml';
if (scope.removeInventoryPropertiesLoaded) {
scope.removeInventoryPropertiesLoaded();
}
scope.removeInventoryPropertiesLoaded = scope.$on('inventoryPropertiesLoaded', function() {
Wait('stop');
$('#inventory-edit-modal-dialog').dialog('open');
});
scope.formModalActionLabel = 'Save';
scope.formModalCancelShow = true;
scope.formModalInfo = false;
scope.formModalHeader = 'Inventory Properties';
Wait('start');
Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
Rest.get()
.success(function (data) {
var fld;
for (fld in form.fields) {
if (fld === 'variables') {
scope.variables = ParseVariableString(data.variables);
master.variables = scope.variables;
} else if (fld === 'inventory_name') {
scope[fld] = data.name;
master[fld] = scope[fld];
} else if (fld === 'inventory_description') {
scope[fld] = data.description;
master[fld] = scope[fld];
} else if (data[fld]) {
scope[fld] = data[fld];
master[fld] = scope[fld];
}
if (form.fields[fld].sourceModel && data.summary_fields &&
data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
LookUpInit({
scope: scope,
form: form,
current_item: scope.organization,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
scope.$emit('inventoryPropertiesLoaded');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status });
});
if (scope.removeInventorySaved) {
scope.removeInventorySaved();
}
scope.removeInventorySaved = scope.$on('InventorySaved', function () {
//$('#form-modal').modal('hide');
// Restore prior search state
//if (scope.searchCleanp) {
// scope.searchCleanup();
//}
//SearchInit({
// scope: parent_scope,
// set: PreviousSearchParams.set,
// list: PreviousSearchParams.list,
// url: PreviousSearchParams.defaultUrl,
// iterator: PreviousSearchParams.iterator,
// sort_order: PreviousSearchParams.sort_order,
// setWidgets: false
//});
//parent_scope.$emit('RefreshInventories');
try {
$('#inventory-edit-modal-dialog').dialog('close');
}
catch(err) {
// ignore
}
parent_scope.$emit('RefreshInventories');
scope.$destroy();
});
scope.cancelModal = function () {
// Restore prior search state
/*if (scope.searchCleanp) {
scope.searchCleanup();
}
SearchInit({
scope: parent_scope,
set: PreviousSearchParams.set,
list: PreviousSearchParams.list,
url: PreviousSearchParams.defaultUrl,
iterator: PreviousSearchParams.iterator,
sort_order: PreviousSearchParams.sort_order,
setWidgets: false
});*/
try {
$('#inventory-edit-modal-dialog').dialog('close');
}
catch(err) {
// ignore
}
scope.$destroy();
};
scope.saveModal = function () {
scope.inventory_id = inventory_id;
SaveInventory({ scope: scope, parent_scope: parent_scope });
};
};
}
]);

View File

@ -1,13 +1,5 @@
<div class="tab-pane" id="inventories">
<div ui-view></div>
<div ui-view=""></div>
<div ng-cloak id="htmlTemplate" class="Panel"></div>
<div id="inventory-edit-modal-dialog"></div>
<div ng-include="'/static/partials/logviewer.html'"></div>
<div id="copy-job-modal" style="display:none">
<form name="copy_form" id="copy_form">
What would you like to name the copy of job template <b><span id=job_name></span></b>?<br>
<input id="new_copy_name" name="new_copy_name" ng-model ="new_copy_name" ng-required="true" class="form-control ng-pristine ng-invalid-required ng-invalid" style="margin-top:10px;">
<div class="error survey_error ng-hide" ng-show="copy_form.new_copy_name.$dirty && copy_form.new_copy_name.$error.required">Please enter a name for this job template copy.</div></input>
</form>
</div>
</div>

View File

@ -9,7 +9,7 @@
* @name controllers.function:Adhoc
* @description This controller controls the adhoc form creation, command launching and navigating to standard out after command has been succesfully ran.
*/
function adhocController($q, $scope, $rootScope, $location, $stateParams,
function adhocController($q, $scope, $location, $stateParams,
$state, CheckPasswords, PromptForPasswords, CreateLaunchDialog, adhocForm,
GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices,
KindChange, LookUpInit, CredentialList, Empty, Wait) {
@ -23,7 +23,7 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
this.privateFn = privateFn;
var id = $stateParams.inventory_id,
hostPattern = $rootScope.hostPatterns || "all";
hostPattern = $stateParams.pattern;
// note: put any urls that the controller will use in here!!!!
privateFn.setAvailableUrls = function() {
@ -102,7 +102,6 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
privateFn.instantiateHostPatterns = function(hostPattern) {
$scope.limit = hostPattern;
$scope.providedHostPatterns = $scope.limit;
delete $rootScope.hostPatterns;
};
// call helpers to initialize lookup and select fields through get
@ -295,7 +294,7 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
}
export default ['$q', '$scope', '$rootScope', '$location', '$stateParams',
export default ['$q', '$scope', '$location', '$stateParams',
'$state', 'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'adhocForm',
'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
'GetChoices', 'KindChange', 'LookUpInit', 'CredentialList', 'Empty', 'Wait',

View File

@ -122,20 +122,19 @@ export default function() {
dataContainer: "body"
},
},
buttons: {
reset: {
ngClick: 'formReset()',
ngDisabled: true,
label: 'Reset',
'class': 'Form-buttonDefault Form-button'
},
launch: {
label: 'Save',
ngClick: 'launchJob()',
ngDisabled: true,
'class': 'Form-buttonDefault Form-button'
}
'class': 'btn btn-sm List-buttonSubmit'
},
reset: {
ngClick: 'formReset()',
ngDisabled: true,
label: 'Reset',
'class': 'btn btn-sm Form-cancelButton'
}
},
related: {}

View File

@ -0,0 +1,24 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory';
export default {
route: '/adhoc',
params:{
pattern: {
value: 'all',
squash: true
}
},
name: 'inventoryManage.adhoc',
views: {
'form@inventoryManage': {
templateUrl: templateUrl('inventories/manage/adhoc/adhoc'),
controller: 'adhocController'
}
}
};

View File

@ -0,0 +1,11 @@
import route from './adhoc.route';
import adhocController from './adhoc.controller';
import form from './adhoc.form';
export default
angular.module('adhoc', [])
.controller('adhocController', adhocController)
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}])
.factory('adhocForm', form);

View File

@ -0,0 +1,25 @@
.InventoryManageBreadCrumbs .BreadCrumb-list{
padding-right: 0px;
}
.InventoryManageBreadCrumb-ncy.BreadCrumb-list{
padding-left: 5px;
}
.InventoryManageBreadCrumbs-separator{
content: "/";
padding: 0 5px;
color: #B7B7B7;
}
.InventoryManageBreadCrumbs{
position: relative;
height: auto;
top: -40px;
.BreadCrumb-list{
margin-bottom: 0px;
}
}
.InventoryManage-breakWord{
word-break: break-all;
}
ol.BreadCrumb-list{
display: inline-block;
}

View File

@ -0,0 +1,27 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$state', '$stateParams', '$scope', 'inventoryData', 'breadCrumbData', function($state, $stateParams, $scope, inventoryData, breadCrumbData){
// process result data into the same order specified in the traversal path
$scope.groups = _.sortBy(breadCrumbData, function(item){
var index = _.indexOf($stateParams.group, item.id);
return (index === -1) ? $stateParams.group.length : index;
});
$scope.inventory = inventoryData;
// slices the group stack at $index to supply new group params to $state.go()
$scope.goToGroup = function($index){
var group = $stateParams.group.slice(0, $index);
$state.go('inventoryManage', {group: group}, {reload: true});
};
$scope.state = $state;
$scope.isRootState = function(){
return $state.current.name === 'inventoryManage';
};
$scope.goToInventory = function(){
$state.go('inventoryManage', {group: undefined}, {reload: true});
};
}];

View File

@ -0,0 +1,50 @@
<div class="BreadCrumb InventoryManageBreadCrumbs">
<ol class="BreadCrumb-list">
<li class="BreadCrumb-item"><a ui-sref="inventories">Inventories</a></li>
<li href class="BreadCrumb-item"><a href ng-click="goToInventory()">Manage {{inventory.name}}</a></li>
<!-- inside inventoryManage list view (last item is not clickable) -->
<span ng-if="isRootState()">
<li class="BreadCrumb-item"></li>
<li ng-repeat="group in groups | limitTo:(groups.length-1) track by $index" class="BreadCrumb-item">
<a href ng-click="goToGroup($index+1)">{{group.name}}</a>
</li>
<li ng-hide="groups.length == 0" class="BreadCrumb-item"><span>{{groups[groups.length-1].name}}</span></li>
</span>
<!-- inside inventoryManage.child like add/edit (last item is clickable)-->
<span ng-if="!isRootState()">
<li class="BreadCrumb-item"></li>
<li ng-repeat="group in groups track by $index" class="BreadCrumb-item">
<a href ng-click="goToGroup($index)">{{group.name}}</a>
</li>
<li class="BreadCrumb-item"></li>
</span>
<div class="InventoryManageBreadCrumb-ncy" ng-if="!licenseMissing" ncy-breadcrumb></div>
</ol>
<div class="BreadCrumb-menuLink"
id="bread_crumb_activity_stream"
aw-tool-tip="View Activity Stream"
data-placement="left"
data-trigger="hover"
data-container="body"
ng-class="{'BreadCrumb-menuLinkActive' : activityStreamActive}"
ng-if="showActivityStreamButton"
ng-hide= "licenseMissing"
ng-click="openActivityStream()">
<i class="BreadCrumb-menuLinkImage icon-activity-stream"
alt="Activity Stream">
</i>
</div>
<a class="BreadCrumb-menuLink"
id="bread_crumb_dashboard"
ui-sref="dashboard"
aw-tool-tip="View Dashboard"
data-placement="left"
data-trigger="hover"
data-container="body"
ng-hide="licenseMissing"
ng-if="!showActivityStreamButton">
<i class="BreadCrumb-menuLinkImage fa fa-tachometer"
alt="Dashboard">
</i>
</a>
</div>

View File

@ -0,0 +1,68 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$state', '$stateParams', 'generateList', 'SearchInit', 'PaginateInit', 'GroupManageService', 'GetBasePath', 'CopyMoveGroupList', 'group',
function($scope, $state, $stateParams, GenerateList, SearchInit, PaginateInit, GroupManageService, GetBasePath, CopyMoveGroupList, group){
var list = CopyMoveGroupList,
view = GenerateList;
$scope.item = group;
$scope.submitMode = $stateParams.groups === undefined ? 'move' : 'copy';
$scope['toggle_'+ list.iterator] = function(id){
// toggle off anything else currently selected
_.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;});
// yoink the currently selected thing
$scope.selected = _.find($scope.groups, (item) => {return item.id === id;});
};
$scope.formCancel = function(){
$state.go('^');
};
$scope.formSave = function(){
switch($scope.submitMode) {
case 'copy':
GroupManageService.associateGroup(group, $scope.selected.id).then(() => $state.go('^', null, {reload: true}));
break;
case 'move':
// at the root group level, no dissassociation is needed
if (!$stateParams.group){
GroupManageService.associateGroup(group, $scope.selected.id).then(() => $state.go('^', null, {reload: true}));
}
else{
// unsure if orphaned resources get garbage collected, safe bet is to associate before disassociate
GroupManageService.associateGroup(group, $scope.selected.id).then(() => {
GroupManageService.disassociateGroup(group, _.last($stateParams.group))
.then(() => $state.go('^', null, {reload: true}));
});
}
break;
}
};
var init = function(){
var url = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/';
url += $stateParams.group ? '?not__id__in=' + group.id + ',' + _.last($stateParams.group) : '?not__id=' + group.id;
list.basePath = url;
view.inject(list, {
mode: 'lookup',
id: 'copyMove-list',
scope: $scope
});
SearchInit({
scope: $scope,
set: list.name,
list: list,
url: url
});
PaginateInit({
scope: $scope,
list: list,
url : url,
mode: 'lookup'
});
$scope.search(list.iterator, null, true, false);
// remove the current group from list
};
init();
}];

View File

@ -0,0 +1,65 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$state', '$stateParams', 'generateList', 'SearchInit', 'PaginateInit', 'HostManageService', 'GetBasePath', 'CopyMoveGroupList', 'host',
function($scope, $state, $stateParams, GenerateList, SearchInit, PaginateInit, HostManageService, GetBasePath, CopyMoveGroupList, host){
var list = CopyMoveGroupList,
view = GenerateList;
$scope.item = host;
$scope.submitMode = 'copy';
$scope['toggle_'+ list.iterator] = function(id){
// toggle off anything else currently selected
_.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;});
// yoink the currently selected thing
$scope.selected = _.find($scope.groups, (item) => {return item.id === id;});
};
$scope.formCancel = function(){
$state.go('^');
};
$scope.formSave = function(){
switch($scope.submitMode) {
case 'copy':
HostManageService.associateGroup(host, $scope.selected.id).then(() => $state.go('^'));
break;
case 'move':
// at the root group level, no dissassociation is needed
if (!$stateParams.group){
HostManageService.associateGroup(host, $scope.selected.id).then(() => $state.go('^', null, {reload: true}));
}
else{
HostManageService.associateGroup(host, $scope.selected.id).then(() => {
HostManageService.disassociateGroup(host, _.last($stateParams.group))
.then(() => $state.go('^', null, {reload: true}));
});
}
break;
}
};
var init = function(){
var url = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/';
list.basePath = url;
view.inject(list, {
mode: 'lookup',
id: 'copyMove-list',
scope: $scope
});
SearchInit({
scope: $scope,
set: list.name,
list: list,
url: url
});
PaginateInit({
scope: $scope,
list: list,
url : url,
mode: 'lookup'
});
$scope.search(list.iterator, null, true, false);
};
init();
}];

View File

@ -4,13 +4,6 @@
.List-searchRow {
width: 50%;
}
.ui-dialog-buttonpane.ui-widget-content {
border: none;
text-align: right;
margin-top: 15px;
}
.Form-header {
width: 50%;
margin-top: -20px;
@ -22,9 +15,12 @@
}
}
}
.copyMove-directive--copyMoveChoices {
.copyMove-choices {
float: right;
width: 25%;
text-align: right;
}
.copyMove-buttons{
height: 30px;
margin-top: 10px;
}

View File

@ -0,0 +1,19 @@
<div class="Panel copyMove-panel">
<div class="Form-header">
<div class="Form-title ng-binding">{{item.name}}</div>
</div>
<div class="form-group copyMove-choices">
<label class="radio-inline">
<input type="radio" ng-model="submitMode" value="copy" class="ng-pristine ng-valid ng-touched"> Copy
</label>
<label class="radio-inline">
<input type="radio" ng-model="submitMode" value="move" class="ng-pristine ng-untouched ng-valid"> Move
</label>
</div>
<div id="copyMove-select"></div>
<div id="copyMove-list"></div>
<div class="copyMove-buttons">
<button type="button" class="pull-right btn btn-sm btn-default Form-cancelButton" ng-click="formCancel()">Cancel</button>
<button type="button" class="pull-right btn btn-sm Form-saveButton" ng-disabled="!selected" ng-click="formSave()">Save</button>
</div>
</div>

View File

@ -0,0 +1,51 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory';
import CopyMoveGroupsController from './copy-move-groups.controller';
import CopyMoveHostsController from './copy-move-hosts.controller';
var copyMoveGroup = {
name: 'inventoryManage.copyMoveGroup',
route: '/copy-move-group/{group_id}',
data: {
group_id: 'group_id',
},
ncyBreadcrumb: {
label: "COPY OR MOVE {{item.name}}"
},
resolve: {
group: ['GroupManageService', '$stateParams', function(GroupManageService, $stateParams){
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
}]
},
views: {
'form@inventoryManage' : {
controller: CopyMoveGroupsController,
templateUrl: templateUrl('inventories/manage/copy-move/copy-move'),
}
}
};
var copyMoveHost = {
name: 'inventoryManage.copyMoveHost',
route: '/copy-move-host/{host_id}',
ncyBreadcrumb: {
label: "COPY OR MOVE {{item.name}}"
},
resolve: {
host: ['HostManageService', '$stateParams', function(HostManageService, $stateParams){
return HostManageService.get({id: $stateParams.host_id}).then(res => res.data.results[0]);
}]
},
views: {
'form@inventoryManage': {
templateUrl: templateUrl('inventories/manage/copy-move/copy-move'),
controller: CopyMoveHostsController,
}
}
};
export {copyMoveGroup, copyMoveHost};

View File

@ -4,12 +4,11 @@
* All Rights Reserved
*************************************************/
import route from './copy.route';
import {copyMoveGroup, copyMoveHost} from './copy-move.route';
export default
angular.module('inventory-copy', [])
angular.module('manageCopyMove', [])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route.copy);
$stateExtender.addState(route.copyGroup);
$stateExtender.addState(route.copyHost);
$stateExtender.addState(copyMoveGroup);
$stateExtender.addState(copyMoveHost);
}]);

View File

@ -1,306 +0,0 @@
function CopyGroupsCtrl($compile, $state, $scope, $location, Rest, ProcessErrors, CreateDialog,
GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit, GetRootGroups, ParamPass, Store) {
var vm = this;
var name;
var params = ParamPass.get(),
group_id,
parent_scope,
scope;
if (params !== undefined) {
group_id = $state.params.group_id;
parent_scope = params.scope;
scope = parent_scope.$new();
var parent_group = parent_scope.selected_group_id,
url, group;
} else {
group_id = $state.params.group_id;
parent_scope = $scope.$new();
scope = parent_scope.$new();
}
var inventory_id = $state.params.inventory_id;
var PreviousSearchParams = Store('group_current_search_params');
if (scope.removeGroupsCopyPostRefresh) {
scope.removeGroupsCopyPostRefresh();
}
scope.removeGroupCopyPostRefresh = scope.$on('PostRefresh', function() {
scope.copy_groups.forEach(function(row, i) {
scope.copy_groups[i].checked = '0';
});
Wait('stop');
// prevent backspace from navigation when not in input or textarea field
$(document).on('keydown', function(e) {
if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) {
e.preventDefault();
}
});
});
if (scope.removeCopyDialogReady) {
scope.removeCopyDialogReady();
}
scope.removeCopyDialogReady = scope.$on('CopyDialogReady', function() {
var url = GetBasePath('inventory') + inventory_id + '/groups/';
url += (parent_group) ? '?not__id__in=' + group_id + ',' + parent_group : '?not__id=' + group_id;
GenerateList.inject(GroupList, {
mode: 'lookup',
id: 'copyMove-directive--copyGroupSelect',
scope: scope
});
SearchInit({
scope: scope,
set: GroupList.name,
list: GroupList,
url: url
});
PaginateInit({
scope: scope,
list: GroupList,
url: url,
mode: 'lookup'
});
scope.search(GroupList.iterator);
});
if (scope.removeShowDialog) {
scope.removeShowDialog();
}
scope.removeShowDialog = scope.$on('ShowDialog', function() {
var d;
scope.name = group.name;
scope.copy_choice = "copy";
d = angular.element(document.getElementById('copyMove-directive--copyGroupSelect'));
$compile(d)(scope);
scope.$emit('CopyDialogReady');
});
if (scope.removeRootGroupsReady) {
scope.removeRootGroupsReady();
}
scope.removeRootGroupsReady = scope.$on('RootGroupsReady', function(e, root_groups) {
scope.offer_root_group = true;
scope.use_root_group = false;
root_groups.every(function(row) {
if (row.id === group_id) {
scope.offer_root_group = false;
return false;
}
return true;
});
url = GetBasePath('groups') + group_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
group = data;
vm.name = group.name;
scope.$emit('ShowDialog');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status
});
});
});
Wait('start');
GetRootGroups({
scope: scope,
group_id: group_id,
inventory_id: $state.params.inventory_id,
callback: 'RootGroupsReady'
});
var restoreSearch = function() {
// Restore search params and related stuff, plus refresh
// groups and hosts lists
SearchInit({
scope: $scope,
set: PreviousSearchParams.set,
list: PreviousSearchParams.list,
url: PreviousSearchParams.defaultUrl,
iterator: PreviousSearchParams.iterator,
sort_order: PreviousSearchParams.sort_order,
setWidgets: false
});
$scope.refreshHostsOnGroupRefresh = true;
//$scope.search(InventoryGroups.iterator, null, true, false, true);
};
var cancel = function() {
restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists
scope.$destroy();
$state.go('inventoryManage', {}, {
reload: true
});
};
var allowSave = false;
scope['toggle_' + GroupList.iterator] = function(id) {
var count = 0,
list = GroupList;
scope[list.name].forEach(function(row, i) {
if (row.id === id) {
if (row.checked) {
scope[list.name][i].success_class = 'success';
} else {
scope[list.name][i].success_class = '';
}
} else {
scope[list.name][i].checked = 0;
scope[list.name][i].success_class = '';
}
});
// Check if any rows are checked
scope[list.name].forEach(function(row) {
if (row.checked) {
count++;
}
});
if (count === 0) {
vm.allowSave = false;
} else {
vm.allowSave = true;
}
};
scope.toggleUseRootGroup = function() {
var list = GroupList;
if (scope.use_root_group) {
$('#group-copy-ok-button').removeAttr('disabled');
} else {
// check for group selection
$('#group-copy-ok-button').attr('disabled', 'disabled');
scope[list.name].every(function(row) {
if (row.checked === 1) {
$('#group-copy-ok-button').removeAttr('disabled');
return false;
}
return true;
});
}
};
var performCopy = function() {
var list = GroupList,
target,
url;
Wait('start');
if (scope.use_root_group) {
target = null;
} else {
scope[list.name].every(function(row) {
if (row.checked === 1) {
target = row;
return false;
}
return true;
});
}
if (vm.copy_choice === 'move') {
// Respond to move
// disassociate the group from the original parent
if (scope.removeGroupRemove) {
scope.removeGroupRemove();
}
scope.removeGroupRemove = scope.$on('RemoveGroup', function() {
if (parent_group > 0) {
// Only remove a group from a parent when the parent is a group and not the inventory root
url = GetBasePath('groups') + parent_group + '/children/';
Rest.setUrl(url);
Rest.post({
id: group.id,
disassociate: 1
})
.success(function() {
vm.cancel();
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to remove ' + group.name + ' from group ' + parent_group + '. POST returned: ' + status
});
});
} else {
vm.cancel();
}
});
// add the new group to the target
url = (target) ?
GetBasePath('groups') + target.id + '/children/' :
GetBasePath('inventory') + inventory_id + '/groups/';
group = {
id: group.id,
name: group.name,
description: group.description,
inventory: inventory_id
};
Rest.setUrl(url);
Rest.post(group)
.success(function() {
scope.$emit('RemoveGroup');
})
.error(function(data, status) {
var target_name = (target) ? target.name : 'inventory';
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status
});
});
} else {
// Respond to copy by adding the new group to the target
url = (target) ?
GetBasePath('groups') + target.id + '/children/' :
GetBasePath('inventory') + inventory_id + '/groups/';
group = {
id: group.id,
name: group.name,
description: group.description,
inventory: inventory_id
};
Rest.setUrl(url);
Rest.post(group)
.success(function() {
vm.cancel();
})
.error(function(data, status) {
var target_name = (target) ? target.name : 'inventory';
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status
});
});
}
};
var copy_choice = 'copy';
angular.extend(vm, {
cancel: cancel,
performCopy: performCopy,
copy_choice: copy_choice,
name: name,
allowSave: allowSave
});
}
export default ['$compile', '$state', '$scope', '$location', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList', 'SearchInit',
'PaginateInit', 'GetRootGroups', 'ParamPass', 'Store', CopyGroupsCtrl
];

View File

@ -1,22 +0,0 @@
<div>
<div class="Form-header">
<div class="Form-title ng-binding">{{vm.name}}</div>
</div>
<div class="form-group copyMove-directive--copyMoveChoices">
<label class="radio-inline">
<input type="radio" ng-model="vm.copy_choice" value="copy" class="ng-pristine ng-valid ng-touched" name="199"> Copy
</label>
<label class="radio-inline">
<input type="radio" ng-model="vm.copy_choice" value="move" class="ng-pristine ng-untouched ng-valid" name="200"> Move
</label>
</div>
<div id="copyMove-directive--copyGroupSelect"></div>
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<div class="ui-dialog-buttonset">
<button type="button" class="btn btn-primary Form-saveButton" id="Inventory-copyGroup--saveButton" ng-disabled="!vm.allowSave" ng-click="vm.performCopy()">
Save</button>
<button type="button" class="btn btn-default Form-cancelButton" id="Inventory-copyGroup--cancelButton" ng-click="vm.cancel()">
Cancel</button>
</div>
</div>
</div>

View File

@ -1,239 +0,0 @@
function CopyHostsCtrl($compile, $state, $scope, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit, ParamPass, Store) {
var vm = this;
var name;
var host_id = $state.params.host_id;
var inventory_id = $state.params.inventory_id;
var url, host, group_scope, parent_scope, scope, parent_group;
var params = ParamPass.get();
if (params !== undefined) {
group_scope = params.group_scope;
parent_scope = params.host_scope;
parent_group = group_scope.selected_group_id;
scope = parent_scope.$new();
} else {
group_scope = $scope.$new();
parent_scope = $scope.$new();
scope = parent_scope.$new();
}
var PreviousSearchParams = Store('group_current_search_params');
if (scope.removeHostCopyPostRefresh) {
scope.removeHostCopyPostRefresh();
}
scope.removeHostCopyPostRefresh = scope.$on('PostRefresh', function() {
scope.copy_groups.forEach(function(row, i) {
scope.copy_groups[i].checked = '0';
});
Wait('stop');
// prevent backspace from navigation when not in input or textarea field
$(document).on("keydown", function(e) {
if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) {
e.preventDefault();
}
});
});
if (scope.removeHostCopyDialogReady) {
scope.removeHostCopyDialogReady();
}
scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() {
var url = GetBasePath('inventory') + inventory_id + '/groups/';
GenerateList.inject(GroupList, {
mode: 'lookup',
id: 'copyMove-directive--copyHostSelect',
scope: scope
//,
//instructions: instructions
});
SearchInit({
scope: scope,
set: GroupList.name,
list: GroupList,
url: url
});
PaginateInit({
scope: scope,
list: GroupList,
url: url,
mode: 'lookup'
});
scope.search(GroupList.iterator, null, true, false);
});
if (scope.removeShowDialog) {
scope.removeShowDialog();
}
scope.removeShowDialog = scope.$on('ShowDialog', function() {
var d;
scope.name = host.name;
d = angular.element(document.getElementById('copyMove-directive--copyHostPanel'));
$compile(d)(scope);
scope.$emit('HostCopyDialogReady');
});
Wait('start');
url = GetBasePath('hosts') + host_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
host = data;
vm.name = host.name;
scope.$emit('ShowDialog');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status
});
});
var restoreSearch = function() {
// Restore search params and related stuff, plus refresh
// groups and hosts lists
SearchInit({
scope: $scope,
set: PreviousSearchParams.set,
list: PreviousSearchParams.list,
url: PreviousSearchParams.defaultUrl,
iterator: PreviousSearchParams.iterator,
sort_order: PreviousSearchParams.sort_order,
setWidgets: false
});
$scope.refreshHostsOnGroupRefresh = true;
};
var cancel = function() {
$(document).off("keydown");
restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists
scope.$destroy();
$state.go('inventoryManage', {}, {
reload: true
});
};
var allowSave = false;
scope['toggle_' + GroupList.iterator] = function(id) {
var count = 0,
list = GroupList;
scope[list.name].forEach(function(row, i) {
if (row.id === id) {
if (row.checked) {
scope[list.name][i].success_class = 'success';
} else {
scope[list.name][i].success_class = '';
}
} else {
scope[list.name][i].checked = 0;
scope[list.name][i].success_class = '';
}
});
// Check if any rows are checked
scope[list.name].forEach(function(row) {
if (row.checked) {
count++;
}
});
if (count === 0) {
vm.allowSave = false;
} else {
vm.allowSave = true;
}
};
var performCopy = function() {
var list = GroupList,
target,
url;
Wait('start');
if (scope.use_root_group) {
target = null;
} else {
scope[list.name].every(function(row) {
if (row.checked === 1) {
target = row;
return false;
}
return true;
});
}
if (vm.copy_choice === 'move') {
// Respond to move
// disassociate the host from the original parent
if (scope.removeHostRemove) {
scope.removeHostRemove();
}
scope.removeHostRemove = scope.$on('RemoveHost', function() {
if (parent_group > 0) {
// Only remove a host from a parent when the parent is a group and not the inventory root
url = GetBasePath('groups') + parent_group + '/hosts/';
Rest.setUrl(url);
Rest.post({
id: host.id,
disassociate: 1
})
.success(function() {
vm.cancel();
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to remove ' + host.name + ' from group ' + parent_group + '. POST returned: ' + status
});
});
} else {
vm.cancel();
}
});
// add the new host to the target
url = GetBasePath('groups') + target.id + '/hosts/';
Rest.setUrl(url);
Rest.post(host)
.success(function() {
scope.$emit('RemoveHost');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status
});
});
} else {
// Respond to copy by adding the new host to the target
url = GetBasePath('groups') + target.id + '/hosts/';
Rest.setUrl(url);
Rest.post(host)
.success(function() {
vm.cancel();
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status
});
});
}
};
var copy_choice = 'copy';
angular.extend(vm, {
copy_choice: copy_choice,
name: name,
cancel: cancel,
allowSave: allowSave,
performCopy: performCopy
});
}
export default ['$compile', '$state', '$scope', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList', 'SearchInit',
'PaginateInit', 'ParamPass', 'Store', CopyHostsCtrl
];

View File

@ -1,23 +0,0 @@
<div>
<div class="Form-header">
<div class="Form-title ng-binding">{{vm.name}}</div>
</div>
<div class="form-group copyMove-directive--copyMoveChoices">
<label class="radio-inline">
<input type="radio" ng-model="vm.copy_choice" value="copy" class="ng-pristine ng-valid ng-touched" name="199"> Copy
</label>
<label class="radio-inline">
<input type="radio" ng-model="vm.copy_choice" value="move" class="ng-pristine ng-untouched ng-valid" name="200"> Move
</label>
</div>
<div id="copyMove-directive--copyHostSelect"></div>
<div id="copyMove-directive--copyHostPanel"></div>
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<div class="ui-dialog-buttonset">
<button type="button" class="btn btn-primary Form-saveButton" id="Inventory-copyGroup--saveButton" ng-disabled="!vm.allowSave" ng-click="vm.performCopy()">
Save</button>
<button type="button" class="btn btn-default Form-cancelButton" id="Inventory-copyGroup--cancelButton" ng-click="vm.cancel()">
Cancel</button>
</div>
</div>
</div>

View File

@ -1,16 +0,0 @@
function inventoryManageCopyCtrl($state) {
var vm = this;
var cancelPanel = function() {
$state.go('inventoryManage', {}, {
reload: true
});
};
angular.extend(vm, {
cancelPanel: cancelPanel
});
}
export default ['$state', inventoryManageCopyCtrl
];

View File

@ -1,10 +0,0 @@
<div class="tab-pane" id="Inventory-copyMovePanel">
<div ng-cloak id="Inventory-groupCopy--panel" class="Panel">
<div class="Form-exitHolder">
<button class="Form-exit" ng-click="vm.cancelPanel()">
<i class="fa fa-times-circle"></i>
</button>
</div>
<ui-view></ui-view>
</div>
</div>

View File

@ -1,54 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {
templateUrl
} from '../../../shared/template-url/template-url.factory';
import inventoryManageCopyCtrl from './copy.controller';
import CopyGroupsCtrl from './copy-groups.controller';
import CopyHostsCtrl from './copy-hosts.controller';
export default {
copy: {
name: 'inventoryManage.copy',
route: '/copy',
templateUrl: templateUrl('inventories/manage/copy/copy'),
ncyBreadcrumb: {
label: "COPY"
},
controller: inventoryManageCopyCtrl,
controllerAs: 'vm',
bindToController: true,
},
copyGroup: {
name: 'inventoryManage.copy.group',
route: '/group/:group_id?groups',
templateUrl: templateUrl('inventories/manage/copy/copy-groups'),
data: {
group_id: 'group_id',
},
ncyBreadcrumb: {
label: "GROUP"
},
controller: CopyGroupsCtrl,
controllerAs: 'vm',
bindToController: true
},
copyHost: {
name: 'inventoryManage.copy.host',
route: '/host/:host_id?groups',
templateUrl: templateUrl('inventories/manage/copy/copy-hosts'),
data: {
host_id: 'host_id',
},
ncyBreadcrumb: {
label: "HOST"
},
controller: CopyHostsCtrl,
controllerAs: 'vm',
bindToController: true
}
};

View File

@ -0,0 +1,215 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'LookUpInit',
'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions',
function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ParseTypeChange, GenerateForm, inventoryData, LookUpInit,
GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions){
var generator = GenerateForm,
form = GroupForm();
$scope.formCancel = function(){
$state.go('^');
};
$scope.formSave = function(){
var params, source;
// group fields
var group = {
variables: $scope.variables === ('---' || '{}') ? null : $scope.variables,
name: $scope.name,
description: $scope.description,
inventory: inventoryData.id
};
if ($scope.source){
// inventory_source fields
params = {
instance_filters: $scope.instance_filters,
source_vars: $scope[$scope.source.value + '_variables'] === ('---' || '{}') ? null : $scope[$scope.source.value + '_variables'],
source_script: $scope.inventory_script,
source: $scope.source.value,
credential: $scope.credential,
overwrite: $scope.overwrite,
overwrite_vars: $scope.overwrite_vars,
update_on_launch: $scope.update_on_launch,
update_cache_timeout: $scope.update_cache_timeout || 0,
// comma-delimited strings
group_by: _.map($scope.group_by, 'value').join(','),
source_regions: _.map($scope.source_regions, 'value').join(',')
};
source = $scope.source.value;
}
else{
source = null;
}
switch(source){
// no inventory source set, just create a new group
// '' is the value supplied for Manual source type
case null || '':
GroupManageService.post(group).then(res => {
// associate
if ($stateParams.group){
return GroupManageService.associateGroup(res.data, _.last($stateParams.group))
.then(() => $state.go('^', null, {reload: true}));
}
else{
$state.go('^', null, {reload: true});
}
});
break;
// create a new group and create/associate an inventory source
// equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'foreman' || 'cloudforms' || 'openstack' || 'custom'
default:
GroupManageService.post(group)
// associate to group
.then(res => {
if ($stateParams.group){
GroupManageService.associateGroup(res.data, _.last($stateParams.group));
return res;
}
else {return res;}
// pass the original POST response and not the association response
})
.then(res => GroupManageService.putInventorySource(
// put the received group ID into inventory source payload
// and pass the related endpoint
_.assign(params, {group: res.data.id}), res.data.related.inventory_source))
.then(res => $state.go('inventoryManage.editGroup', {group_id: res.data.group}, {reload: true}));
break;
}
};
$scope.sourceChange = function(source){
source = source.value === 'azure_rm' ? 'azure' : source.value;
if (source === 'custom'){
LookUpInit({
scope: $scope,
url: GetBasePath('inventory_script'),
form: form,
list: InventoryScriptsList,
field: 'inventory_script',
input_type: "radio"
});
}
else if (source === 'ec2'){
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + '?kind=aws',
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
}
// equal to case 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'foreman' || 'cloudforms' || 'openstack' || 'custom'
else{
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + (source === '' ? '' : '?kind=' + source),
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
}
// reset fields
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
$scope.source_region_choices = $scope[source + '_regions'];
$scope.cloudCredentialRequired = source !== 'manual' && source !== 'custom' ? true : false;
$scope.group_by = null;
$scope.source_regions = null;
$scope.credential = null;
$scope.credential_name = null;
initRegionSelect();
};
var initRegionSelect = function(){
CreateSelect2({
element: '#group_source_regions',
multiple: true
});
CreateSelect2({
element: '#group_group_by',
multiple: true
});
};
var initSourceSelect = function(){
CreateSelect2({
element: '#group_source',
multiple: false
});
};
var initSources = function(){
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'rax_regions',
choice_name: 'rax_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'ec2_regions',
choice_name: 'ec2_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'gce_regions',
choice_name: 'gce_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'azure_regions',
choice_name: 'azure_region_choices',
callback: 'choicesReadyGroup'
});
// Load options for group_by
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'group_by',
variable: 'ec2_group_by',
choice_name: 'ec2_group_by_choices',
callback: 'choicesReadyGroup'
});
GetSourceTypeOptions({
scope: $scope,
variable: 'source_type_options',
//callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref
});
};
// region / source options callback
$scope.$on('choicesReadyGroup', function(){
initRegionSelect();
});
$scope.$on('sourceTypeOptionsReady', function(){
initSourceSelect();
});
var init = function(){
$scope.parseType = 'yaml';
$scope.variables = '---';
generator.inject(form, {mode: 'add', related: false, id: 'Inventory-groupManage--panel', scope: $scope});
ParseTypeChange({
scope: $scope,
field_id: 'group_variables',
variable: 'variables',
});
initSources();
};
init();
}];

View File

@ -0,0 +1,288 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ToggleNotification',
'ParseTypeChange', 'GenerateForm', 'LookUpInit', 'RelatedSearchInit', 'RelatedPaginateInit', 'NotificationsListInit',
'GroupManageService','GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'groupData', 'inventorySourceData',
function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ToggleNotification,
ParseTypeChange, GenerateForm, LookUpInit, RelatedSearchInit, RelatedPaginateInit, NotificationsListInit,
GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, groupData, inventorySourceData){
var generator = GenerateForm,
form = GroupForm();
$scope.formCancel = function(){
$state.go('^');
};
$scope.formSave = function(){
var params, source;
// group fields
var group = {
variables: $scope.variables === ('---' || '{}') ? null : $scope.variables,
name: $scope.name,
description: $scope.description,
inventory: $scope.inventory,
id: groupData.id
};
if ($scope.source){
// inventory_source fields
params = {
group: groupData.id,
source: $scope.source.value,
credential: $scope.credential,
overwrite: $scope.overwrite,
overwrite_vars: $scope.overwrite_vars,
source_script: $scope.inventory_script,
update_on_launch: $scope.update_on_launch,
update_cache_timeout: $scope.update_cache_timeout || 0,
// comma-delimited strings
group_by: _.map($scope.group_by, 'value').join(','),
source_regions: _.map($scope.source_regions, 'value').join(','),
instance_filters: $scope.instance_filters,
source_vars: $scope[$scope.source.value + '_variables'] === '' ? null : $scope[$scope.source.value + '_variables']
};
source = $scope.source.value;
}
else{
source = null;
}
switch(source){
// no inventory source set, just create a new group
// '' is the value supplied for Manual source type
case null || '':
GroupManageService.put(group).then(() => $state.go('^', null, {reload: true}));
break;
// create a new group and create/associate an inventory source
// equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'foreman' || 'cloudforms' || 'openstack' || 'custom'
default:
GroupManageService.put(group)
.then(() => GroupManageService.putInventorySource(params, groupData.related.inventory_source))
.then(() => $state.go('^', null, {reload: true}));
break;
}
};
$scope.toggleNotification = function(event, notifier_id, column) {
var notifier = this.notification;
try {
$(event.target).tooltip('hide');
}
catch(e) {
// ignore
}
ToggleNotification({
scope: $scope,
url: GetBasePath('inventory_sources'),
id: inventorySourceData.id,
notifier: notifier,
column: column,
callback: 'NotificationRefresh'
});
};
$scope.sourceChange = function(source){
$scope.source = source;
if (source.value === 'custom'){
LookUpInit({
scope: $scope,
url: GetBasePath('inventory_script'),
form: form,
list: InventoryScriptsList,
field: 'inventory_script',
input_type: "radio"
});
}
else if (source.value === 'ec2'){
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + '?kind=aws',
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
}
else{
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + (source.value === '' ? '' : '?kind=' + (source.value)),
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
}
// reset fields
$scope.source_region_choices = $scope[source + '_regions'];
$scope.cloudCredentialRequired = source !== 'manual' && source !== 'custom' ? true : false;
$scope.group_by = null;
$scope.source_regions = null;
$scope.credential = null;
$scope.credential_name = null;
initRegionSelect();
};
var initRegionSelect = function(){
CreateSelect2({
element: '#group_source_regions',
multiple: true
});
CreateSelect2({
element: '#group_group_by',
multiple: true
});
};
var initSourceSelect = function(){
$scope.source = _.find($scope.source_type_options, {value: inventorySourceData.source});
CreateSelect2({
element: '#group_source',
multiple: false
});
};
var initRegionData = function(){
var source = $scope.source.value === 'azure_rm' ? 'azure' : $scope.source.value;
var regions = inventorySourceData.source_regions.split(',');
$scope.source_region_choices = $scope[source + '_regions'];
$scope.source_regions = _.map(regions, (region) => _.find($scope[source+'_regions'], {value: region}));
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
if (source ==='ec2'){
var group_by = inventorySourceData.group_by.split(',');
$scope.group_by = _.map(group_by, (item) => _.find($scope.ec2_group_by, {value: item}));
}
initRegionSelect();
};
var initSources = function(){
GetSourceTypeOptions({
scope: $scope,
variable: 'source_type_options',
//callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'rax_regions',
choice_name: 'rax_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'ec2_regions',
choice_name: 'ec2_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'gce_regions',
choice_name: 'gce_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'azure_regions',
choice_name: 'azure_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'group_by',
variable: 'ec2_group_by',
choice_name: 'ec2_group_by_choices',
callback: 'choicesReadyGroup'
});
};
// region / source options callback
$scope.$on('choicesReadyGroup', function(){
if (angular.isObject($scope.source)){
initRegionData();
}
});
$scope.$on('sourceTypeOptionsReady', function(){
initSourceSelect();
});
var init = function(){
// instantiate expected $scope values from inventorySourceData & groupData
var relatedSets = form.relatedSets(groupData.related);
generator.inject(form, {mode: 'edit', related: false, id: 'Inventory-groupManage--panel', scope: $scope});
_.assign($scope,
{credential: inventorySourceData.credential},
{overwrite: inventorySourceData.overwrite},
{overwrite_vars: inventorySourceData.overwrite_vars},
{update_on_launch: inventorySourceData.update_on_launch},
{update_cache_timeout: inventorySourceData.update_cache_timeout},
{instance_filters: inventorySourceData.instance_filters},
{inventory_script: inventorySourceData.source_script}
);
if(inventorySourceData.source === ('ec2' || 'openstack' || 'custom' || 'vmware')){
$scope[inventorySourceData.source + '_variables'] = inventorySourceData.source_vars;
}
if (inventorySourceData.credential){
GroupManageService.getCredential(inventorySourceData.credential).then(res => $scope.credential_name = res.data.name);
}
$scope = angular.extend($scope, groupData);
$scope.variables = $scope.variables === (null || '') ? '---' : $scope.variables;
$scope.parseType = 'yaml';
// instantiate lookup fields
if (inventorySourceData.source !== 'custom'){
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + (inventorySourceData.source === '' ? '' : 'kind=' + (inventorySourceData.source)),
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
}
else if (inventorySourceData.source === 'ec2'){
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + '?kind=aws',
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
}
// equal to case 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'foreman' || 'cloudforms' || 'openstack' || 'custom'
else{
$scope.inventory_script_name = inventorySourceData.summary_fields.source_script.name;
LookUpInit({
scope: $scope,
url: GetBasePath('inventory_script'),
form: form,
list: InventoryScriptsList,
field: 'inventory_script',
input_type: "radio"
});
}
ParseTypeChange({
scope: $scope,
field_id: 'group_variables',
variable: 'variables',
});
NotificationsListInit({
scope: $scope,
url: GetBasePath('inventory_sources'),
id: inventorySourceData.id
});
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
initSources();
_.forEach(relatedSets, (value, key) => $scope.search(relatedSets[key].iterator));
};
init();
}];

View File

@ -1,5 +1,4 @@
<div class="tab-pane" id="Inventory-groupManage">
<div ng-cloak id="Inventory-groupManage--panel" class="Panel">
<manage-groups></manage-groups>
</div>
</div>

View File

@ -0,0 +1,145 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroups', 'generateList', 'InventoryUpdate', 'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus',
'InventoryManageService', 'groupsUrl', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg',
function($scope, $rootScope, $state, $stateParams, InventoryGroups, generateList, InventoryUpdate, GroupManageService, GroupsCancelUpdate, ViewUpdateStatus,
InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg){
var list = InventoryGroups,
view = generateList,
pageSize = 20;
$scope.inventory_id = $stateParams.inventory_id;
$scope.groupSelect = function(id){
var group = $stateParams.group === undefined ? [id] : _($stateParams.group).concat(id).value();
$state.go('inventoryManage', {inventory_id: $stateParams.inventory_id, group: group}, {reload: true});
};
$scope.createGroup = function(){
$state.go('inventoryManage.addGroup');
};
$scope.editGroup = function(id){
$state.go('inventoryManage.editGroup', {group_id: id});
};
$scope.deleteGroup = function(group){
$scope.toDelete = {};
angular.extend($scope.toDelete, group);
$('#group-delete-modal').modal('show');
};
$scope.confirmDelete = function(){
switch($scope.deleteOption){
case 'promote':
GroupManageService.promote($scope.toDelete.id, $stateParams.inventory_id)
.then(() => {
$state.go('inventoryManage', null, {reload: true});
$('#group-delete-modal').modal('hide');
});
break;
case 'delete':
GroupManageService.delete($scope.toDelete.id).then(() => {
$state.go('inventoryManage', null, {reload: true});
$('#group-delete-modal').modal('hide');
});
}
};
$scope.updateGroup = function(group) {
GroupManageService.getInventorySource({group: group.id}).then(res =>InventoryUpdate({
scope: $scope,
group_id: group.id,
url: res.data.results[0].related.update,
group_name: group.name,
group_source: res.data.results[0].source
}));
$scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates
$rootScope.$on('JobStatusChange-inventory', (event, data) => {
switch(data.status){
case 'failed' || 'successful':
$state.reload();
break;
default:
var status = GetSyncStatusMsg({
status: data.status,
has_inventory_sources: group.has_inventory_sources,
source: group.source
});
group.status = data.status;
group.status_class = status.class;
group.status_tooltip = status.tooltip;
group.launch_tooltip = status.launch_tip;
group.launch_class = status.launch_class;
}
});
};
$scope.cancelUpdate = function (id) {
GroupsCancelUpdate({ scope: $scope, id: id });
};
$scope.viewUpdateStatus = function (id) {
ViewUpdateStatus({
scope: $scope,
group_id: id
});
};
$scope.showFailedHosts = function(x, y, z){
$state.go('inventoryManage', {failed: true}, {reload: true});
};
$scope.scheduleGroup = function(id) {
$state.go('inventoryManage.schedules', {id: id});
};
// $scope.$parent governed by InventoryManageController, for unified multiSelect options
$scope.$on('multiSelectList.selectionChanged', (event, selection) => {
$scope.$parent.groupsSelected = selection.length > 0 ? true : false;
$scope.$parent.groupsSelectedItems = selection.selectedItems;
});
$scope.$on('PostRefresh', () =>{
$scope.groups.forEach( (group, index) => {
var group_status, hosts_status;
group_status = GetSyncStatusMsg({
status: group.summary_fields.inventory_source.status,
has_inventory_sources: group.has_inventory_sources,
source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
});
hosts_status = GetHostsStatusMsg({
active_failures: group.hosts_with_active_failures,
total_hosts: group.total_hosts,
inventory_id: $scope.inventory_id,
group_id: group.id
});
_.assign($scope.groups[index],
{status_class: group_status.class},
{status_tooltip: group_status.tooltip},
{launch_tooltip: group_status.launch_tip},
{launch_class: group_status.launch_class},
{hosts_status_tip: hosts_status.tooltip},
{hosts_status_class: hosts_status.class},
{source: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.source : null},
{status: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.status : null});
});
});
$scope.copyMoveGroup = function(id){
$state.go('inventoryManage.copyMoveGroup', {group_id: id, groups: $stateParams.groups});
};
var init = function(){
list.basePath = groupsUrl;
view.inject(list,{
id: 'groups-list',
$scope: $scope,
mode: 'edit'
});
SearchInit({
scope: $scope,
list: list,
url: groupsUrl,
set: 'groups'
});
PaginateInit({
scope: $scope,
list: list,
url: groupsUrl,
pageSize: pageSize
});
$scope.search(list.iterator);
};
init();
}];

View File

@ -0,0 +1,73 @@
<div id="groups-list" class="Panel"></div>
<div class="modal fade GroupDelete" id="group-delete-modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content Modal-content">
<div class="Modal-header">
<div class="Modal-title ng-binding">Delete Group</div>
<div class="Modal-exitHolder">
<button class="close Modal-exit" data-target="#group-delete-modal" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times-circle"></i></button>
</div>
</div>
<div class="modal-body">
<div ng-show="toDelete.total_groups > 0 || toDelete.total_hosts > 0">
<div>
<p class="Prompt-bodyQuery">Deleting group <em>{{ toDelete.name }}</em>.
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0"> This group contains {{ toDelete.total_groups }} groups and {{ toDelete.total_hosts }} hosts. </span>
<span ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0"> This group contains {{ toDelete.total_hosts }} hosts. </span>
<span ng-show="groupsCopunt > 0 && toDelete.total_hosts == 0"> This group contains {{ toDelete.total_groups }} groups. </span>
Delete or promote the group's children?</p>
<div style="margin: 15px auto;">
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="promote"> Promote groups and hosts
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="delete"> Delete groups and hosts
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
<label>
<input type="radio" ng-model="deleteOption" value="promote"> Promote groups
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
<label>
<input type="radio" ng-model="deleteOption" value="delete"> Delete groups
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="promote"> Promote hosts
</label>
</div>
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
<label>
<input type="radio" ng-model="deleteOption" value="delete"> Delete hosts
</label>
</div>
</div>
</div>
</div>
<div ng-show="toDelete.total_groups == 0 && toDelete.total_hosts == 0">
<div class="Prompt-bodyQuery">Are you sure you want to permanently delete the group below from the inventory?</div>
<div class="Prompt-bodyTarget">{{ toDelete.name }}</div>
</div>
<div class="Modal-footer">
<div class="GroupDelete-help--container">
<a href="" id="awp-promote" href="" aw-pop-over="<dl><dt>Delete</dt><dd>Deletes groups and hosts associated with the group being deleted. If a group or host is associated with other groups, it will still exist within those groups. Otherwise, the associated groups and hosts will no longer appear in the inventory.</dd>\n<dt style='margin-top: 5px;'>Promote</dt><dd>Groups and hosts associated with the group being removed will be promoted root level. Note: groups already associated with other groups cannot be promoted.</dd></dl>\n" aw-tool-tip="Click for help" data-placement="right" data-container="body" data-title="Help" class="help-link"><i class="fa fa-question-circle"></i> Click for help</a>
</div>
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn" class="btn Modal-footerButton Modal-errorButton">DELETE</a>
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn" class="btn Modal-defaultButton Modal-footerButton">CANCEL</a>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,19 @@
.select2-selection.select2-selection--multiple.Form-dropDown{
height: auto !important;
}
.GroupDelete-help--container{
margin-right: auto;
}
.GroupDelete .Modal-header{
margin-bottom: 0;
}
.GroupDelete .modal-body{
padding-top: 20px;
}
.Inventory-groupManage{
// ugly hack to avoid the surface area of changing form generator's default classes
.checkbox-inline{
display: block;
padding-bottom: 5px;
}
}

View File

@ -0,0 +1,51 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory';
import addController from './groups-add.controller';
import editController from './groups-edit.controller';
var ManageGroupsEdit = {
name: 'inventoryManage.editGroup',
route: '/edit-group?group_id',
ncyBreadcrumb: {
label: "EDIT {{group.name}}"
},
data: {
mode: 'edit'
},
resolve: {
groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService){
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
}],
inventorySourceData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService){
return GroupManageService.getInventorySource({group: $stateParams.group_id}).then(res => res.data.results[0]);
}]
},
views: {
'form@inventoryManage': {
controller: editController,
templateUrl: templateUrl('inventories/manage/groups/groups-form'),
}
}
};
var ManageGroupsAdd = {
name: 'inventoryManage.addGroup',
route: '/add-group',
// use a query string to break regex search
ncyBreadcrumb: {
label: "ADD GROUP"
},
data: {
mode: 'add'
},
views: {
'form@inventoryManage': {
controller: addController,
templateUrl: templateUrl('inventories/manage/groups/groups-form'),
}
}
};
export {ManageGroupsAdd, ManageGroupsEdit};

View File

@ -0,0 +1,113 @@
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){
return {
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&';
}, '');
},
// cute abstractions via fn.bind()
url: function(){
return '';
},
error: function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + this.url + '. GET returned: ' + status });
},
success: function(data){
return data;
},
// HTTP methods
get: function(params){
Wait('start');
this.url = GetBasePath('groups') + '?' + this.stringifyParams(params);
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
post: function(group){
Wait('start');
this.url = GetBasePath('groups');
Rest.setUrl(this.url);
return Rest.post(group)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
put: function(group){
Wait('start');
this.url = GetBasePath('groups') + group.id;
Rest.setUrl(this.url);
return Rest.put(group)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
delete: function(id){
Wait('start');
this.url = GetBasePath('groups') + id;
Rest.setUrl(this.url);
return Rest.destroy()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
getCredential: function(id){
Wait('start');
this.url = GetBasePath('credentials') + id;
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
getInventorySource: function(params){
Wait('start');
this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params);
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
putInventorySource: function(params, url){
Wait('start');
this.url = url;
Rest.setUrl(this.url);
return Rest.put(params)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
// these relationship setters could be consolidated, but verbosity makes the operation feel more clear @ controller level
associateGroup: function(group, target){
Wait('start');
this.url = GetBasePath('groups') + target + '/children/';
Rest.setUrl(this.url);
return Rest.post(group)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
disassociateGroup: function(group, parent){
Wait('start');
this.url = GetBasePath('groups') + parent + '/children/';
Rest.setUrl(this.url);
return Rest.post({id: group, disassociate: 1})
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
promote: function(group, inventory){
Wait('start');
this.url = GetBasePath('inventory') + inventory + '/groups/';
Rest.setUrl(this.url);
return Rest.post({id: group, disassociate: 1})
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
}
};
}];

View File

@ -0,0 +1,14 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {ManageGroupsAdd, ManageGroupsEdit} from './groups.route';
export default
angular.module('manageGroups', [])
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(ManageGroupsAdd);
$stateExtender.addState(ManageGroupsEdit);
}]);

View File

@ -5,16 +5,16 @@
*************************************************/
export default
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'ManageHostsService',
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, ManageHostsService){
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService',
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService){
var generator = GenerateForm,
form = HostForm;
$scope.parseType = 'yaml';
$scope.extraVars = '---';
$scope.formCancel = function(){
$state.go('^', null, {reload: true});
$state.go('^');
};
$scope.toggleEnabled = function(){
$scope.toggleHostEnabled = function(){
$scope.host.enabled = !$scope.host.enabled;
};
$scope.formSave = function(){
@ -25,8 +25,16 @@
enabled: $scope.host.enabled,
inventory: $stateParams.inventory_id
};
ManageHostsService.post(params).then(function(){
$state.go('^', null, {reload: true});
HostManageService.post(params).then(function(res){
// assign the host to current group if not at the root level
if ($stateParams.group){
HostManageService.associateGroup(res.data, _.last($stateParams.group)).then(function(){
$state.go('inventoryManage', null, {reload: true});
});
}
else{
$state.go('inventoryManage', null, {reload: true});
}
});
};
var init = function(){

View File

@ -5,13 +5,13 @@
*************************************************/
export default
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'ManageHostsService', 'host',
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, ManageHostsService, host){
['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'host',
function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host){
var generator = GenerateForm,
form = HostForm;
$scope.parseType = 'yaml';
$scope.formCancel = function(){
$state.go('^', null, {reload: true});
$state.go('^');
};
$scope.toggleHostEnabled = function(){
$scope.host.enabled = !$scope.host.enabled;
@ -24,7 +24,7 @@
description: $scope.description,
enabled: $scope.host.enabled
};
ManageHostsService.put(host).then(function(){
HostManageService.put(host).then(function(){
$state.go('^', null, {reload: true});
});
};

View File

@ -0,0 +1,84 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryHosts', 'generateList', 'InventoryManageService', 'HostManageService',
'hostsUrl', 'SearchInit', 'PaginateInit', 'SetStatus', 'Prompt', 'Wait', 'inventoryData',
function($scope, $rootScope, $state, $stateParams, InventoryHosts, generateList, InventoryManageService, HostManageService,
hostsUrl, SearchInit, PaginateInit, SetStatus, Prompt, Wait, inventoryData){
var list = InventoryHosts,
view = generateList,
pageSize = 20;
$scope.createHost = function(){
$state.go('inventoryManage.addHost');
};
$scope.editHost = function(id){
$state.go('inventoryManage.editHost', {host_id: id});
};
$scope.deleteHost = function(id, name){
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + name + '</div>';
var action = function(){
delete $rootScope.promptActionBtnClass;
Wait('start');
HostManageService.delete(id).then(() => {
$('#prompt-modal').modal('hide');
$state.go($state.current.name, null, {reload: true});
Wait('stop');
});
};
// Prompt depends on having $rootScope.promptActionBtnClass available...
Prompt({
hdr: 'Delete Host',
body: body,
action: action,
actionText: 'DELETE',
});
$rootScope.promptActionBtnClass = 'Modal-errorButton';
};
$scope.copyMoveHost = function(id){
$state.go('inventoryManage.copyMoveHost', {host_id: id});
};
$scope.systemTracking = function(){
var hostIds = _.map($scope.$parent.hostsSelectedItems, (host) => host.id);
$state.go('systemTracking', {
inventory: inventoryData,
inventoryId: $stateParams.inventory_id,
hosts: $scope.$parent.hostsSelectedItems,
hostIds: hostIds
});
};
// $scope.$parent governed by InventoryManageController, for unified multiSelect options
$scope.$on('multiSelectList.selectionChanged', (event, selection) => {
$scope.$parent.hostsSelected = selection.length > 0 ? true : false;
$scope.$parent.hostsSelectedItems = selection.selectedItems;
$scope.$parent.systemTrackingDisabled = selection.length > 0 && selection.length < 3 ? false : true;
$scope.$parent.systemTrackingTooltip = selection.length === 1 ? "Compare host facts over time" : "Compare hosts' facts";
});
$scope.$on('PostRefresh', ()=>{
_.forEach($scope.hosts, (host) => SetStatus({scope: $scope, host: host}));
});
var init = function(){
list.basePath = hostsUrl;
view.inject(list,{
id: 'hosts-list',
scope: $scope,
mode: 'edit'
});
SearchInit({
scope: $scope,
list: list,
url: hostsUrl,
set: 'hosts'
});
PaginateInit({
scope: $scope,
list: list,
url: hostsUrl,
pageSize: pageSize
});
$scope.search(list.iterator);
};
init();
}];

View File

@ -0,0 +1,50 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory';
import addController from './hosts-add.controller';
import editController from './hosts-edit.controller';
var ManageHostsEdit = {
name: 'inventoryManage.editHost',
route: '/edit-host?host_id',
ncyBreadcrumb: {
label: "EDIT {{host.name}}",
},
data: {
mode: 'edit'
},
resolve: {
host: ['$stateParams', 'HostManageService', function($stateParams, HostManageService){
return HostManageService.get({id: $stateParams.host_id}).then(function(res){
return res.data.results[0];
});
}]
},
views: {
'form@inventoryManage': {
controller: editController,
templateUrl: templateUrl('inventories/manage/hosts/hosts-form'),
}
}
};
var ManageHostsAdd = {
name: 'inventoryManage.addHost',
route: '/add-host',
// use a query string to break regex search
ncyBreadcrumb: {
label: "ADD HOST"
},
data: {
mode: 'add'
},
views: {
'form@inventoryManage': {
controller: addController,
templateUrl: templateUrl('inventories/manage/hosts/hosts-form'),
}
}
};
export {ManageHostsAdd, ManageHostsEdit};

View File

@ -0,0 +1,84 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){
return {
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&';
}, '');
},
// cute abstractions via fn.bind()
url: function(){
return '';
},
error: function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + this.url + '. GET returned: ' + status });
},
success: function(data){
return data;
},
// HTTP methods
get: function(params){
Wait('start');
this.url = GetBasePath('hosts') + '?' + this.stringifyParams(params);
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
post: function(host){
Wait('start');
this.url = GetBasePath('hosts');
Rest.setUrl(this.url);
return Rest.post(host)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
put: function(host){
Wait('start');
this.url = GetBasePath('hosts') + host.id;
Rest.setUrl(this.url);
return Rest.put(host)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
delete: function(id){
Wait('start');
this.url = GetBasePath('hosts') + id;
Rest.setUrl(this.url);
return Rest.destroy()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
// these relationship setters could be consolidated, but verbosity makes the operation feel more clear @ controller level
associateGroup: function(host, group){
Wait('start');
this.url = GetBasePath('groups') + group + '/hosts/';
Rest.setUrl(this.url);
return Rest.post(host)
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
disassociateGroup: function(host, group){
Wait('start');
this.url = GetBasePath('groups') + group + '/hosts/';
Rest.setUrl(this.url);
return Rest.post({id: host.id, disassociate: 1})
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
}
};
}];

View File

@ -4,13 +4,11 @@
* All Rights Reserved
*************************************************/
import {ManageHostsAdd, ManageHostsEdit} from './manage-hosts.route';
import service from './manage-hosts.service';
import {ManageHostsAdd, ManageHostsEdit} from './hosts.route';
export default
angular.module('manageHosts', [])
.service('ManageHostsService', service)
.run(['$stateExtender', function($stateExtender){
.run(['$stateExtender', '$state', function($stateExtender, $state){
$stateExtender.addState(ManageHostsAdd);
$stateExtender.addState(ManageHostsEdit);
}]);

View File

@ -0,0 +1,3 @@
.InventoryManage-container{
margin-top: -40px;
}

View File

@ -3,532 +3,18 @@
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name controllers.function:Inventories
* @description This controller's for the Inventory page
*/
function InventoriesManage($log, $scope, $rootScope, $location,
$state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert,
GetBasePath, ProcessErrors, InventoryGroups,
InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg,
GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate,
ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete,
EditInventoryProperties, ShowJobSummary,
InventoryGroupsHelp, HelpDialog,
GroupsCopy, HostsCopy, $stateParams, ParamPass) {
var PreviousSearchParams,
url,
hostScope = $scope.$new();
ClearScope();
// TODO: only display adhoc button if the user has permission to use it.
// TODO: figure out how to get the action-list partial to update so that
// the tooltip can be changed based off things being selected or not.
$scope.adhocButtonTipContents = "Launch adhoc command for the inventory";
// watcher for the group list checkbox changes
$scope.$on('multiSelectList.selectionChanged', function(e, selection) {
if (selection.length > 0) {
$scope.groupsSelected = true;
// $scope.adhocButtonTipContents = "Launch adhoc command for the "
// + "selected groups and hosts.";
} else {
$scope.groupsSelected = false;
// $scope.adhocButtonTipContents = "Launch adhoc command for the "
// + "inventory.";
}
$scope.groupsSelectedItems = selection.selectedItems;
});
// watcher for the host list checkbox changes
hostScope.$on('multiSelectList.selectionChanged', function(e, selection) {
// you need this so that the event doesn't bubble to the watcher above
// for the host list
e.stopPropagation();
var trackingButton = angular.element(document.querySelector('.system-tracking'));
trackingButton.html('SYSTEM TRACKING');
if (selection.length === 0) {
$scope.hostsSelected = false;
} else if (selection.length === 1) {
$scope.systemTrackingTooltip = "Compare host over time";
$scope.hostsSelected = true;
$scope.systemTrackingDisabled = false;
} else if (selection.length === 2) {
$scope.systemTrackingTooltip = "Compare hosts against each other";
$scope.hostsSelected = true;
$scope.systemTrackingDisabled = false;
} else {
$scope.hostsSelected = true;
$scope.systemTrackingDisabled = true;
}
$scope.hostsSelectedItems = selection.selectedItems;
});
$scope.systemTracking = function() {
var hostIds = _.map($scope.hostsSelectedItems, function(x){
return x.id;
});
$state.transitionTo('systemTracking',
{ inventory: $scope.inventory,
inventoryId: $scope.inventory.id,
hosts: $scope.hostsSelectedItems,
hostIds: hostIds
});
};
// populates host patterns based on selected hosts/groups
$scope.populateAdhocForm = function() {
var host_patterns = "all";
if ($scope.hostsSelected || $scope.groupsSelected) {
var allSelectedItems = [];
if ($scope.groupsSelectedItems) {
allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems);
}
if ($scope.hostsSelectedItems) {
allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems);
}
if (allSelectedItems) {
host_patterns = _.pluck(allSelectedItems, "name").join(":");
}
}
$rootScope.hostPatterns = host_patterns;
$state.go('inventoryManage.adhoc');
};
$scope.refreshHostsOnGroupRefresh = false;
$scope.selected_group_id = null;
Wait('start');
if ($scope.removeHostReloadComplete) {
$scope.removeHostReloadComplete();
}
$scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() {
if ($scope.initial_height) {
var host_height = $('#hosts-container .well').height(),
group_height = $('#group-list-container .well').height(),
new_height;
if (host_height > group_height) {
new_height = host_height - (host_height - group_height);
}
else if (host_height < group_height) {
new_height = host_height + (group_height - host_height);
}
if (new_height) {
$('#hosts-container .well').height(new_height);
}
$scope.initial_height = null;
}
});
if ($scope.removeRowCountReady) {
$scope.removeRowCountReady();
}
$scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) {
// Add hosts view
$scope.show_failures = false;
InjectHosts({
group_scope: $scope,
host_scope: hostScope,
inventory_id: $scope.inventory.id,
tree_id: null,
group_id: null,
pageSize: rows
});
SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups });
PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows });
$scope.search(InventoryGroups.iterator, null, true);
});
if ($scope.removeInventoryLoaded) {
$scope.removeInventoryLoaded();
}
$scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() {
var rows;
generateList.inject(InventoryGroups, {
mode: 'edit',
id: 'group-list-container',
searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12',
scope: $scope
});
rows = 20;
hostScope.host_page_size = rows;
$scope.group_page_size = rows;
$scope.show_failures = false;
InjectHosts({
group_scope: $scope,
host_scope: hostScope,
inventory_id: $scope.inventory.id,
tree_id: null,
group_id: null,
pageSize: rows
});
// Load data
SearchInit({
scope: $scope,
set: 'groups',
list: InventoryGroups,
url: $scope.inventory.related.root_groups
});
PaginateInit({
scope: $scope,
list: InventoryGroups ,
url: $scope.inventory.related.root_groups,
pageSize: rows
});
$scope.search(InventoryGroups.iterator, null, true);
$scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates
});
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) {
if (set === 'groups') {
$scope.groups.forEach( function(group, idx) {
var stat, hosts_status;
stat = GetSyncStatusMsg({
status: group.summary_fields.inventory_source.status,
has_inventory_sources: group.has_inventory_sources,
source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
}); // from helpers/Groups.js
$scope.groups[idx].status_class = stat['class'];
$scope.groups[idx].status_tooltip = stat.tooltip;
$scope.groups[idx].launch_tooltip = stat.launch_tip;
$scope.groups[idx].launch_class = stat.launch_class;
hosts_status = GetHostsStatusMsg({
active_failures: group.hosts_with_active_failures,
total_hosts: group.total_hosts,
inventory_id: $scope.inventory.id,
group_id: group.id
}); // from helpers/Groups.js
$scope.groups[idx].hosts_status_tip = hosts_status.tooltip;
$scope.groups[idx].show_failures = hosts_status.failures;
$scope.groups[idx].hosts_status_class = hosts_status['class'];
$scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null;
$scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null;
});
if ($scope.refreshHostsOnGroupRefresh) {
$scope.refreshHostsOnGroupRefresh = false;
HostsReload({
scope: hostScope,
group_id: $scope.selected_group_id,
inventory_id: $scope.inventory.id,
pageSize: hostScope.host_page_size
});
}
else {
Wait('stop');
}
}
});
// Load Inventory
url = GetBasePath('inventory') + $stateParams.inventory_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
$scope.inventory = data;
$scope.$emit('InventoryLoaded');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id +
' GET returned status: ' + status });
});
// start watching for real-time updates
if ($rootScope.removeWatchUpdateStatus) {
$rootScope.removeWatchUpdateStatus();
}
$rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) {
var stat, group;
if (data.group_id) {
group = Find({ list: $scope.groups, key: 'id', val: data.group_id });
if (data.status === "failed" || data.status === "successful") {
if (data.group_id === $scope.selected_group_id || group) {
// job completed, fefresh all groups
$log.debug('Update completed. Refreshing the tree.');
$scope.refreshGroups();
}
}
else if (group) {
// incremental update, just update
$log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status);
stat = GetSyncStatusMsg({
status: data.status,
has_inventory_sources: group.has_inventory_sources,
source: group.source
});
$log.debug('changing tooltip to: ' + stat.tooltip);
group.status = data.status;
group.status_class = stat['class'];
group.status_tooltip = stat.tooltip;
group.launch_tooltip = stat.launch_tip;
group.launch_class = stat.launch_class;
}
}
});
// Load group on selection
function loadGroups(url) {
SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url });
PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size });
$scope.search(InventoryGroups.iterator, null, true, false, true);
}
$scope.refreshHosts = function() {
HostsReload({
scope: hostScope,
group_id: $scope.selected_group_id,
inventory_id: $scope.inventory.id,
pageSize: hostScope.host_page_size
});
};
$scope.refreshGroups = function() {
$scope.refreshHostsOnGroupRefresh = true;
$scope.search(InventoryGroups.iterator, null, true, false, true);
};
$scope.restoreSearch = function() {
// Restore search params and related stuff, plus refresh
// groups and hosts lists
SearchInit({
scope: $scope,
set: PreviousSearchParams.set,
list: PreviousSearchParams.list,
url: PreviousSearchParams.defaultUrl,
iterator: PreviousSearchParams.iterator,
sort_order: PreviousSearchParams.sort_order,
setWidgets: false
});
$scope.refreshHostsOnGroupRefresh = true;
$scope.search(InventoryGroups.iterator, null, true, false, true);
};
$scope.groupSelect = function(id) {
var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id });
if($state.params.groups){
groups.push($state.params.groups);
}
groups.push(group.id);
groups = groups.join();
$state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false});
loadGroups(group.related.children, group.id);
$scope.selected_group_id = group.id;
HostsReload({
scope: hostScope,
group_id: $scope.selected_group_id,
inventory_id: $scope.inventory.id,
pageSize: hostScope.host_page_size
});
};
$scope.createGroup = function () {
PreviousSearchParams = Store('group_current_search_params');
var params = {
scope: $scope,
inventory_id: $scope.inventory.id,
group_id: $scope.selected_group_id,
mode: 'add'
};
ParamPass.set(params);
$state.go('inventoryManage.addGroup');
};
$scope.editGroup = function (id) {
PreviousSearchParams = Store('group_current_search_params');
var params = {
scope: $scope,
inventory_id: $scope.inventory.id,
group_id: id,
mode: 'edit'
};
ParamPass.set(params);
$state.go('inventoryManage.editGroup', {group_id: id});
};
// Launch inventory sync
$scope.updateGroup = function (id) {
var group = Find({ list: $scope.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>' +
group.name + '</em> Click the <i class="fa fa-refresh"></i> button to monitor the status.', 'alert-info', null, null, null, null, true);
} else {
Wait('start');
Rest.setUrl(group.related.inventory_source);
Rest.get()
.success(function (data) {
InventoryUpdate({
scope: $scope,
url: data.related.update,
group_name: data.summary_fields.group.name,
group_source: data.source,
group_id: group.id,
});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
group.related.inventory_source + ' GET returned status: ' + status });
});
}
}
};
$scope.cancelUpdate = function (id) {
GroupsCancelUpdate({ scope: $scope, id: id });
};
$scope.viewUpdateStatus = function (id) {
ViewUpdateStatus({
scope: $scope,
group_id: id
});
};
$scope.scheduleGroup = function(id) {
$state.go('inventoryManageSchedules', {
inventory_id: $scope.inventory.id, id: id
});
};
$scope.copyGroup = function(id) {
PreviousSearchParams = Store('group_current_search_params');
var params = {
scope: $scope
};
ParamPass.set(params);
$location.search('groups', null);
$state.go('inventoryManage.copy.group', {group_id: id});
};
$scope.deleteGroup = function (id) {
GroupsDelete({
scope: $scope,
group_id: id,
inventory_id: $scope.inventory.id
});
};
$scope.editInventoryProperties = function () {
// EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id });
$location.path('/inventories/' + $scope.inventory.id + '/');
};
hostScope.createHost = function () {
var params = {
host_scope: hostScope,
group_scope: $scope,
mode: 'add',
host_id: null,
selected_group_id: $scope.selected_group_id,
inventory_id: $scope.inventory.id
};
ParamPass.set(params);
$state.go('inventoryManage.addHost');
};
hostScope.editHost = function (host_id) {
var params = {
host_scope: hostScope,
group_scope: $scope,
mode: 'edit',
host_id: host_id,
inventory_id: $scope.inventory.id
};
ParamPass.set(params);
$state.go('inventoryManage.editHost', {host_id: host_id});
};
hostScope.deleteHost = function (host_id, host_name) {
HostsDelete({
parent_scope: $scope,
host_scope: hostScope,
host_id: host_id,
host_name: host_name
});
};
hostScope.copyHost = function(id) {
PreviousSearchParams = Store('group_current_search_params');
var params = {
group_scope: $scope,
host_scope: hostScope,
host_id: id
};
ParamPass.set(params);
$state.go('inventoryManage.copy.host', {host_id: id});
};
hostScope.showJobSummary = function (job_id) {
ShowJobSummary({
job_id: job_id
});
};
$scope.showGroupHelp = function (params) {
var opts = {
defn: InventoryGroupsHelp
};
if (params) {
opts.autoShow = params.autoShow || false;
}
HelpDialog(opts);
}
;
$scope.showHosts = function (group_id, show_failures) {
// Clicked on group
if (group_id !== null) {
Wait('start');
hostScope.show_failures = show_failures;
$scope.groupSelect(group_id);
hostScope.hosts = [];
$scope.show_failures = show_failures; // turn on failed hosts
// filter in hosts view
} else {
Wait('stop');
}
};
if ($scope.removeGroupDeleteCompleted) {
$scope.removeGroupDeleteCompleted();
}
$scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted',
function() {
$scope.refreshGroups();
}
);
}
export default [
'$log', '$scope', '$rootScope', '$location',
'$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait',
'Rest', 'Alert', 'GetBasePath', 'ProcessErrors',
'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload',
'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg',
'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus',
'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete',
'EditInventoryProperties', 'ShowJobSummary', 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy',
'HostsCopy', '$stateParams', 'ParamPass', InventoriesManage
];
export default
['$scope', '$state', function($scope, $state){
$scope.groupsSelected = false;
$scope.hostsSelected = false;
$scope.hostsSelectedItems = [];
$scope.groupsSelectedItems = [];
$scope.setAdhocPattern = function(){
var pattern = _($scope.groupsSelectedItems)
.concat($scope.hostsSelectedItems)
.map(function(item){
return item.name;
}).value().join(':');
$state.go('inventoryManage.adhoc', {pattern: pattern});
};
}];

View File

@ -1,152 +1,10 @@
<div class="tab-pane" id="inventory_edit">
<div ui-view></div>
<div class="tab-pane InventoryManage-container" id="inventory_edit">
<div ui-view="form"></div>
<div ng-cloak id="htmlTemplate">
<div class="row">
<div id="groups-container" class="col-lg-6">
<div id="group-list-container" class="Panel"></div>
</div>
<div id="hosts-container" class="col-lg-6">
<div id="host-list-container" class="Panel"></div>
</div>
<div ui-view="groupsList" class="col-lg-6"></div>
<div ui-view="hostsList" class="col-lg-6"></div>
</div>
<div id="group-copy-dialog" style="display: none;">
<div id="copy-group-radio-container" class="well">
<div class="title"><span class="highlight">1.</span> Copy or move <span ng-bind="name"></span>?</div>
<div class="form-group">
<label class="radio-inline">
<input type="radio" ng-model="copy_choice" value="copy"> Copy
</label>
<label class="radio-inline">
<input type="radio" ng-model="copy_choice" value="move"> Move
</label>
</div>
</div>
<div id="copy-group-target-container" class="well">
<div class="title" ng-show="offer_root_group"><span class="highlight">2.</span> Select a target group below, OR choose the inventory root:
<div class="form-group">
<label><input type="checkbox" ng-model="use_root_group" ng-change="toggleUseRootGroup()"> Use the inventory root</label>
</div>
</div>
<div class="title" ng-show="!offer_root_group"><span class="highlight">2.</span> Select a target group:</div>
<div id="copy-select-container" ng-show="!use_root_group"></div>
</div>
</div>
<div id="host-copy-dialog" style="display: none;">
<div id="copy-group-radio-container" class="well">
<div class="title"><span class="highlight">1.</span> Copy or move <span ng-bind="name"></span>?</div>
<div class="form-group">
<label class="radio-inline">
<input type="radio" ng-model="copy_choice" value="copy"> Copy
</label>
<label class="radio-inline">
<input type="radio" ng-model="copy_choice" value="move"> Move
</label>
</div>
</div>
<div id="copy-group-target-container" class="well">
<div class="title"><span class="highlight">2.</span> Select a target group:</div>
<div id="copy-host-select-container"></div>
</div>
</div>
<div id="group-modal-dialog" style="display: none;">
<ul id="group_tabs" class="nav nav-tabs">
<li class="active"><a id="properties_link" ng-click="toggleTab($event, 'properties_link', 'group_tabs')"
href="#properties-tab" data-toggle="tab">Properties</a></li>
<li ng-show="showSourceTab"><a id="source_link" ng-click="toggleTab($event, 'source_link', 'group_tabs')"
href="#sources-tab" data-toggle="tab">Source</a></li>
<li ng-show="showSchedulesTab"><a id="schedules_link" ng-click="toggleTab($event, 'schedules_link', 'group_tabs')"
href="#schedules-tab" data-toggle="tab">Schedule</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="properties-tab"></div>
<div class="tab-pane" id="sources-tab"></div>
<div class="tab-pane" id="schedules-tab">
<div id="schedules-overlay"></div>
<div id="schedules-list"></div>
<div id="schedules-form-container">
<div id="schedules-title">
<h4 ng-bind="schedulesTitle"></h4>
<button type="button" class="close pull-right" ng-click="cancelScheduleForm()">x</button>
</div>
<div id="schedules-form-container-body">
<div id="schedules-form"></div>
<div id="schedules-detail"></div>
</div>
<div id="schedules-buttons">
<a id="schedules-flip-link" ng-show="formShowing" ng-click="showScheduleDetail()" href=""><i class="fa fa-search-plus"></i> View Details</a>
<a id="schedules-flip-link" ng-show="!formShowing" ng-click="showScheduleDetail()" href=""><i class="fa fa-arrow-circle-left"></i> Back to options</a>
<button type="button" class="btn btn-default btn-sm" id="reset-button" ng-click="cancelScheduleForm()"><i class="fa fa-times"></i> Cancel</button>
<button type="button" class="btn btn-primary btn-sm" id="save-button" ng-click="saveScheduleForm()"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
</div>
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div>
<div id="group-delete-dialog" style="display: none;" class="dialog-content">
<div ng-show="groupsCount > 0 || hostsCount > 0">
<div class="alert alert-info">
<p>Deleting group <em>{{ group_name }}</em>.
<span ng-show="groupsCount > 0 && hostsCount > 0"> This group contains {{ groupsCount }} groups and {{ hostsCount }} hosts. </span>
<span ng-show="groupsCount == 0 && hostsCount > 0"> This group contains {{ hostsCount }} hosts. </span>
<span ng-show="groupsCopunt > 0 && hostsCount == 0"> This group contains {{ groupsCount }} groups. </span>
Delete or promote the group's children?</p>
<div style="width: 50%; margin: 15px auto;">
<div class="radio" ng-show="groupsCount > 0 && hostsCount > 0">
<label>
<input type="radio" ng-model="deleteOption" name="delete_option1" value="preserve-all"> Promote groups and hosts
</label>
</div>
<div class="radio" ng-show="groupsCount > 0 && hostsCount > 0">
<label>
<input type="radio" ng-model="deleteOption" name="delete_option1" value="delete-all"> Delete groups and hosts
</label>
</div>
<div class="radio" ng-show="groupsCount > 0 && hostsCount == 0">
<label>
<input type="radio" ng-model="deleteOption" name="delete_option2" value="preserve-all"> Promote groups
</label>
</div>
<div class="radio" ng-show="groupsCount > 0 && hostsCount == 0">
<label>
<input type="radio" ng-model="deleteOption" name="delete_option2" value="delete-all"> Delete groups
</label>
</div>
<div class="radio" ng-show="groupsCount == 0 && hostsCount > 0">
<label>
<input type="radio" ng-model="deleteOption" name="delete_option3" value="preserve-all"> Promote hosts
</label>
</div>
<div class="radio" ng-show="groupsCount == 0 && hostsCount > 0">
<label>
<input type="radio" ng-model="deleteOption" name="delete_option3" value="delete-all"> Delete hosts
</label>
</div>
</div>
</div>
<div class="help-container" style="text-align:right;">
<a href="" id="awp-promote" href="" aw-pop-over="{{ helpText }}" aw-tool-tip="Click for help" aw-pop-over-watch="helpText" data-placement="top" data-container="body" data-title="Help" class="help-link"><i class="fa fa-question-circle"></i> click for help</a>
</div>
</div>
<div ng-show="groupsCount == 0 && hostsCount == 0">
<div class="alert alert-info">Delete group <em>{{ group_name }}</em>?</div>
</div>
</div>
<div id="inventory-edit-modal-dialog"></div>
<div ng-include="'/static/partials/logviewer.html'"></div>
</div>
</div>

View File

@ -6,18 +6,71 @@
import {templateUrl} from '../../shared/template-url/template-url.factory';
import InventoriesManage from './inventory-manage.controller';
import BreadcrumbsController from './breadcrumbs/breadcrumbs.controller';
import HostsListController from './hosts/hosts-list.controller';
import GroupsListController from './groups/groups-list.controller';
export default {
name: 'inventoryManage',
url: '/inventories/:inventory_id/manage?groups',
templateUrl: templateUrl('inventories/manage/inventory-manage'),
controller: InventoriesManage,
url: '/inventories/:inventory_id/manage?{group:int}{failed}',
data: {
activityStream: true,
activityStreamTarget: 'inventory',
activityStreamId: 'inventory_id'
},
params:{
group:{
array: true
},
failed:{
value: 'false',
squash: true
}
},
ncyBreadcrumb: {
label: "INVENTORY MANAGE"
skip: true // Never display this state in ncy-breadcrumb.
},
// enforce uniqueness in group param
onEnter: function($stateParams){
$stateParams.group = _.uniq($stateParams.group);
},
resolve: {
groupsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams){
return !$stateParams.group ?
InventoryManageService.rootGroupsUrl($stateParams.inventory_id) :
InventoryManageService.childGroupsUrl(_.last($stateParams.group));
}],
hostsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams){
// at the root group level
return !$stateParams.group ?
InventoryManageService.rootHostsUrl($stateParams.inventory_id, $stateParams.failed) :
InventoryManageService.childHostsUrl(_.last($stateParams.group, $stateParams.failed));
}],
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams){
return InventoryManageService.getInventory($stateParams.inventory_id).then(res => res.data);
}],
breadCrumbData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams){
return ( (!$stateParams.group) ? false : InventoryManageService.getBreadcrumbs($stateParams.group).then(res => res.data.results));
}]
},
views:{
// target the ui-view with name "groupBreadcrumbs" at the root template level
'groupBreadcrumbs@': {
controller: BreadcrumbsController,
templateUrl: templateUrl('inventories/manage/breadcrumbs/breadcrumbs')
},
'': {
templateUrl: templateUrl('inventories/manage/inventory-manage'),
controller: InventoriesManage
},
// target ui-views with name@inventoryManage template level
'groupsList@inventoryManage': {
templateUrl: templateUrl('inventories/manage/groups/groups-list'),
controller: GroupsListController
},
'hostsList@inventoryManage': {
template: '<div id="hosts-list" class="Panel"></div>',
controller: HostsListController
}
}
};

View File

@ -0,0 +1,63 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){
return {
// cute abstractions via fn.bind()
url: function(){
return '';
},
error: function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + this.url + '. GET returned: ' + status });
},
success: function(data){
return data;
},
// data getters
getInventory: function(id){
Wait('start');
this.url = GetBasePath('inventory') + id;
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally(Wait('stop'));
},
getBreadcrumbs: function(groups){
Wait('start');
this.url = GetBasePath('groups') + '?' + _.map(groups, function(item){
return '&or__id=' + item;
}).join('');
Rest.setUrl(this.url);
return Rest.get()
.success(this.success.bind(this))
.error(this.error.bind(this));
},
// these methods generate a query string to pass to PaginateInit(), SearchInit()
// always supply trailing slashes and ? prefix
rootHostsUrl: function(id, failed){
var url = GetBasePath('inventory') + id + '/hosts' +
(failed === 'true' ? '?has_active_failures=true' : '?');
return url;
},
childHostsUrl: function(id, failed){
var url = GetBasePath('groups') + id + '/hosts' +
(failed === 'true' ? '?has_active_failures=true' : '?');
return url;
},
childGroupsUrl: function(id){
var url = GetBasePath('groups') + id + '/children?';
return url;
},
rootGroupsUrl: function(id){
var url = GetBasePath('inventory') + id+ '/root_groups/';
return url;
}
};
}];

View File

@ -1,21 +1,28 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import route from './inventory-manage.route';
import manageHosts from './manage-hosts/main';
import manageGroups from './manage-groups/main';
import copy from './copy/main';
import InventoryManageService from './inventory-manage.service';
import HostManageService from './hosts/hosts.service';
import GroupManageService from './groups/groups.service';
import hosts from './hosts/main';
import groups from './groups/main';
import adhoc from './adhoc/main';
import copyMove from './copy-move/main';
export default
angular.module('inventoryManage', [
manageHosts.name,
manageGroups.name,
copy.name,
hosts.name,
groups.name,
copyMove.name,
adhoc.name
])
.service('InventoryManageService', InventoryManageService)
.service('HostManageService', HostManageService)
.service('GroupManageService', GroupManageService)
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}]);

View File

@ -1,574 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
function manageGroupsDirectiveController($filter, $location, $log,
$stateParams, $compile, $state, $scope, Rest, Alert, GroupForm,
GenerateForm, Prompt, ProcessErrors, GetBasePath, SetNodeName,
ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty,
Wait, GetChoices, UpdateGroup, SourceChange, Find, ParseVariableString,
ToJSON, GroupsScheduleListInit, SetSchedulesInnerDialogSize,
CreateSelect2, ToggleNotification, NotificationsListInit,
RelatedSearchInit, RelatedPaginateInit) {
var vm = this;
var group_id = $stateParams.group_id,
mode = $state.current.data.mode,
inventory_id = $stateParams.inventory_id,
generator = GenerateForm,
group_created = false,
defaultUrl,
master = {},
form = GroupForm(),
relatedSets = {},
choicesReady, group;
if (mode === 'edit') {
defaultUrl = GetBasePath('groups') + group_id + '/';
} else {
defaultUrl = (group_id !== undefined) ? GetBasePath('groups') + group_id + '/children/' :
GetBasePath('inventory') + inventory_id + '/groups/';
}
Rest.setUrl(defaultUrl);
Rest.get()
.success(function(data) {
group = data;
for (var fld in form.fields) {
if (data[fld]) {
$scope[fld] = data[fld];
master[fld] = $scope[fld];
}
}
if(mode === 'edit') {
$scope.variable_url = data.related.variable_data;
$scope.source_url = data.related.inventory_source;
$scope.source_id = data.related.inventory_source.split('/')[4];
$scope.$emit('LoadSourceData');
}
})
.error(function(data, status) {
ProcessErrors($scope, data, status, {
hdr: 'Error!',
msg: 'Failed to retrieve group: ' + defaultUrl + '. GET status: ' + status
});
});
$scope.parseType = 'yaml';
generator.inject(form, {
mode: mode,
id: 'group-manage-panel',
scope: $scope,
related: false,
cancelButton: false
});
generator.reset();
GetSourceTypeOptions({
scope: $scope,
variable: 'source_type_options'
});
$scope.source = form.fields.source['default'];
$scope.sourcePathRequired = false;
$scope[form.fields.source_vars.parseTypeName] = 'yaml';
$scope.update_cache_timeout = 0;
$scope.parseType = 'yaml';
function initSourceChange() {
$scope.showSchedulesTab = (mode === 'edit' && $scope.source && $scope.source.value !== "manual") ? true : false;
SourceChange({
scope: $scope,
form: form
});
}
// JT -- this gets called after the properties & properties variables are loaded, and is emitted from (groupLoaded)
if ($scope.removeLoadSourceData) {
$scope.removeLoadSourceData();
}
$scope.removeLoadSourceData = $scope.$on('LoadSourceData', function() {
ParseTypeChange({
scope: $scope,
variable: 'variables',
parse_variable: 'parseType',
field_id: 'group_variables'
});
NotificationsListInit({
scope: $scope,
url: GetBasePath('inventory_sources'),
id: $scope.source_id
});
if ($scope.source_url) {
// get source data
Rest.setUrl($scope.source_url);
Rest.get()
.success(function(data) {
var fld, i, j, flag, found, set, opts, list;
for (fld in form.fields) {
if (fld === 'checkbox_group') {
for (i = 0; i < form.fields[fld].fields.length; i++) {
flag = form.fields[fld].fields[i];
if (data[flag.name] !== undefined) {
$scope[flag.name] = data[flag.name];
master[flag.name] = $scope[flag.name];
}
}
}
if (fld === 'source') {
found = false;
data.source = (data.source === "") ? "manual" : data.source;
for (i = 0; i < $scope.source_type_options.length; i++) {
if ($scope.source_type_options[i].value === data.source) {
$scope.source = $scope.source_type_options[i];
found = true;
}
}
if (!found || $scope.source.value === "manual") {
$scope.groupUpdateHide = true;
} else {
$scope.groupUpdateHide = false;
}
master.source = $scope.source;
} else if (fld === 'source_vars') {
// Parse source_vars, converting to YAML.
$scope.source_vars = ParseVariableString(data.source_vars);
master.source_vars = $scope.variables;
} else if (fld === "inventory_script") {
// the API stores it as 'source_script', we call it inventory_script
data.summary_fields.inventory_script = data.summary_fields.source_script;
$scope.inventory_script = data.source_script;
master.inventory_script = $scope.inventory_script;
} else if (fld === "source_regions") {
if (data[fld] === "") {
$scope[fld] = data[fld];
master[fld] = $scope[fld];
} else {
$scope[fld] = data[fld].split(",");
master[fld] = $scope[fld];
}
} else if (data[fld] !== undefined &&
fld !== "description" &&
fld !== "name" &&
fld !== "variables") {
$scope[fld] = data[fld];
master[fld] = $scope[fld];
}
if (form.fields[fld].sourceModel && data.summary_fields &&
data.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
relatedSets = form.relatedSets(data.related);
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
initSourceChange();
if (data.source_regions) {
if (data.source === 'ec2' ||
data.source === 'rax' ||
data.source === 'gce' ||
data.source === 'azure') {
if (data.source === 'ec2') {
set = $scope.ec2_regions;
} else if (data.source === 'rax') {
set = $scope.rax_regions;
} else if (data.source === 'gce') {
set = $scope.gce_regions;
} else if (data.source === 'azure') {
set = $scope.azure_regions;
}
opts = [];
list = data.source_regions.split(',');
for (i = 0; i < list.length; i++) {
for (j = 0; j < set.length; j++) {
if (list[i] === set[j].value) {
opts.push({
id: set [j].value,
text: set [j].label
});
}
}
}
master.source_regions = opts;
CreateSelect2({
element: "group_source_regions",
multiple: true,
opts: opts
});
}
} else {
// If empty, default to all
master.source_regions = [{
id: 'all',
text: 'All'
}];
}
if (data.group_by && data.source === 'ec2') {
set = $scope.ec2_group_by;
opts = [];
list = data.group_by.split(',');
for (i = 0; i < list.length; i++) {
for (j = 0; j < set.length; j++) {
if (list[i] === set[j].value) {
opts.push({
id: set [j].value,
text: set [j].label
});
}
}
}
master.group_by = opts;
CreateSelect2({
element: "#group_group_by",
multiple: true,
opts: opts
});
}
$scope.group_update_url = data.related.update;
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);
}
})
.error(function(data, status) {
$scope.source = "";
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to retrieve inventory source. GET status: ' + status
});
});
}
});
if ($scope.remove$scopeSourceTypeOptionsReady) {
$scope.remove$scopeSourceTypeOptionsReady();
}
$scope.remove$scopeSourceTypeOptionsReady = $scope.$on('sourceTypeOptionsReady', function() {
if (mode === 'add') {
$scope.source = Find({
list: $scope.source_type_options,
key: 'value',
val: ''
});
$scope.showSchedulesTab = false;
}
});
choicesReady = 0;
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReadyGroup', function() {
CreateSelect2({
element: '#group_source',
multiple: false
});
$scope.$emit('LoadSourceData');
choicesReady++;
if (choicesReady === 5) {
if (mode !== 'edit') {
$scope.variables = "---";
master.variables = $scope.variables;
}
}
});
// Load options for source regions
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'rax_regions',
choice_name: 'rax_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'ec2_regions',
choice_name: 'ec2_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'gce_regions',
choice_name: 'gce_region_choices',
callback: 'choicesReadyGroup'
});
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source_regions',
variable: 'azure_regions',
choice_name: 'azure_region_choices',
callback: 'choicesReadyGroup'
});
// Load options for group_by
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'group_by',
variable: 'ec2_group_by',
choice_name: 'ec2_group_by_choices',
callback: 'choicesReadyGroup'
});
//Wait('start');
if ($scope.removeAddTreeRefreshed) {
$scope.removeAddTreeRefreshed();
}
$scope.removeAddTreeRefreshed = $scope.$on('GroupTreeRefreshed', function() {
// Clean up
Wait('stop');
if ($scope.searchCleanUp) {
$scope.searchCleanup();
}
try {
//$('#group-modal-dialog').dialog('close');
} catch (e) {
// ignore
}
});
if ($scope.removeSaveComplete) {
$scope.removeSaveComplete();
}
$scope.removeSaveComplete = $scope.$on('SaveComplete', function(e, error) {
if (!error) {
$scope.formCancel();
}
});
if ($scope.removeFormSaveSuccess) {
$scope.removeFormSaveSuccess();
}
$scope.removeFormSaveSuccess = $scope.$on('formSaveSuccess', function() {
// Source data gets stored separately from the group. Validate and store Source
// related fields, then call SaveComplete to wrap things up.
var parseError = false,
regions, r, i,
group_by,
data = {
group: group_id,
source: (($scope.source && $scope.source.value !== 'manual') ? $scope.source.value : ''),
source_path: $scope.source_path,
credential: $scope.credential,
overwrite: $scope.overwrite,
overwrite_vars: $scope.overwrite_vars,
source_script: $scope.inventory_script,
update_on_launch: $scope.update_on_launch,
update_cache_timeout: ($scope.update_cache_timeout || 0)
};
// Create a string out of selected list of regions
if ($scope.source_regions) {
regions = $('#group_source_regions').select2("data");
r = [];
for (i = 0; i < regions.length; i++) {
r.push(regions[i].id);
}
data.source_regions = r.join();
}
if ($scope.source && ($scope.source.value === 'ec2')) {
data.instance_filters = $scope.instance_filters;
// Create a string out of selected list of regions
group_by = $('#group_group_by').select2("data");
r = [];
for (i = 0; i < group_by.length; i++) {
r.push(group_by[i].id);
}
data.group_by = r.join();
}
if ($scope.source && ($scope.source.value === 'ec2')) {
// for ec2, validate variable data
data.source_vars = ToJSON($scope.envParseType, $scope.source_vars, true);
}
if ($scope.source && ($scope.source.value === 'custom')) {
data.source_vars = ToJSON($scope.envParseType, $scope.extra_vars, true);
}
if ($scope.source && ($scope.source.value === 'vmware' ||
$scope.source.value === 'openstack')) {
data.source_vars = ToJSON($scope.envParseType, $scope.inventory_variables, true);
}
// the API doesn't expect the credential to be passed with a custom inv script
if ($scope.source && $scope.source.value === 'custom') {
delete(data.credential);
}
if (!parseError) {
Rest.setUrl($scope.source_url);
Rest.put(data)
.success(function() {
$scope.$emit('SaveComplete', false);
})
.error(function(data, status) {
$('#group_tabs a:eq(1)').tab('show');
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to update group inventory source. PUT status: ' + status
});
});
}
});
$scope.toggleNotification = function(event, notifier_id, column) {
var notifier = this.notification;
try {
$(event.target).tooltip('hide');
}
catch(e) {
// ignore
}
ToggleNotification({
scope: $scope,
url: GetBasePath('inventory_sources'),
id: $scope.source_id,
notifier: notifier,
column: column,
callback: 'NotificationRefresh'
});
};
// Cancel
$scope.formCancel = function() {
Wait('stop');
$state.go('inventoryManage', {}, {reload: true});
};
// Save
$scope.saveGroup = function() {
Wait('start');
var fld, data, json_data;
try {
json_data = ToJSON($scope.parseType, $scope.variables, true);
data = {};
for (fld in form.fields) {
data[fld] = $scope[fld];
}
data.inventory = inventory_id;
Rest.setUrl(defaultUrl);
if (mode === 'edit' || (mode === 'add' && group_created)) {
Rest.put(data)
.success(function() {
$scope.$emit('formSaveSuccess');
})
.error(function(data, status) {
$('#group_tabs a:eq(0)').tab('show');
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to update group: ' + group_id + '. PUT status: ' + status
});
});
} else {
Rest.post(data)
.success(function(data) {
group_created = true;
group_id = data.id;
$scope.source_url = data.related.inventory_source;
$scope.source_id = $scope.source_url.split('/')[4];
$scope.$emit('formSaveSuccess');
})
.error(function(data, status) {
$('#group_tabs a:eq(0)').tab('show');
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to create group: ' + group_id + '. POST status: ' + status
});
});
}
} catch (e) {
// ignore. ToJSON will have already alerted the user
}
};
// Start the update process
$scope.updateGroup = function() {
if ($scope.source === "manual" || $scope.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.status === 'updating') {
Alert('Update in Progress', 'The inventory update process is currently running for group <em>' +
$filter('sanitize')($scope.summary_fields.group.name) + '</em>. Use the Refresh button to monitor the status.', 'alert-info', null, null, null, null, true);
} else {
InventoryUpdate({
scope: $scope,
group_id: group_id,
url: $scope.group_update_url,
group_name: $scope.name,
group_source: $scope.source.value
});
}
};
// Change the lookup and regions when the source changes
$scope.sourceChange = function() {
$scope.credential_name = "";
$scope.credential = "";
if ($scope.credential_name_api_error) {
delete $scope.credential_name_api_error;
}
initSourceChange();
};
angular.extend(vm, {
formCancel : $scope.formCancel,
saveGroup: $scope.saveGroup
});
}
export default ['$filter', '$location', '$log', '$stateParams',
'$compile', '$state', '$scope', 'Rest', 'Alert', 'GroupForm',
'GenerateForm','Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName',
'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate', 'LookUpInit',
'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find',
'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit',
'SetSchedulesInnerDialogSize', 'CreateSelect2',
'ToggleNotification', 'NotificationsListInit', 'RelatedSearchInit',
'RelatedPaginateInit',
manageGroupsDirectiveController
];

View File

@ -1,25 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/* jshint unused: vars */
import manageGroupsDirectiveController from './manage-groups.directive.controller';
export default ['templateUrl', 'ParamPass',
function(templateUrl, ParamPass) {
return {
restrict: 'EA',
scope: true,
replace: true,
templateUrl: templateUrl('inventories/manage/manage-groups/directive/manage-groups.directive'),
link: function(scope, element, attrs) {
},
controller: manageGroupsDirectiveController,
controllerAs: 'vm',
bindToController: true
};
}
];

View File

@ -1,5 +0,0 @@
<div>
<div id="group-manage-panel">
<div id="properties-tab"></div>
</div>
</div>

View File

@ -1,16 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import route from './manage-groups.route';
import manageGroupsDirective from './directive/manage-groups.directive';
export default
angular.module('manage-groups', [])
.directive('manageGroups', manageGroupsDirective)
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route.edit);
$stateExtender.addState(route.add);
}]);

View File

@ -1,36 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {
templateUrl
} from '../../../shared/template-url/template-url.factory';
export default {
edit: {
name: 'inventoryManage.editGroup',
route: '/:group_id/editGroup',
templateUrl: templateUrl('inventories/manage/manage-groups/manage-groups'),
data: {
group_id: 'group_id',
mode: 'edit'
},
ncyBreadcrumb: {
label: "INVENTORY EDIT GROUPS"
}
},
add: {
name: 'inventoryManage.addGroup',
route: '/addGroup',
templateUrl: templateUrl('inventories/manage/manage-groups/manage-groups'),
ncyBreadcrumb: {
label: "INVENTORY ADD GROUP"
},
data: {
mode: 'add'
}
},
};

View File

@ -1,41 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../../shared/template-url/template-url.factory';
import addController from './manage-hosts-add.controller';
import editController from './manage-hosts-edit.controller';
var ManageHostsEdit = {
name: 'inventoryManage.editHost',
route: '/host/:host_id',
controller: editController,
templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'),
ncyBreadcrumb: {
label: "INVENTORY EDIT HOSTS"
},
data: {
mode: 'edit'
},
resolve: {
host: ['$stateParams', 'ManageHostsService', function($stateParams, ManageHostsService){
return ManageHostsService.get({id: $stateParams.host_id}).then(function(res){
return res.data.results[0];
});
}]
}
};
var ManageHostsAdd = {
name: 'inventoryManage.addHost',
route: '/host/add',
controller: addController,
templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'),
ncyBreadcrumb: {
label: "INVENTORY ADD HOST"
},
data: {
mode: 'add'
}
};
export {ManageHostsAdd, ManageHostsEdit};

View File

@ -1,53 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors',
function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&';
}, '');
},
get: function(params){
var url = GetBasePath('hosts') + '?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(res){
return res;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
post: function(params){
var url = GetBasePath('hosts');
Rest.setUrl(url);
return Rest.post(params)
.success(function(res){
return res;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
put: function(host){
var url = GetBasePath('hosts') + host.id;
Rest.setUrl(url);
return Rest.put(host)
.success(function(res){
return res;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
};
}];

View File

@ -1,18 +0,0 @@
#Inventory-groupManage--panel,
#Inventory-hostManage--panel {
.ui-dialog-buttonpane.ui-widget-content {
border: none;
text-align: right;
}
#host-panel-form,
#properties-tab {
.Form-header {
margin-top: -20px;
}
}
.Form-textArea {
width: 100%;
}
}

View File

@ -11,22 +11,17 @@
*/
export default
[ '$location', '$rootScope', '$filter', '$scope', '$compile',
'$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait',
'ProcessErrors', 'SelectPlay', 'SelectTask', 'GetElapsed',
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'InitiatePlaybookRun',
'LoadPlays', 'LoadTasks', 'HostsEdit',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
'EditSchedule', 'ParseTypeChange', 'JobDetailService',
[ '$location', '$rootScope', '$filter', '$scope', '$compile', '$stateParams', '$log', 'ClearScope',
'GetBasePath', 'Wait', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'GetElapsed', 'JobIsFinished',
'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'InitiatePlaybookRun', 'LoadPlays', 'LoadTasks',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels', 'EditSchedule',
'ParseTypeChange', 'JobDetailService',
function(
$location, $rootScope, $filter, $scope, $compile, $stateParams,
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
SelectPlay, SelectTask, GetElapsed,
JobIsFinished,
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
InitiatePlaybookRun, LoadPlays, LoadTasks,
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
$location, $rootScope, $filter, $scope, $compile, $stateParams, $log, ClearScope,
GetBasePath, Wait, ProcessErrors, SelectPlay, SelectTask, GetElapsed, JobIsFinished,
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, InitiatePlaybookRun, LoadPlays, LoadTasks,
ParseVariableString, GetChoices, fieldChoices, fieldLabels, EditSchedule,
ParseTypeChange, JobDetailService
) {
ClearScope();

View File

@ -9,41 +9,17 @@
export default
angular.module('GroupListDefinition', [])
.value('GroupList', {
.value('CopyMoveGroupList', {
name: 'copy_groups',
iterator: 'copy_group',
name: 'groups',
iterator: 'group',
selectTitle: 'Copy Groups',
editTitle: 'Groups',
index: false,
well: false,
well: false,
fields: {
name: {
key: true,
label: 'Name'
}
},
actions: { },
fieldActions: {
edit: {
label: 'Edit',
ngClick: "editGroup(group.id)",
icon: 'icon-edit',
"class": 'btn-xs',
awToolTip: 'Edit group',
dataPlacement: 'top'
},
"delete": {
label: 'Delete',
ngClick: "deleteGroup(group.id, group.name)",
icon: 'icon-trash',
"class": 'btn-xs',
awToolTip: 'Delete group',
dataPlacement: 'top'
label: 'Target Group Name'
}
}
});

View File

@ -96,7 +96,7 @@ export default
edit: {
label: 'Edit',
ngClick: 'editInventory(inventory.id)', //'editInventoryProperties(inventory.id)',
ngClick: 'editInventory(inventory.id)',
awToolTip: 'Edit inventory',
dataPlacement: 'top'
},

View File

@ -21,18 +21,47 @@ export default
multiSelect: true,
fields: {
sync_status: {
label: '',
nosort: true,
searchable: false,
mode: 'all',
iconOnly: true,
ngClick: 'viewUpdateStatus(group.id)',
awToolTip: "{{ group.status_tooltip }}",
dataTipWatch: "group.status_tooltip",
icon: "{{ 'fa icon-cloud-' + group.status_class }}",
ngClass: "group.status_class",
dataPlacement: "top",
columnClass: 'status-column List-staticColumn--smallStatus'
},
failed_hosts: {
label: '',
nosort: true,
searchable: false,
mode: 'all',
iconOnly: true,
awToolTip: "{{ group.hosts_status_tip }}",
dataPlacement: "top",
ngClick: "showFailedHosts(group)",
icon: "{{ 'fa icon-job-' + group.hosts_status_class }}",
columnClass: 'status-column List-staticColumn--smallStatus'
},
name: {
label: 'Groups',
key: true,
ngClick: "groupSelect(group.id)",
columnClick: "groupSelect(group.id)",
columnClass: 'col-lg-3 col-md-3 col-sm-3 col-xs-3'
columnClass: 'col-lg-3 col-md-3 col-sm-3 col-xs-3',
class: 'InventoryManage-breakWord'
},
total_groups: {
nosort: true,
label: '',
type: 'badgeCount',
ngHide: 'group.total_groups == 0',
noLink: true,
awToolTip: "{{group.name}} contains {{group.total_groups}} {{group.total_groups === 1 ? 'child' : 'children'}}",
},
source: {
label: 'Source',
@ -100,8 +129,9 @@ export default
},
launch: {
mode: 'all',
ngShow: 'inventory.can_run_ad_hoc_commands',
ngClick: 'populateAdhocForm()',
// $scope.$parent is governed by InventoryManageController,
ngShow: '$parent.groupsSelected || $parent.hostsSelected',
ngClick: '$parent.setAdhocPattern()',
awToolTip: "Run a command on the selected inventory",
actionClass: 'btn List-buttonDefault',
buttonContent: 'RUN COMMANDS'
@ -116,7 +146,7 @@ export default
ngClick: "createGroup()",
awToolTip: "Create a new group",
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD'
buttonContent: '&#43; ADD GROUP'
}
},
@ -124,32 +154,16 @@ export default
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right',
sync_status: {
mode: 'all',
ngClick: "viewUpdateStatus(group.id)",
awToolTip: "{{ group.status_tooltip }}",
dataTipWatch: "group.status_tooltip",
iconClass: "{{ 'fa icon-cloud-' + group.status_class }}",
ngClass: "group.status_class",
dataPlacement: "top"
},
failed_hosts: {
mode: 'all',
awToolTip: "{{ group.hosts_status_tip }}",
dataPlacement: "top",
ngClick: "showHosts(group.id, group.group_id, group.show_failures)",
iconClass: "{{ 'fa icon-job-' + group.hosts_status_class }}"
},
group_update: {
//label: 'Sync',
mode: 'all',
ngClick: 'updateGroup(group.id)',
ngClick: 'updateGroup(group)',
awToolTip: "{{ group.launch_tooltip }}",
dataTipWatch: "group.launch_tooltip",
ngShow: "group.status !== 'running' && group.status " +
"!== 'pending' && group.status !== 'updating'",
ngClass: "group.launch_class",
dataPlacement: "top"
dataPlacement: "top",
},
cancel: {
//label: 'Cancel',
@ -159,11 +173,12 @@ export default
'class': 'red-txt',
ngShow: "group.status == 'running' || group.status == 'pending' " +
"|| group.status == 'updating'",
dataPlacement: "top"
dataPlacement: "top",
iconClass: "fa fa-minus-circle"
},
copy: {
mode: 'all',
ngClick: "copyGroup(group.id)",
ngClick: "copyMoveGroup(group.id)",
awToolTip: 'Copy or move group',
ngShow: "group.id > 0",
dataPlacement: "top"
@ -186,7 +201,7 @@ export default
"delete": {
//label: 'Delete',
mode: 'all',
ngClick: "deleteGroup(group.id)",
ngClick: "deleteGroup(group)",
awToolTip: 'Delete group',
dataPlacement: "top"
}

View File

@ -22,6 +22,23 @@ export default
multiSelect: true,
fields: {
active_failures: {
label: '',
iconOnly: true,
searchable: false,
nosort: true,
// do not remove this ng-click directive
// the list generator case to handle fields without ng-click
// cannot handle the aw-* directives
ngClick: 'noop()',
awPopOver: "{{ host.job_status_html }}",
dataTitle: "{{ host.job_status_title }}",
awToolTip: "{{ host.badgeToolTip }}",
dataPlacement: 'top',
icon: "{{ 'fa icon-job-' + host.active_failures }}",
id: 'active-failures-action',
columnClass: 'status-column List-staticColumn--smallStatus'
},
name: {
key: true,
label: 'Hosts',
@ -29,7 +46,8 @@ export default
ngClass: "{ 'host-disabled-label': !host.enabled }",
columnClass: 'col-lg-6 col-md-8 col-sm-8 col-xs-7',
dataHostId: "{{ host.id }}",
dataType: "host"
dataType: "host",
class: 'InventoryManage-breakWord'
},
enabled: {
label: 'Disabled?',
@ -50,20 +68,9 @@ export default
fieldActions: {
columnClass: 'col-lg-6 col-md-4 col-sm-4 col-xs-5 text-right',
label: false,
active_failures: {
awPopOver: "{{ host.job_status_html }}",
dataTitle: "{{ host.job_status_title }}",
awToolTip: "{{ host.badgeToolTip }}",
awTipPlacement: 'top',
dataPlacement: 'left',
iconClass: "{{ 'fa icon-job-' + host.active_failures }}",
id: 'active-failutes-action'
},
copy: {
mode: 'all',
ngClick: "copyHost(host.id)",
ngClick: "copyMoveHost(host.id)",
awToolTip: 'Copy or move host to another group',
dataPlacement: "top"
},
@ -85,15 +92,15 @@ export default
actions: {
system_tracking: {
label: 'System Tracking',
ngClick: 'systemTracking()', //'editInventoryProperties(inventory.id)',
buttonContent: 'System Tracking',
ngClick: 'systemTracking()',
awToolTip: "{{ systemTrackingTooltip }}",
dataTipWatch: "systemTrackingTooltip",
dataPlacement: 'top',
awFeature: 'system_tracking',
ngDisabled: 'systemTrackingDisabled',
actionClass: 'btn List-buttonDefault system-tracking',
ngShow: 'hostsSelected'
ngShow: 'hostsSelected',
ngDisabled: 'systemTrackingDisabled'
},
refresh: {
mode: 'all',
@ -108,7 +115,7 @@ export default
ngClick: "createHost()",
awToolTip: "Create a new host",
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD'
buttonContent: '&#43; ADD HOST'
}
}

View File

@ -10,6 +10,9 @@ export default
scope.isCurrentState = function(name){
return $state.current.name === name;
};
scope.includesCurrentState = function(name){
return $state.includes(name);
};
// set up the user tooltip
$rootScope.$on('current_user', function(user) {

View File

@ -52,19 +52,26 @@ export default
controller: 'schedulerEditController'
});
$stateExtender.addState({
name: 'inventoryManageSchedules',
route: '/inventory/:inventory_id/manage/:id/schedules',
templateUrl: templateUrl("scheduler/scheduler"),
controller: 'schedulerController'
name: 'inventoryManage.schedules',
route: '/schedules/:id',
views: {
'form@inventoryManage': {
templateUrl: templateUrl("scheduler/scheduler"),
controller: 'schedulerController'
}
},
ncyBreadcrumb: {
label: "{{name}} SCHEDULES"
},
});
$stateExtender.addState({
name: 'inventoryManageSchedules.add',
name: 'inventoryManage.schedules.add',
route: '/add',
templateUrl: templateUrl("scheduler/schedulerForm"),
controller: 'schedulerAddController'
});
$stateExtender.addState({
name: 'inventoryManageSchedules.edit',
name: 'inventoryManage.schedules.edit',
route: '/:schedule_id',
templateUrl: templateUrl("scheduler/schedulerForm"),
controller: 'schedulerEditController'

View File

@ -1,609 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name shared.function:inventoryTree
* @description
* InventoryTree.js
*
* Build data for the tree selector table used on inventory detail page.
*
*/
export default
angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'PromptDialog'])
.factory('SortNodes', [
function () {
return function (data) {
//Sort nodes by name
var i, j, names = [], newData = [];
for (i = 0; i < data.length; i++) {
names.push(data[i].name);
}
names.sort();
for (j = 0; j < names.length; j++) {
for (i = 0; i < data.length; i++) {
if (data[i].name === names[j]) {
newData.push(data[i]);
}
}
}
return newData;
};
}
])
.factory('BuildTree', ['$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Store',
function ($location, Rest, GetBasePath, ProcessErrors, SortNodes, Wait, GetSyncStatusMsg, GetHostsStatusMsg, Store) {
return function (params) {
var inventory_id = params.inventory_id,
scope = params.scope,
refresh = params.refresh,
emit = params.emit,
new_group_id = params.new_group_id,
groups = [],
id = 1,
local_child_store,
path = $location.path();
function buildAllHosts(tree_data) {
// Start our tree object with All Hosts
var children = [],
sorted = SortNodes(tree_data),
j, all_hosts;
for (j = 0; j < sorted.length; j++) {
children.push(sorted[j].id);
}
all_hosts = {
name: 'All Hosts',
id: 1,
group_id: null,
parent: 0,
description: '',
show: true,
ngicon: null,
has_children: false,
related: {},
selected_class: '',
show_failures: false,
isDraggable: false,
isDroppable: true,
children: children
};
groups.push(all_hosts);
}
function getExpandState(key) {
var result = true;
local_child_store.every(function(child) {
if (child.key === key) {
result = child.expand;
return false;
}
return true;
});
return result;
}
function getShowState(key) {
var result = null;
local_child_store.every(function(child) {
if (child.key === key) {
result = (child.show !== undefined) ? child.show : true;
return false;
}
return true;
});
return result;
}
function buildGroups(tree_data, parent, level) {
var children, stat, hosts_status, group,
sorted = SortNodes(tree_data),
expand, show;
sorted.forEach( function(row, i) {
id++;
stat = GetSyncStatusMsg({
status: sorted[i].summary_fields.inventory_source.status,
has_inventory_sources: sorted[i].has_inventory_sources,
source: ( (sorted[i].summary_fields.inventory_source) ? sorted[i].summary_fields.inventory_source.source : null )
}); // from helpers/Groups.js
hosts_status = GetHostsStatusMsg({
active_failures: sorted[i].hosts_with_active_failures,
total_hosts: sorted[i].total_hosts,
inventory_id: inventory_id,
group_id: sorted[i].id
}); // from helpers/Groups.js
children = [];
sorted[i].children.forEach( function(child, j) {
children.push(sorted[i].children[j].id);
});
expand = (sorted[i].children.length > 0) ? getExpandState(sorted[i].id) : false;
show = getShowState(sorted[i].id);
if (show === null) {
// this is a node we haven't seen before, so check the parent expand/collapse state
// If parent is not expanded, then child should be hidden.
show = true;
if (parent > 0) {
groups.every(function(g) {
if (g.id === parent) {
show = getExpandState(g.key);
return false;
}
return true;
});
}
}
group = {
name: sorted[i].name,
has_active_failures: sorted[i].has_active_failures,
total_hosts: sorted[i].total_hosts,
hosts_with_active_failures: sorted[i].hosts_with_active_failures,
total_groups: sorted[i].total_groups,
groups_with_active_failures: sorted[i].groups_with_active_failures,
parent: parent,
has_children: (sorted[i].children.length > 0) ? true : false,
has_inventory_sources: sorted[i].has_inventory_sources,
id: id,
source: sorted[i].summary_fields.inventory_source.source,
key: sorted[i].id,
group_id: sorted[i].id,
event_level: level,
children: children,
show: show,
related: sorted[i].related,
status: sorted[i].summary_fields.inventory_source.status,
status_class: stat['class'],
status_tooltip: stat.tooltip,
launch_tooltip: stat.launch_tip,
launch_class: stat.launch_class,
hosts_status_tip: hosts_status.tooltip,
show_failures: hosts_status.failures,
hosts_status_class: hosts_status['class'],
inventory_id: inventory_id,
selected_class: '',
isDraggable: true,
isDroppable: true
};
if (sorted[i].children.length > 0) {
if (expand) {
group.ngicon = 'fa fa-minus-square-o node-toggle';
}
else {
group.ngicon = 'fa fa-plus-square-o node-toggle';
}
}
else {
group.ngicon = 'fa fa-square-o node-no-toggle';
}
if (new_group_id && group.group_id === new_group_id) {
scope.selected_tree_id = id;
scope.selected_group_id = group.group_id;
}
groups.push(group);
if (sorted[i].children.length > 0) {
buildGroups(sorted[i].children, id, level + 1);
}
});
}
// Build the HTML for our tree
if (scope.buildAllGroupsRemove) {
scope.buildAllGroupsRemove();
}
scope.buildAllGroupsRemove = scope.$on('buildAllGroups', function (e, inventory_name, inventory_tree) {
Rest.setUrl(inventory_tree);
Rest.get()
.success(function (data) {
buildAllHosts(data);
buildGroups(data, 0, 0);
scope.autoShowGroupHelp = (data.length === 0) ? true : false;
if (refresh) {
scope.groups = groups;
scope.$emit('GroupTreeRefreshed', inventory_name, groups, emit);
} else {
scope.$emit('GroupTreeLoaded', inventory_name, groups, emit);
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory tree for: ' + inventory_id + '. GET returned: ' + status
});
});
});
function loadTreeData() {
// Load the inventory root node
Wait('start');
Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
Rest.get()
.success(function (data) {
scope.$emit('buildAllGroups', data.name, data.related.tree, data.related.groups);
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status
});
});
}
local_child_store = Store(path + '_children');
if (!local_child_store) {
local_child_store = [];
}
loadTreeData();
};
}
])
// Update a group with a set of properties
.factory('UpdateGroup', ['ApplyEllipsis', 'GetSyncStatusMsg', 'Empty',
function (ApplyEllipsis, GetSyncStatusMsg, Empty) {
return function (params) {
var scope = params.scope,
group_id = params.group_id,
properties = params.properties,
i, p, grp, old_name, stat;
for (i = 0; i < scope.groups.length; i++) {
if (scope.groups[i].id === group_id) {
grp = scope.groups[i];
for (p in properties) {
if (p === 'name') {
old_name = scope.groups[i].name;
}
if (p === 'source') {
if (properties[p] !== scope.groups[i][p]) {
// User changed source
if (!Empty(properties[p]) && (scope.groups[i].status === 'none' || Empty(scope.groups[i].status))) {
// We have a source but no status, seed the status with 'never' to enable sync button
scope.groups[i].status = 'never updated';
} else if (!properties[p]) {
// User removed source
scope.groups[i].status = 'none';
}
// Update date sync status links/icons
stat = GetSyncStatusMsg({
status: scope.groups[i].status,
has_inventory_sources: properties.has_inventory_sources,
source: properties.source
});
scope.groups[i].status_class = stat['class'];
scope.groups[i].status_tooltip = stat.tooltip;
scope.groups[i].launch_tooltip = stat.launch_tip;
scope.groups[i].launch_class = stat.launch_class;
}
}
scope.groups[i][p] = properties[p];
}
}
/*if (scope.groups[i].id === scope.selected_tree_id) {
//Make sure potential group name change gets reflected throughout the page
scope.selected_group_name = scope.groups[i].name;
scope.search_place_holder = 'Search ' + scope.groups[i].name;
scope.hostSearchPlaceholder = 'Search ' + scope.groups[i].name;
}*/
}
// Update any titles attributes created by ApplyEllipsis
if (old_name) {
setTimeout(function () {
$('#groups_table .group-name a[title="' + old_name + '"]').attr('title', properties.name);
ApplyEllipsis('#groups_table .group-name a');
}, 2500);
}
};
}
])
// Set node name and description after an update to Group properties.
.factory('SetNodeName', [
function () {
return function (params) {
var name = params.name,
descr = params.description,
group_id = (params.group_id !== undefined) ? params.group_id : null,
inventory_id = (params.inventory_id !== undefined) ? params.inventory_id : null;
if (group_id !== null) {
$('#inventory-tree').find('li [data-group-id="' + group_id + '"]').each(function () {
$(this).attr('data-name', name);
$(this).attr('data-description', descr);
$(this).find('.activate').first().text(name);
});
}
if (inventory_id !== null) {
$('#inventory-root-node').attr('data-name', name).attr('data-description', descr).find('.activate').first().text(name);
}
};
}
])
// Copy or Move a group on the tree after drag-n-drop
.factory('CopyMoveGroup', ['$compile', 'Alert', 'ProcessErrors', 'Find', 'Wait', 'Rest', 'Empty', 'GetBasePath',
function ($compile, Alert, ProcessErrors, Find, Wait, Rest, Empty, GetBasePath) {
return function (params) {
var scope = params.scope,
target = Find({ list: scope.groups, key: 'id', val: params.target_tree_id }),
inbound = Find({ list: scope.groups, key: 'id', val: params.inbound_tree_id }),
e, html = '';
// Build the html for our prompt dialog
html += "<div id=\"copy-prompt-modal\" class=\"modal fade\">\n";
html += "<div class=\"modal-dialog\">\n";
html += "<div class=\"modal-content\">\n";
html += "<div class=\"modal-header\">\n";
html += "<button type=\"button\" class=\"close\" data-target=\"#copy-prompt-modal\" " +
"data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n";
if (target.id === 1 || inbound.parent === 0) {
// We're moving the group to the top level, or we're moving a top level group down
html += "<h3>Move Group</h3>\n";
} else {
html += "<h3>Copy or Move?</h3>\n";
}
html += "</div>\n";
html += "<div class=\"modal-body\">\n";
if (target.id === 1) {
html += "<div class=\"alert alert-info\">Are you sure you want to move group " + inbound.name + " to the top level?</div>";
} else if (inbound.parent === 0) {
html += "<div class=\"alert alert-info\">Are you sure you want to move group " + inbound.name + " from the top level and make it a child of " +
target.name + "?</div>";
} else {
html += "<div class=\"text-center\">\n";
html += "<p>Would you like to copy or move group <em>" + inbound.name + "</em> to group <em>" + target.name + "</em>?</p>\n";
html += "<div style=\"margin-top: 30px;\">\n";
html += "<a href=\"\" ng-click=\"moveGroup()\" class=\"btn btn-primary\" style=\"margin-right: 15px;\"><i class=\"fa fa-cut\"></i> Move</a>\n";
html += "<a href=\"\" ng-click=\"copyGroup()\" class=\"btn btn-primary\"><i class=\"fa fa-copy\"></i> Copy</a>\n";
html += "</div>\n";
html += "</div>\n";
}
html += "</div>\n";
html += "<div class=\"modal-footer\">\n";
html += "<a href=\"#\" data-target=\"#prompt-modal\" data-dismiss=\"modal\" class=\"btn btn-default\">Cancel</a>\n";
if (target.id === 1 || inbound.parent === 0) {
// We're moving the group to the top level, or we're moving a top level group down
html += "<a href=\"\" data-target=\"#prompt-modal\" ng-click=\"moveGroup()\" class=\"btn btn-primary\">Yes</a>\n";
}
html += "</div>\n";
html += "</div><!-- modal-content -->\n";
html += "</div><!-- modal-dialog -->\n";
html += "</div><!-- modal -->\n";
// Inject our custom dialog
e= angular.element(document.getElementById('inventory-modal-container'));
e.empty().append(html);
$compile(e)(scope);
// Display it
$('#copy-prompt-modal').modal({
backdrop: 'static',
keyboard: true,
show: true
});
// Respond to move
scope.moveGroup = function () {
var url, group, parent;
$('#copy-prompt-modal').modal('hide');
Wait('start');
// disassociate the group from the original parent
if (scope.removeGroupRemove) {
scope.removeGroupRemove();
}
scope.removeGroupRemove = scope.$on('removeGroup', function () {
if (inbound.parent > 0) {
// Only remove a group from a parent when the parent is a group and not the inventory root
parent = Find({ list: scope.groups, key: 'id', val: inbound.parent });
url = GetBasePath('base') + 'groups/' + parent.group_id + '/children/';
Rest.setUrl(url);
Rest.post({ id: inbound.group_id, disassociate: 1 })
.success(function () {
//Triggers refresh of group list in inventory controller
scope.$emit('GroupDeleteCompleted');
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to remove ' + inbound.name +
' from ' + parent.name + '. POST returned status: ' + status
});
});
} else {
//Triggers refresh of group list in inventory controller
scope.$emit('GroupDeleteCompleted');
}
});
// add the new group to the target parent
url = (!Empty(target.group_id)) ?
GetBasePath('base') + 'groups/' + target.group_id + '/children/' :
GetBasePath('inventory') + scope.inventory_id + '/groups/';
group = {
id: inbound.group_id,
name: inbound.name,
description: inbound.description,
inventory: scope.inventory_id
};
Rest.setUrl(url);
Rest.post(group)
.success(function () {
scope.$emit('removeGroup');
})
.error(function (data, status) {
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + inbound.name + ' to ' + target_name + '. POST returned status: ' + status });
});
};
scope.copyGroup = function () {
$('#copy-prompt-modal').modal('hide');
Wait('start');
// add the new group to the target parent
var url = (!Empty(target.group_id)) ?
GetBasePath('base') + 'groups/' + target.group_id + '/children/' :
GetBasePath('inventory') + scope.inventory_id + '/groups/',
group = {
id: inbound.group_id,
name: inbound.name,
description: inbound.description,
inventory: scope.inventory_id
};
Rest.setUrl(url);
Rest.post(group)
.success(function () {
//Triggers refresh of group list in inventory controller
scope.$emit('GroupDeleteCompleted');
})
.error(function (data, status) {
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to add ' + inbound.name + ' to ' + target_name + '. POST returned status: ' + status
});
});
};
};
}
])
// Copy a host after drag-n-drop
.factory('CopyMoveHost', ['$compile', 'Alert', 'ProcessErrors', 'Find', 'Wait', 'Rest', 'Empty', 'GetBasePath',
function ($compile, Alert, ProcessErrors, Find, Wait, Rest, Empty, GetBasePath) {
return function (params) {
var scope = params.scope,
target = Find({ list: scope.groups, key: 'id', val: params.target_tree_id }),
host = Find({ list: scope.hosts, key: 'id', val: params.host_id }),
found = false, e, i, html = '';
if (host.summary_fields.all_groups) {
for (i = 0; i < host.summary_fields.all_groups.length; i++) {
if (host.summary_fields.all_groups[i].id === target.group_id) {
found = true;
break;
}
}
}
if (found) {
html += "<div id=\"copy-alert-modal\" class=\"modal fade\">\n";
html += "<div class=\"modal-dialog\">\n";
html += "<div class=\"modal-content\">\n";
html += "<div class=\"modal-header\">\n";
html += "<button type=\"button\" class=\"close\" ng-hide=\"disableButtons\" data-target=\"#copy-alert-modal\"\n";
html += "data-dismiss=\"modal\" class=\"modal\" aria-hidden=\"true\">&times;</button>\n";
html += "<h3>Already in Group</h3>\n";
html += "</div>\n";
html += "<div class=\"modal-body\">\n";
html += "<div class=\"alert alert-info\"><p>Host " + host.name + " is already in group " + target.name + ".</p></div>\n";
html += "</div>\n";
html += "<div class=\"modal-footer\">\n";
html += "<a href=\"#\" data-target=\"#copy-alert-modal\" data-dismiss=\"modal\" class=\"btn btn-primary\">OK</a>\n";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
html += "</div>\n";
// Inject our custom dialog
e = angular.element(document.getElementById('inventory-modal-container'));
e.empty().append(html);
$compile(e)(scope);
// Display it
$('#copy-alert-modal').modal({
backdrop: 'static',
keyboard: true,
show: true
});
} else {
// Build the html for our prompt dialog
html = '';
html += "<div id=\"copy-prompt-modal\" class=\"modal fade\">\n";
html += "<div class=\"modal-dialog\">\n";
html += "<div class=\"modal-content\">\n";
html += "<div class=\"modal-header\">\n";
html += "<button type=\"button\" class=\"close\" data-target=\"#copy-prompt-modal\" " +
"data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n";
html += "<h3>Copy Host</h3>\n";
html += "</div>\n";
html += "<div class=\"modal-body\">\n";
html += "<div class=\"alert alert-info\">Are you sure you want to copy host " + host.name + ' to group ' + target.name + '?</div>';
html += "</div>\n";
html += "<div class=\"modal-footer\">\n";
html += "<a href=\"#\" data-target=\"#prompt-modal\" data-dismiss=\"modal\" class=\"btn btn-default\">No</a>\n";
html += "<a href=\"\" data-target=\"#prompt-modal\" ng-click=\"copyHost()\" class=\"btn btn-primary\">Yes</a>\n";
html += "</div>\n";
html += "</div><!-- modal-content -->\n";
html += "</div><!-- modal-dialog -->\n";
html += "</div><!-- modal -->\n";
// Inject our custom dialog
e = angular.element(document.getElementById('inventory-modal-container'));
e.empty().append(html);
$compile(e)(scope);
// Display it
$('#copy-prompt-modal').modal({
backdrop: 'static',
keyboard: true,
show: true
});
scope.copyHost = function () {
$('#copy-prompt-modal').modal('hide');
Wait('start');
Rest.setUrl(GetBasePath('groups') + target.group_id + '/hosts/');
Rest.post(host)
.success(function () {
// Signal the controller to refresh the hosts view
scope.$emit('GroupTreeRefreshed');
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to add ' + host.name + ' to ' +
target.name + '. POST returned status: ' + status });
});
};
}
};
}
]);

View File

@ -277,10 +277,14 @@ angular.module('GeneratorHelpers', [systemStatus.name])
html = "<td class=\"" + fld + "-column";
html += (field.columnClass) ? " " + field.columnClass : "";
html += "\">\n";
html += "<a ng-href=\"" + field.ngHref + "\" aw-tool-tip=\"" + field.awToolTip + "\"";
if (!field.noLink){
html += "<a ng-href=\"" + field.ngHref + "\" aw-tool-tip=\"" + field.awToolTip + "\"";
html += (field.dataPlacement) ? " data-placement=\"" + field.dataPlacement + "\"" : "";
html += ">";
}
html += "<span class=\"badge\"";
html += " aw-tool-tip=\"" + field.awToolTip + "\"";
html += (field.dataPlacement) ? " data-placement=\"" + field.dataPlacement + "\"" : "";
html += ">";
html += "<span class=\"badge";
html += (field['class']) ? " " + field['class'] : "";
html += (field.ngHide) ? "\" ng-hide=\"" + field.ngHide : "";
html += "\">";

View File

@ -439,6 +439,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate
innerTable += "ng-class-odd=\"'List-tableRow--oddRow'\" ";
innerTable += "ng-class-even=\"'List-tableRow--evenRow'\" ";
innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name;
innerTable += (list.trackBy) ? " track by " + list.trackBy : " track by $index";
innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : "";
innerTable += (list.filterBy) ? " | filter: " + list.filterBy : "";
innerTable += "\">\n";
@ -459,7 +460,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate
"ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>";
}
else { // its assumed that options.input_type = checkbox
innerTable += "<td class=\"List-tableCell\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_" + list.iterator + "_{{" +
innerTable += "<td class=\"List-tableCell select-column List-staticColumn--smallStatus\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>";
}
@ -619,7 +620,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate
html += buildSelectAll().prop('outerHTML');
}
else if (options.mode === 'lookup') {
html += "<th class=\"List-tableHeader List-staticColumn--smallStatus col-lg-1 col-md-1 col-sm-2 col-xs-2\"></th>";
html += "<th class=\"List-tableHeader select-column List-staticColumn--smallStatus\"></th>";
}
for (fld in list.fields) {
if ((list.fields[fld].searchOnly === undefined || list.fields[fld].searchOnly === false) &&

View File

@ -1,6 +1,6 @@
import '../support/node';
import adhocModule from 'adhoc/main';
import adhocModule from 'inventories/manage/adhoc/main';
import RestStub from '../support/rest-stub';
describe("adhoc.controller", function() {

View File

@ -40,7 +40,7 @@
<include-svg href="{{ STATIC_URL }}assets/icons.svg"></include-svg>
<main-menu></main-menu>
<bread-crumb></bread-crumb>
<bread-crumb ng-if="!includesCurrentState('inventoryManage')"></bread-crumb><div ui-view="groupBreadcrumbs" ng-if="includesCurrentState('inventoryManage')"></div>
<toast></toast>
<div class="container-fluid" id="content-container">