/************************************ * * Copyright (c) 2014 AnsibleWorks, Inc. * * InventoryTree.js * * Build data for the tree selector table used on inventory detail page. * */ 'use strict'; 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', ['Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait', 'GetSyncStatusMsg', 'GetHostsStatusMsg', function (Rest, GetBasePath, ProcessErrors, SortNodes, Wait, GetSyncStatusMsg, GetHostsStatusMsg) { 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; 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 buildGroups(tree_data, parent, level) { var i, j, children, stat, hosts_status, group, sorted = SortNodes(tree_data); for (i = 0; i < sorted.length; i++) { id++; stat = GetSyncStatusMsg({ status: sorted[i].summary_fields.inventory_source.status }); // 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 = []; for (j = 0; j < sorted[i].children.length; j++) { children.push(sorted[i].children[j].id); } 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, group_id: sorted[i].id, event_level: level, children: children, ngicon: (sorted[i].children.length > 0) ? 'fa fa-minus-square-o node-toggle' : 'fa fa-square-o node-no-toggle', related: { children: (sorted[i].children.length > 0) ? sorted[i].related.children : '', inventory_source: sorted[i].related.inventory_source }, 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'], selected_class: '', show: true, isDraggable: true, isDroppable: true }; groups.push(group); if (new_group_id && group.group_id === new_group_id) { // For new group scope.selected_tree_id = id; scope.selected_group_id = group.group_id; } 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) { Wait('stop'); 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 }); }); } 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].group_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 }); 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 += "
\n"; html += "
\n"; html += "
\n"; html += "
\n"; html += "\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 += "

Move Group

\n"; } else { html += "

Copy or Move?

\n"; } html += "
\n"; html += "
\n"; if (target.id === 1) { html += "

Are you sure you want to move group " + inbound.name + " to the top level?

"; } else if (inbound.parent === 0) { html += "

Are you sure you want to move group " + inbound.name + " from the top level and make it a child of " + target.name + "?

"; } else { html += "
\n"; html += "

Would you like to copy or move group " + inbound.name + " to group " + target.name + "?

\n"; html += "
\n"; html += " Move\n"; html += " Copy\n"; html += "
\n"; html += "
\n"; } html += "
\n"; html += "
\n"; html += "Cancel\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 += "Yes\n"; } html += "
\n"; html += "
\n"; html += "
\n"; html += "
\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 += "
\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 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 += "
\n"; html += "
\n"; html += "
\n"; html += "
\n"; html += "\n"; html += "

Copy Host

\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 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 }); }); }; } }; } ]);