mirror of
https://github.com/ansible/awx.git
synced 2026-03-06 11:11:07 -03:30
Inventory refactor: dragging a host to a group will copy it.
This commit is contained in:
@@ -303,7 +303,7 @@ InventoriesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$lo
|
|||||||
function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait,
|
function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait,
|
||||||
GetSyncStatusMsg, InjectHosts, HostsReload, GroupsAdd, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty,
|
GetSyncStatusMsg, InjectHosts, HostsReload, GroupsAdd, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty,
|
||||||
Rest, ProcessErrors, InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find,
|
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
|
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
|
||||||
//scope.
|
//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 });
|
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) {
|
$scope.showHosts = function(tree_id, group_id, show_failures) {
|
||||||
// Clicked on group
|
// Clicked on group
|
||||||
if (tree_id !== null) {
|
if (tree_id !== null) {
|
||||||
@@ -454,7 +462,6 @@ function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateLis
|
|||||||
// Expand/collapse nodes
|
// Expand/collapse nodes
|
||||||
if (tree_id !== $scope.selected_tree_id) {
|
if (tree_id !== $scope.selected_tree_id) {
|
||||||
$scope.showHosts(tree_id, Find({ list: $scope.groups, key: 'id', val: tree_id }).group_id, false);
|
$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 });
|
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',
|
'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsAdd', 'GroupsEdit', 'GroupsDelete',
|
||||||
'Breadcrumbs', 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren',
|
'Breadcrumbs', 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren',
|
||||||
'ViewUpdateStatus', 'GroupsCancelUpdate', 'Find', 'HostsCreate', 'EditInventoryProperties', 'HostsEdit',
|
'ViewUpdateStatus', 'GroupsCancelUpdate', 'Find', 'HostsCreate', 'EditInventoryProperties', 'HostsEdit',
|
||||||
'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup'
|
'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -35,43 +35,8 @@ angular.module('InventoryGroupsDefinition', [])
|
|||||||
dataContainment: "#groups_table",
|
dataContainment: "#groups_table",
|
||||||
dataTreeId: "\{\{ group.id \}\}",
|
dataTreeId: "\{\{ group.id \}\}",
|
||||||
dataGroupId: "\{\{ group.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: {
|
actions: {
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ angular.module('InventoryHostsDefinition', [])
|
|||||||
label: 'Hosts',
|
label: 'Hosts',
|
||||||
ngClick: "editHost(\{\{ host.id \}\})",
|
ngClick: "editHost(\{\{ host.id \}\})",
|
||||||
searchPlaceholder: "search_place_holder",
|
searchPlaceholder: "search_place_holder",
|
||||||
columnClass: 'col-lg-9'
|
columnClass: 'col-lg-9',
|
||||||
|
dataHostId: "\{\{ host.id \}\}",
|
||||||
|
dataType: "host",
|
||||||
|
awDraggable: "true"
|
||||||
},
|
},
|
||||||
/*groups: {
|
/*groups: {
|
||||||
label: 'Groups',
|
label: 'Groups',
|
||||||
|
|||||||
@@ -375,10 +375,116 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P
|
|||||||
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
|
var target_name = (Empty(target.group_id)) ? 'inventory' : target.name;
|
||||||
Wait('stop');
|
Wait('stop');
|
||||||
ProcessErrors(scope, data, status, null,
|
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 });
|
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 += "<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\">×</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
|
||||||
|
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 += "<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\">×</button>\n";
|
||||||
|
html += "<h3>Copy Group</h3>\n";
|
||||||
|
html += "</div>\n";
|
||||||
|
html += "<div class=\"modal-body\">\n";
|
||||||
|
html += "<p>Are you sure you want to copy host " + host.name + ' to group ' + target.name + '?</p>';
|
||||||
|
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
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@@ -554,30 +554,41 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
|
|||||||
// the following is inventory specific accept checking and
|
// the following is inventory specific accept checking and
|
||||||
// drop processing.
|
// drop processing.
|
||||||
accept: function(draggable) {
|
accept: function(draggable) {
|
||||||
if ($(this).attr('data-group-id') == draggable.attr('data-group-id')) {
|
if (draggable.attr('data-type') == 'group') {
|
||||||
// No dropping a node onto itself (or a copy)
|
// Dropped a group
|
||||||
return false;
|
if ($(this).attr('data-group-id') == draggable.attr('data-group-id')) {
|
||||||
}
|
// No dropping a node onto itself (or a copy)
|
||||||
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
|
|
||||||
return false;
|
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) {
|
over: function(e, ui) {
|
||||||
@@ -589,7 +600,12 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService'])
|
|||||||
drop: function(e, ui) {
|
drop: function(e, ui) {
|
||||||
// Drag-n-drop succeeded. Trigger a response from the inventory.edit controller
|
// Drag-n-drop succeeded. Trigger a response from the inventory.edit controller
|
||||||
$(this).removeClass('droppable-hover');
|
$(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'
|
tolerance: 'touch'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -517,6 +517,8 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
|
|||||||
html += (field.dataContainment) ? Attr(field, 'dataContainment') : '';
|
html += (field.dataContainment) ? Attr(field, 'dataContainment') : '';
|
||||||
html += (field.dataTreeId) ? Attr(field, 'dataTreeId') : '';
|
html += (field.dataTreeId) ? Attr(field, 'dataTreeId') : '';
|
||||||
html += (field.dataGroupId) ? Attr(field, 'dataGroupId') : '';
|
html += (field.dataGroupId) ? Attr(field, 'dataGroupId') : '';
|
||||||
|
html += (field.dataHostId) ? Attr(field, 'dataHostId') : '';
|
||||||
|
html += (field.dataType) ? Attr(field, 'dataType') : '';
|
||||||
}
|
}
|
||||||
if (field.awToolTip) {
|
if (field.awToolTip) {
|
||||||
html += Attr(field, 'awToolTip');
|
html += Attr(field, 'awToolTip');
|
||||||
|
|||||||
@@ -15,19 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
angular.module('PromptDialog', ['Utilities'])
|
angular.module('PromptDialog', ['Utilities'])
|
||||||
.factory('Prompt', ['Alert', function(Alert) {
|
.factory('Prompt', ['$rootScope', '$compile', 'Alert', function($rootScope, $compile, Alert) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
|
|
||||||
var dialog = angular.element(document.getElementById('prompt-modal'));
|
var dialog = angular.element(document.getElementById('prompt-modal'));
|
||||||
var scope = dialog.scope();
|
var scope = dialog.scope();
|
||||||
|
|
||||||
scope.promptHeader = params.hdr;
|
scope.promptHeader = params.hdr;
|
||||||
scope.promptBody = params.body;
|
scope.promptBody = params.body;
|
||||||
scope.promptAction = params.action;
|
scope.promptAction = params.action;
|
||||||
|
|
||||||
var cls = (params['class'] == null || params['class'] == undefined) ? 'btn-danger' : params['class'];
|
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
|
$('#prompt_action_btn').removeClass(cls).addClass(cls);
|
||||||
// use of {{...}}
|
|
||||||
|
|
||||||
$(dialog).modal({
|
$(dialog).modal({
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
|
|||||||
@@ -263,7 +263,7 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-target="#alert-modal"
|
<button type="button" class="close" data-target="#form-modal2"
|
||||||
data-dismiss="modal" aria-hidden="true">×</button>
|
data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
<h3 ng-bind-html-unsafe="formModal2Header"></h3>
|
<h3 ng-bind-html-unsafe="formModal2Header"></h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user