diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js
index b7e02d75eb..df022e8973 100644
--- a/awx/ui/static/js/controllers/Inventories.js
+++ b/awx/ui/static/js/controllers/Inventories.js
@@ -303,7 +303,7 @@ InventoriesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$lo
function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait,
GetSyncStatusMsg, InjectHosts, HostsReload, GroupsAdd, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty,
Rest, ProcessErrors, InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find,
- HostsCreate, EditInventoryProperties, HostsEdit, HostsDelete, ToggleHostEnabled, CopyMoveGroup)
+ HostsCreate, EditInventoryProperties, HostsEdit, HostsDelete, ToggleHostEnabled, CopyMoveGroup, CopyMoveHost)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@@ -377,6 +377,14 @@ function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateLis
CopyMoveGroup({ scope: $scope, target_tree_id: target_tree_id, inbound_tree_id: inbound_tree_id });
});
+ // Respond to a host drag-n-drop
+ if ($scope.removeCopMoveHost) {
+ $scope.removeCopyMoveHost();
+ }
+ $scope.removeCopyMoveHost = $scope.$on('CopyMoveHost', function(e, target_tree_id, host_id) {
+ CopyMoveHost({ scope: $scope, target_tree_id: target_tree_id, host_id: host_id });
+ });
+
$scope.showHosts = function(tree_id, group_id, show_failures) {
// Clicked on group
if (tree_id !== null) {
@@ -454,7 +462,6 @@ function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateLis
// Expand/collapse nodes
if (tree_id !== $scope.selected_tree_id) {
$scope.showHosts(tree_id, Find({ list: $scope.groups, key: 'id', val: tree_id }).group_id, false);
- console.log('set group_id to: ' + $scope.selected_group_id);
}
ToggleChildren({ scope: $scope, list: list, id: tree_id });
}
@@ -505,6 +512,6 @@ InventoriesEdit.$inject = [ '$scope', '$location', '$routeParams', '$compile', '
'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsAdd', 'GroupsEdit', 'GroupsDelete',
'Breadcrumbs', 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren',
'ViewUpdateStatus', 'GroupsCancelUpdate', 'Find', 'HostsCreate', 'EditInventoryProperties', 'HostsEdit',
- 'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup'
+ 'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost'
];
diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js
index 8bcc96be3b..7f54d92ba3 100644
--- a/awx/ui/static/js/lists/InventoryGroups.js
+++ b/awx/ui/static/js/lists/InventoryGroups.js
@@ -35,43 +35,8 @@ angular.module('InventoryGroupsDefinition', [])
dataContainment: "#groups_table",
dataTreeId: "\{\{ group.id \}\}",
dataGroupId: "\{\{ group.group_id \}\}",
- dataAccept: "dropAccept" //function determining when draggable is accepted by droppable
+ dataType: "group"
}
- /*source: {
- label: 'Source',
- searchType: 'select',
- searchOptions: [
- { name: "ec2", value: "ec2" },
- { name: "none", value: "" },
- { name: "rax", value: "rax" }],
- sourceModel: 'inventory_source',
- sourceField: 'source',
- searchOnly: true
- },
- has_external_source: {
- label: 'Has external source?',
- searchType: 'in',
- searchValue: 'ec2,rax',
- searchOnly: true,
- sourceModel: 'inventory_source',
- sourceField: 'source'
- },
- has_active_failures: {
- label: 'Has failed hosts?',
- searchSingleValue: true,
- searchType: 'boolean',
- searchValue: 'true',
- searchOnly: true
- },
- last_update_failed: {
- label: 'Update failed?',
- searchType: 'select',
- searchSingleValue: true,
- searchValue: 'failed',
- searchOnly: true,
- sourceModel: 'inventory_source',
- sourceField: 'status'
- }*/
},
actions: {
diff --git a/awx/ui/static/js/lists/InventoryHosts.js b/awx/ui/static/js/lists/InventoryHosts.js
index c879acc24e..e582348f08 100644
--- a/awx/ui/static/js/lists/InventoryHosts.js
+++ b/awx/ui/static/js/lists/InventoryHosts.js
@@ -26,7 +26,10 @@ angular.module('InventoryHostsDefinition', [])
label: 'Hosts',
ngClick: "editHost(\{\{ host.id \}\})",
searchPlaceholder: "search_place_holder",
- columnClass: 'col-lg-9'
+ columnClass: 'col-lg-9',
+ dataHostId: "\{\{ host.id \}\}",
+ dataType: "host",
+ awDraggable: "true"
},
/*groups: {
label: 'Groups',
diff --git a/awx/ui/static/lib/ansible/InventoryTree.js b/awx/ui/static/lib/ansible/InventoryTree.js
index becda0f682..48b03df06f 100644
--- a/awx/ui/static/lib/ansible/InventoryTree.js
+++ b/awx/ui/static/lib/ansible/InventoryTree.js
@@ -375,10 +375,116 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
Wait('stop');
ProcessErrors(scope, data, status, null,
- { hdr: 'Error!', msg: 'Failed to add ' + node.attr('name') + ' to ' +
+ { 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;
+ var target = Find({ list: scope.groups, key: 'id', val: params.target_tree_id });
+ var host = Find({ list: scope.hosts, key: 'id', val: params.host_id });
+
+ var found = false;
+
+ if (host.summary_fields.all_groups) {
+ for (var 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) {
+ var html = '';
+ html += "
\n";
+ html += "
\n";
+ html += "
\n";
+ html += "\n";
+ html += "
\n";
+ html += "
Host " + host.name + " is already in group " + target.name + ".
\n";
+ html += "
\n";
+ html += "\n";
+ html += "
\n";
+ html += "
\n";
+ html += "
\n";
+
+ // Inject our custom dialog
+ var 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
+ var html = '';
+ html += "\n";
+ html += "
\n";
+ html += "
\n";
+ html += "\n";
+ html += "
\n";
+ html += "
Are you sure you want to copy host " + host.name + ' to group ' + target.name + '?
';
+ html += "
\n";
+ html += "\n";
+ html += "
\n";
+ html += "
\n";
+ html += "
\n";
+
+ // Inject our custom dialog
+ var 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(data, status, headers, config) {
+ // Signal the controller to refresh the hosts view
+ scope.$emit('GroupTreeRefreshed');
+ })
+ .error(function(data, status, headers, config) {
+ Wait('stop');
+ ProcessErrors(scope, data, status, null,
+ { hdr: 'Error!', msg: 'Failed to add ' + host.name + ' to ' +
+ target.name + '. POST returned status: ' + status });
+ });
+ }
+ }
}
}]);
diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js
index b5306c67b4..3f506fa9dd 100644
--- a/awx/ui/static/lib/ansible/directives.js
+++ b/awx/ui/static/lib/ansible/directives.js
@@ -554,30 +554,41 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
// the following is inventory specific accept checking and
// drop processing.
accept: function(draggable) {
- if ($(this).attr('data-group-id') == draggable.attr('data-group-id')) {
- // No dropping a node onto itself (or a copy)
- return false;
- }
- else {
- // No dropping a node into a group that already has the node
- var node = Find({ list: scope.groups, key: 'id', val: parseInt($(this).attr('data-tree-id')) });
- if (node) {
- var group = parseInt(draggable.attr('data-group-id'));
- var found = false;
- // For whatever reason indexOf() would not work...
- for (var i=0; i < node.children.length; i++) {
- if (node.children[i] == group) {
- found = true;
- break;
- }
-
- }
- return (found) ? false : true;
- }
- else {
- // this shouldn't be possible
+ if (draggable.attr('data-type') == 'group') {
+ // Dropped a group
+ if ($(this).attr('data-group-id') == draggable.attr('data-group-id')) {
+ // No dropping a node onto itself (or a copy)
return false;
}
+ else {
+ // No dropping a node into a group that already has the node
+ var node = Find({ list: scope.groups, key: 'id', val: parseInt($(this).attr('data-tree-id')) });
+ if (node) {
+ var group = parseInt(draggable.attr('data-group-id'));
+ var found = false;
+ // For whatever reason indexOf() would not work...
+ for (var i=0; i < node.children.length; i++) {
+ if (node.children[i] == group) {
+ found = true;
+ break;
+ }
+
+ }
+ return (found) ? false : true;
+ }
+ else {
+ // Node not found. This shouldn't be possible
+ return false;
+ }
+ }
+ }
+ else if (draggable.attr('data-type') == 'host') {
+ // Dropped a host
+ var node = Find({ list: scope.groups, key: 'id', val: parseInt($(this).attr('data-tree-id')) });
+ return (node.id > 1) ? true : false;
+ }
+ else {
+ return false;
}
},
over: function(e, ui) {
@@ -589,7 +600,12 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
drop: function(e, ui) {
// Drag-n-drop succeeded. Trigger a response from the inventory.edit controller
$(this).removeClass('droppable-hover');
- scope.$emit('CopyMoveGroup', ui.draggable.attr('data-tree-id'), $(this).attr('data-tree-id'));
+ if (ui.draggable.attr('data-type') == 'group') {
+ scope.$emit('CopyMoveGroup', ui.draggable.attr('data-tree-id'), $(this).attr('data-tree-id'));
+ }
+ else if (ui.draggable.attr('data-type') == 'host') {
+ scope.$emit('CopyMoveHost', $(this).attr('data-tree-id'), ui.draggable.attr('data-host-id'));
+ }
},
tolerance: 'touch'
});
diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js
index 2b0311ebd6..83f4b8bfa2 100644
--- a/awx/ui/static/lib/ansible/generator-helpers.js
+++ b/awx/ui/static/lib/ansible/generator-helpers.js
@@ -517,6 +517,8 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
html += (field.dataContainment) ? Attr(field, 'dataContainment') : '';
html += (field.dataTreeId) ? Attr(field, 'dataTreeId') : '';
html += (field.dataGroupId) ? Attr(field, 'dataGroupId') : '';
+ html += (field.dataHostId) ? Attr(field, 'dataHostId') : '';
+ html += (field.dataType) ? Attr(field, 'dataType') : '';
}
if (field.awToolTip) {
html += Attr(field, 'awToolTip');
diff --git a/awx/ui/static/lib/ansible/prompt-dialog.js b/awx/ui/static/lib/ansible/prompt-dialog.js
index 85c69bf48e..4cc6e306ec 100644
--- a/awx/ui/static/lib/ansible/prompt-dialog.js
+++ b/awx/ui/static/lib/ansible/prompt-dialog.js
@@ -15,19 +15,19 @@
*/
angular.module('PromptDialog', ['Utilities'])
- .factory('Prompt', ['Alert', function(Alert) {
+ .factory('Prompt', ['$rootScope', '$compile', 'Alert', function($rootScope, $compile, Alert) {
return function(params) {
var dialog = angular.element(document.getElementById('prompt-modal'));
- var scope = dialog.scope();
+ var scope = dialog.scope();
scope.promptHeader = params.hdr;
scope.promptBody = params.body;
scope.promptAction = params.action;
+
var cls = (params['class'] == null || params['class'] == undefined) ? 'btn-danger' : params['class'];
- $('#prompt_action_btn').addClass(cls); // Use jquery because django template engine conflicts with Angular's
- // use of {{...}}
+ $('#prompt_action_btn').removeClass(cls).addClass(cls);
$(dialog).modal({
backdrop: 'static',
diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html
index 3952189a31..45755d247c 100644
--- a/awx/ui/templates/ui/index.html
+++ b/awx/ui/templates/ui/index.html
@@ -263,7 +263,7 @@