AC-416 Letest UI changes plus added warning anytime user attempts to add a top-level group to another group.

This commit is contained in:
chouseknecht 2013-09-08 15:55:42 -04:00
parent 3a56ff7cec
commit 7b57189eca
6 changed files with 207 additions and 163 deletions

View File

@ -14,8 +14,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
.factory('GroupsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupList', 'GenerateList',
'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'GroupsAdd', 'RefreshTree', 'SelectionInit',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, LoadBreadCrumbs, SearchInit,
PaginateInit, ProcessErrors, GetBasePath, GroupsAdd, RefreshTree, SelectionInit) {
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, Prompt, SearchInit, PaginateInit,
ProcessErrors, GetBasePath, GroupsAdd, RefreshTree, SelectionInit) {
return function(params) {
var inventory_id = params.inventory_id;
@ -33,7 +33,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
});
scope.formModalActionLabel = 'Select';
scope.formModalHeader = 'Add Existing Groups';
scope.formModalHeader = 'Copy Groups';
scope.formModalCancelShow = true;
scope.formModalActionClass = 'btn btn-success';
@ -43,8 +43,46 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
var url = (group_id) ? GetBasePath('groups') + group_id + '/children/' :
GetBasePath('inventory') + inventory_id + '/groups/';
SelectionInit({ scope: scope, list: list, url: url });
var finish = scope.formModalAction;
scope.formModalAction = function() {
var groups = [];
for (var j=0; j < scope.selected.length; j++) {
if (scope.inventoryRootGroups.indexOf(scope.selected[j].id) > -1) {
groups.push(scope.selected[j].name);
}
}
if (groups.length > 0) {
var action = function() {
$('#prompt-modal').modal('hide');
finish();
}
if (groups.length == 1) {
Prompt({ hdr: 'Warning', body: 'Be aware that ' + groups[0] +
' is a top level group. Adding it to ' + scope.selectedNodeName + ' will remove it from the top level. Do you ' +
' want to continue with this action?',
action: action });
}
else {
var list = '';
for (var i=0; i < groups.length; i++) {
if (i+1 == groups.length) {
list += ' and ' + groups[i];
}
else {
list += groups[i] + ', ';
}
}
Prompt({ hdr: 'Warning', body: 'Be aware that ' + list +
' are top level groups. Adding them to ' + scope.selectedNodeName + ' will remove them from the top level. Do you ' +
' want to continue with this action?',
action: action });
}
}
}
if (scope.PostRefreshRemove) {
scope.PostRefreshRemove();
}

View File

@ -95,7 +95,8 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
.factory('TreeInit', ['Alert', 'Rest', 'Authorization', '$http', 'LoadTreeData', 'GetBasePath', 'ProcessErrors', 'Wait',
function(Alert, Rest, Authorization, $http, LoadTreeData, GetBasePath, ProcessErrors, Wait) {
'LoadRootGroups',
function(Alert, Rest, Authorization, $http, LoadTreeData, GetBasePath, ProcessErrors, Wait, LoadRootGroups) {
return function(params) {
var scope = params.scope;
@ -109,7 +110,8 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
var inventory_id = inventory.id;
var inventory_descr = inventory.description;
var tree_id = '#tree-view';
var json_tree_data;
// After loading the Inventory top-level data, initialize the tree
if (scope.buildTreeRemove) {
scope.buildTreeRemove();
@ -117,12 +119,13 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
scope.buildTreeRemove = scope.$on('buildTree', function(e, treeData, index, group_idx) {
var idx = index;
var selected = (group_idx !== undefined && group_idx !== null) ? group_idx : 'inventory-node';
json_tree_data = treeData;
$(tree_id).jstree({
"core": { //"initially_open":['inventory-node'],
"html_titles": true
},
"plugins": ['themes', 'json_data', 'ui', 'contextmenu', 'dnd', 'crrm'],
"plugins": ['themes', 'json_data', 'ui', 'dnd', 'crrm', 'sort'],
"themes": {
"theme": "ansible",
"dots": false,
@ -133,47 +136,15 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
"select_limit": 1
},
"json_data": {
data: treeData
/*,
ajax: {
url: function(node){
scope.selected_node = node;
return $(node).attr('children');
},
headers: { 'Authorization': 'Token ' + Authorization.getToken() },
success: function(data) {
var response = [];
for (var i=0; i < data.results.length; i++) {
response.push({
data: {
title: data.results[i].name
},
attr: {
id: idx,
group_id: data.results[i].id,
type: 'group',
name: data.results[i].name,
description: data.results[i].description,
inventory: data.results[i].inventory,
all: data.results[i].related.all_hosts,
children: data.results[i].related.children + '?order_by=name',
hosts: data.results[i].related.hosts,
variable: data.results[i].related.variable_data,
"data-failures": data.results[i].has_active_failures
},
state: 'closed'
});
idx++;
}
return response;
}
}
*/
data: json_tree_data
},
"dnd": { },
"crrm": {
"move": {
"check_move": function(m) {
if (m.np.attr('id') == 'tree-view') {
return false;
}
if (m.op.attr('id') == m.np.attr('id')) {
// old parent and new parent cannot be the same
return false;
@ -182,134 +153,130 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}
}
},
"crrm" : { },
"contextmenu": {
items: scope.treeController
}
"crrm" : { }
});
});
$(tree_id).bind("loaded.jstree", function () {
scope['treeLoading'] = false;
Wait('stop');
scope.$emit('treeLoaded');
});
$(tree_id).bind("loaded.jstree", function () {
scope['treeLoading'] = false;
$(tree_id).bind('move_node.jstree', function(e, data) {
// When user drags-n-drops a node, update the API
Wait('start');
var node, target, url, parent, inv_id, variables;
node = $('#tree-view li[id="' + data.rslt.o[0].id + '"]'); // node being moved
parent = $('#tree-view li[id="' + data.args[0].op[0].id + '"]'); //node moving from
target = $('#tree-view li[id="' + data.rslt.np[0].id + '"]'); // node moving to
inv_id = inventory_id;
function cleanUp() {
LoadRootGroups({ scope: scope });
Wait('stop');
scope.$emit('treeLoaded');
});
$(tree_id).bind('move_node.jstree', function(e, data) {
// When user drags-n-drops a node, update the API
Wait('start');
var node, target, url, parent, inv_id, variables;
node = $('#tree-view li[id="' + data.rslt.o[0].id + '"]'); // node being moved
parent = $('#tree-view li[id="' + data.args[0].op[0].id + '"]'); //node moving from
target = $('#tree-view li[id="' + data.rslt.np[0].id + '"]'); // node moving to
inv_id = inventory_id;
function cleanUp() {
Wait('stop');
if (!scope.$$phase) {
scope.$digest();
}
}
if (scope.removeCopyVariables) {
scope.removeCopyVariables();
}
scope.removeCopyVariables = scope.$on('copyVariables', function(e, id, url) {
if (variables) {
Rest.setUrl(url);
Rest.put(variables)
.success(function(data, status, headers, config) {
cleanUp();
})
.error(function(data, status, headers, config) {
cleanUp();
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to update variables. PUT returned status: ' + status });
});
}
else {
cleanUp();
}
});
if (scope['addToTargetRemove']) {
scope.addToTargetRemove();
}
scope.addToTargetRemove = scope.$on('addToTarget', function() {
// add the new group to the target parent
var url = (target.attr('type') == 'group') ? GetBasePath('base') + 'groups/' + target.attr('group_id') + '/children/' :
GetBasePath('inventory') + inv_id + '/groups/';
var group = {
name: node.attr('name'),
description: node.attr('description'),
inventory: node.attr('inventory')
}
Rest.setUrl(url);
Rest.post(group)
.success( function(data, status, headers, config) {
//Update the node with new attributes
var filter = (scope.inventoryFailureFilter) ? "has_active_failures=true&" : "";
node.attr('group_id', data.id);
node.attr('variable', data.related.variable_data);
node.attr('all', data.related.all_hosts);
node.attr('children', data.related.children + '?' + filter + 'order_by=name');
node.attr('hosts', data.related.hosts);
node.attr('data-failures', data.has_active_failures);
scope.$emit('copyVariables', data.id, data.related.variable_data);
if (scope.removeCopyVariables) {
scope.removeCopyVariables();
}
scope.removeCopyVariables = scope.$on('copyVariables', function(e, id, url) {
if (variables) {
Rest.setUrl(url);
Rest.put(variables)
.success(function(data, status, headers, config) {
cleanUp();
})
.error( function(data, status, headers, config) {
.error(function(data, status, headers, config) {
cleanUp();
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to add ' + node.attr('name') + ' to ' +
target.attr('name') + '. POST returned status: ' + status });
{ hdr: 'Error!', msg: 'Failed to update variables. PUT returned status: ' + status });
});
});
// disassociate the group from the original parent
if (scope.removeGroupRemove) {
scope.removeGroupRemove();
}
scope.removeGroupRemove = scope.$on('removeGroup', function() {
var url = (parent.attr('type') == 'group') ? GetBasePath('base') + 'groups/' + parent.attr('group_id') + '/children/' :
GetBasePath('inventory') + inv_id + '/groups/';
Rest.setUrl(url);
Rest.post({ id: node.attr('group_id'), disassociate: 1 })
.success( function(data, status, headers, config) {
scope.$emit('addToTarget');
})
.error( function(data, status, headers, config) {
cleanUp();
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to remove ' + node.attr('name') + ' from ' +
parent.attr('name') + '. POST returned status: ' + status });
});
});
// Lookup the inventory. We already have what we need except for variables.
Rest.setUrl(GetBasePath('base') + 'groups/' + node.attr('group_id') + '/');
Rest.get()
else {
cleanUp();
}
});
if (scope['addToTargetRemove']) {
scope.addToTargetRemove();
}
scope.addToTargetRemove = scope.$on('addToTarget', function() {
// add the new group to the target parent
var url = (target.attr('type') == 'group') ? GetBasePath('base') + 'groups/' + target.attr('group_id') + '/children/' :
GetBasePath('inventory') + inv_id + '/groups/';
var group = {
name: node.attr('name'),
description: node.attr('description'),
inventory: node.attr('inventory')
}
Rest.setUrl(url);
Rest.post(group)
.success( function(data, status, headers, config) {
//Update the node with new attributes
var filter = (scope.inventoryFailureFilter) ? "has_active_failures=true&" : "";
node.attr('group_id', data.id);
node.attr('variable', data.related.variable_data);
node.attr('all', data.related.all_hosts);
node.attr('children', data.related.children + '?' + filter + 'order_by=name');
node.attr('hosts', data.related.hosts);
node.attr('data-failures', data.has_active_failures);
scope.$emit('copyVariables', data.id, data.related.variable_data);
})
.error( function(data, status, headers, config) {
cleanUp();
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to add ' + node.attr('name') + ' to ' +
target.attr('name') + '. POST returned status: ' + status });
});
});
// disassociate the group from the original parent
if (scope.removeGroupRemove) {
scope.removeGroupRemove();
}
scope.removeGroupRemove = scope.$on('removeGroup', function() {
var url = (parent.attr('type') == 'group') ? GetBasePath('base') + 'groups/' + parent.attr('group_id') + '/children/' :
GetBasePath('inventory') + inv_id + '/groups/';
Rest.setUrl(url);
Rest.post({ id: node.attr('group_id'), disassociate: 1 })
.success( function(data, status, headers, config) {
variables = (data.variables) ? JSON.parse(data.variables) : "";
scope.$emit('removeGroup');
scope.$emit('addToTarget');
})
.error( function(data, status, headers, config) {
cleanUp();
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to lookup group ' + node.attr('name') +
'. GET returned status: ' + status });
{ hdr: 'Error!', msg: 'Failed to remove ' + node.attr('name') + ' from ' +
parent.attr('name') + '. POST returned status: ' + status });
});
if (!scope.$$phase) {
scope.$digest();
}
});
// When user clicks on a group, display the related hosts in the list view
// Lookup the inventory. We already have what we need except for variables.
Rest.setUrl(GetBasePath('base') + 'groups/' + node.attr('group_id') + '/');
Rest.get()
.success( function(data, status, headers, config) {
variables = (data.variables) ? JSON.parse(data.variables) : "";
scope.$emit('removeGroup');
})
.error( function(data, status, headers, config) {
cleanUp();
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to lookup group ' + node.attr('name') +
'. GET returned status: ' + status });
});
if (!scope.$$phase) {
scope.$digest();
}
});
// When user clicks on a group
$(tree_id).bind("select_node.jstree", function(e, data){
scope.$emit('NodeSelect', data.inst.get_json()[0]);
});
});
Wait('start');
LoadTreeData(params);
@ -317,11 +284,32 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}
}])
.factory('LoadRootGroups', ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) {
return function(params) {
// Build an array of root group IDs. We'll need this when copying IDs.
var scope = params.scope;
Rest.setUrl(scope.inventoryRootGroupsUrl);
Rest.get()
.success( function(data, status, headers, config) {
scope.inventoryRootGroups = [];
for (var i=0; i < data.results.length; i++){
scope.inventoryRootGroups.push(data.results[i].id);
}
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to retrieve root groups for inventory: ' +
scope.inventory_id + '. GET status: ' + status });
});
}
}])
.factory('LoadInventory', ['$routeParams', 'Alert', 'Rest', 'Authorization', '$http', 'ProcessErrors',
'RelatedSearchInit', 'RelatedPaginateInit', 'GetBasePath', 'LoadBreadCrumbs', 'InventoryForm',
'RelatedSearchInit', 'RelatedPaginateInit', 'GetBasePath', 'LoadBreadCrumbs', 'InventoryForm', 'LoadRootGroups',
function($routeParams, Alert, Rest, Authorization, $http, ProcessErrors, RelatedSearchInit, RelatedPaginateInit,
GetBasePath, LoadBreadCrumbs, InventoryForm) {
GetBasePath, LoadBreadCrumbs, InventoryForm, LoadRootGroups) {
return function(params) {
// Load inventory detail record
@ -330,6 +318,13 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
var form = InventoryForm;
scope.relatedSets = [];
scope.master = {};
if (scope.removeLevelOneGroups) {
scope.removeLevelOneGroups();
}
scope.removeLevelOneGroups = scope.$on('inventoryLoaded', function() {
LoadRootGroups({ scope: scope });
});
Rest.setUrl(GetBasePath('inventory') + scope['inventory_id'] + '/');
Rest.get()
@ -358,7 +353,8 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}
}
scope.inventoryGroupsUrl = data.related.groups;
scope.inventoryGroupsUrl = data.related.groups;
scope.inventoryRootGroupsUrl = data.related.root_groups;
scope.TreeParams = { scope: scope, inventory: data };
scope.variable_url = data.related.variable_data;
scope.relatedSets['hosts'] = { url: data.related.hosts, iterator: 'host' };
@ -372,13 +368,14 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
scope.$emit('inventoryLoaded');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $routeParams.id + '. GET status: ' + status });
});
}
}])
.factory('RefreshGroupName', [ function() {
return function(node, name, description) {
// Call after GroupsEdit controller saves changes
@ -389,6 +386,7 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}
}])
.factory('RefreshTree', ['Alert', 'Rest', 'Authorization', '$http', 'TreeInit', 'LoadInventory',
function(Alert, Rest, Authorization, $http, TreeInit, LoadInventory) {
return function(params) {

View File

@ -12,7 +12,7 @@ angular.module('GroupListDefinition', [])
name: 'groups',
iterator: 'group',
selectTitle: 'Add Group',
selectTitle: 'Copy Groups',
editTitle: 'Groups',
index: true,
well: false,
@ -29,8 +29,8 @@ angular.module('GroupListDefinition', [])
actions: {
help: {
awPopOver: "Select groups by clicking on each group you wish to add. Add the selected groups to your inventory " +
"or to the selected parent group by clicking the <em>Select</em> button.",
awPopOver: "Choose groups by clicking on each group you wish to add. Click the <em>Select</em> button to add the groups to " +
"the selected inventory group.",
dataPlacement: 'left',
dataContainer: '#form-modal .modal-content',
icon: "icon-question-sign",

View File

@ -101,6 +101,12 @@ angular.module('Utilities',[])
$cookieStore.put('sessionExpired', true);
$location.path('/login');
}
else if (status == 401 && data.detail && data.detail == 'Invalid token') {
// should this condition be treated as an expired session?? Yes, for now.
$rootScope.sessionExpired = true;
$cookieStore.put('sessionExpired', true);
$location.path('/login');
}
else if (data.non_field_errors) {
Alert('Error!', data.non_field_errors);
}

View File

@ -1048,16 +1048,17 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
// "aw-tool-tip=\"Edit inventory properties\" data-placement=\"bottom\"><i class=\"icon-edit\"></i> " +
// "Inventory Properties</button>\n";
html += "<button type=\"button\" class=\"btn btn-default btn-xs\" ng-click=\"editGroup()\" ng-hide=\"groupEditHide\" " +
"aw-tool-tip=\"Edit the selected group\" data-placement=\"bottom\"><i class=\"icon-edit\"></i> " +
"aw-tool-tip=\"Edit the selected group's properties\" data-placement=\"bottom\"><i class=\"icon-edit\"></i> " +
"Properties</button>\n";
//html += "<button type=\"button\" class=\"btn btn-default btn-xs\" ng-click=\"editHosts()\" ng-hide=\"showGroupHelp\" " +
// "aw-tool-tip=\"Modify and create inventory hosts\" data-placement=\"bottom\"><i class=\"icon-laptop\"></i> Hosts</button>\n";
html += "<button type=\"button\" class=\"btn btn-success btn-xs\" ng-click=\"addGroup()\" ng-hide=\"groupAddHide\" " +
"aw-tool-tip=\"Add an existing group\" data-placement=\"bottom\"><i class=\"icon-check\"></i> Add Existing Group</button>\n";
"aw-tool-tip=\"Copy existing groups to the selected group\" data-placement=\"bottom\"><i class=\"icon-check\"></i> Copy</button>\n";
html += "<button type=\"button\" class=\"btn btn-success btn-xs\" ng-click=\"createGroup()\" ng-hide=\"groupCreateHide\" " +
"aw-tool-tip=\"Create a new group\" data-placement=\"bottom\"><i class=\"icon-plus\"></i> Create New Group</button>\n";
"aw-tool-tip=\"Create a brand new group and add it to the selected group\" data-placement=\"bottom\"><i class=\"icon-plus\"></i> Create New</button>\n";
html += "<button type=\"button\" class=\"btn btn-danger btn-xs\" ng-click=\"deleteGroup()\" ng-hide=\"groupDeleteHide\" " +
"aw-tool-tip=\"Delete the selected group\" data-placement=\"bottom\"><i class=\"icon-trash\"></i> Delete Group</button>\n";
"aw-tool-tip=\"Permanently delete the selected group. Any hosts in the group will still be available in All Hosts.\" " +
"data-placement=\"bottom\"><i class=\"icon-trash\"></i> Delete</button>\n";
html += "</div><!-- tree controls -->\n";
html += "<div id=\"tree-view\"></div>\n";
html += "</div><!-- tree-container -->\n";

View File

@ -17,9 +17,10 @@
.jstree-ansible .jstree-closed > ins { background-position:-54px 0; }
.jstree-ansible .jstree-leaf > ins { background-position:-36px 0; }
.jstree-ansible .jstree-hovered { background:#e7f4f9; border:1px solid #e7f4f9; padding:0 2px 0 1px; }
.jstree-ansible li a { height: 19px;}
.jstree-ansible .jstree-hovered { background:#d9edf7; border:1px solid #e7f4f9; padding:0 2px 0 1px; }
.jstree-ansible .jstree-clicked { background:#d9edf7; border:1px solid #3a87ad; padding:0 2px 0 1px; color: #000; }
/* 3a87ad */
/* if data-failures=true (set usng has_active_failures on hosts, groups, inventory), link color is red */
.jstree-ansible li[data-failures="true"] .jstree-clicked { color: #000; }
.jstree-ansible li[data-failures="false"] .jstree-clicked { color: #000; }