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 += "

Already in Group

\n"; + html += "
\n"; + html += "
\n"; + html += "

Host " + host.name + " is already in group " + target.name + ".

\n"; + html += "
\n"; + html += "
\n"; + html += "OK\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 += "

Copy Group

\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 += "No\n"; + html += "Yes\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 @@