diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index 19fb1ff682..a8b6afcba9 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -14,7 +14,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, Wait, Objec ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button) { ClearScope('home'); - + var buttons, html, e, waitCount, loadedCount; // Add buttons to the top of the Home page. We're using lib/ansible/generator_helpers.js-> Buttons() @@ -31,19 +31,19 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, Wait, Objec mode: 'all' } }; - + html = Button({ btn: buttons.refresh, action: 'refresh', toolbar: true }); - + html += Button({ btn: buttons.stream, action: 'stream', toolbar: true }); - + e = angular.element(document.getElementById('home-list-actions')); e.html(html); $compile(e)($scope); @@ -137,7 +137,7 @@ function HomeGroups($scope, $filter, $compile, $location, $routeParams, LogViewe opt, PreviousSearchParams; generator.inject(list, { mode: 'edit', scope: scope }); - + function ellipsis(a) { if (a.length > 20) { return a.substr(0,20) + '...'; @@ -299,7 +299,7 @@ function HomeGroups($scope, $filter, $compile, $location, $routeParams, LogViewe }; scope.editGroup = function (group_id, inventory_id) { - PreviousSearchParams = Store('CurrentSearchParams'); + PreviousSearchParams = Store('group_current_search_params'); GroupsEdit({ scope: scope, group_id: group_id, @@ -335,7 +335,7 @@ function HomeGroups($scope, $filter, $compile, $location, $routeParams, LogViewe var group = Find({ list: scope.home_groups, key: 'id', val: id }); if (group) { if (Empty(group.source)) { - // if no source, do nothing. + // if no source, do nothing. } else if (group.status === 'updating') { Alert('Update in Progress', 'The inventory update process is currently running for group ' + group.name + '. Use the Refresh button to monitor the status.', 'alert-info'); @@ -410,7 +410,7 @@ function HomeGroups($scope, $filter, $compile, $location, $routeParams, LogViewe } scope.removeGroupSummaryReady = scope.$on('GroupSummaryReady', function(e, event, inventory, data) { var html, title; - + Wait('stop'); // Build the html for our popover diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index ef06994582..44aeb64f1a 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -471,238 +471,259 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log -function InventoriesEdit($scope, $location, $routeParams, $compile, $log, $rootScope, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait, - GetSyncStatusMsg, InjectHosts, HostsReload, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty, Rest, ProcessErrors, - InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find, EditInventoryProperties, HostsEdit, - HostsDelete, ToggleHostEnabled, CopyMoveGroup, CopyMoveHost, Stream, GetBasePath, ShowJobSummary, ApplyEllipsis, WatchInventoryWindowResize, - HelpDialog, InventoryGroupsHelp, Store, ViewJob, SetContainerHeights) { +function InventoriesEdit ($scope, $location, $routeParams, $compile, GenerateList, ClearScope, Empty, Wait, Rest, Alert, LoadBreadCrumbs, GetBasePath, ProcessErrors, + Breadcrumbs, InventoryGroups, InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, GroupsEdit, InventoryUpdate, + GroupsCancelUpdate, ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, EditInventoryProperties, ToggleHostEnabled, Stream, ShowJobSummary, + InventoryGroupsHelp, HelpDialog, ViewJob, WatchInventoryWindowResize, SetContainerHeights, GetHostContainerRows, GetGroupContainerRows, GetGroupContainerHeight, + GroupsCopy, HostsCopy) +{ + + var PreviousSearchParams, + url, + hostScope = $scope.$new(); ClearScope(); - var generator = GenerateList, - list = InventoryGroups; + $scope.group_breadcrumbs = [{ + name: 'All', + id: 0, + description: '', + show: true, + ngicon: null, + has_children: false, + related: {}, + active_class: 'active', + show_failures: false + }]; - $scope.inventory_id = $routeParams.inventory_id; + $scope.refreshHostsOnGroupRefresh = false; + $scope.selected_group_id = null; - LoadBreadCrumbs({ - path: $location.path(), - title: '{{ inventory_name }}' - }); + Wait('start'); - // Handle inventory sync status changes - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); + + if ($scope.removeHostReloadComplete) { + $scope.removeHostReloadComplete(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function(e, data) { - var group, stat; - Wait('stop'); - $log.debug(data); - if ($scope.groups) { - // Assuming we have a list of groups available - group = Find({ list: $scope.groups, key: 'group_id', val: data.group_id }); - if (group) { - // And we found the affected group - $log.debug('Received event for group: ' + group.name); - if (data.status === 'failed' || data.status === 'successful') { - $log.debug('Update completed. Refreshing the tree.'); - $scope.refreshGroups(group.id, group.group_id); - } - else { - $log.debug('Status changed to: ' + data.status); - stat = GetSyncStatusMsg({ - status: data.status, - has_inventory_sources: group.has_inventory_sources, - source: group.source - }); - $log.debug('Changing tooltip to: ' + stat.tooltip); - group.status = data.status; - group.status_class = stat['class']; - group.status_tooltip = stat.tooltip; - group.launch_tooltip = stat.launch_tip; - group.launch_class = stat.launch_class; - } - } + $scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() { + if ($scope.initial_height) { + $('#hosts-container .well').height($scope.initial_height + 49); + $scope.initial_height = null; } }); - // After the tree data loads for the first time, generate the groups and hosts lists - if ($scope.removeGroupTreeLoaded) { - $scope.removeGroupTreeLoaded(); + if ($scope.removeInventoryLoaded) { + $scope.removeInventoryLoaded(); } - $scope.removeGroupTreeLoaded = $scope.$on('GroupTreeLoaded', function (event, inventory_name, groups) { - // Add breadcrumbs - var e, html, inventoryAutoHelp; + $scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() { + var e, rows; + + LoadBreadCrumbs({ + path: $location.path(), + title: '{{ inventory.name }}' + }); + + // Build page breadcrumbs e = angular.element(document.getElementById('breadcrumbs')); - e.html(Breadcrumbs({ list: list, mode: 'edit' })); + e.html(Breadcrumbs({ list: InventoryGroups, mode: 'edit' })); $compile(e)($scope); // Add groups view - generator.inject(list, { + GenerateList.inject(InventoryGroups, { mode: 'edit', - id: 'groups-container', + id: 'group-list-container', breadCrumbs: false, - searchSize: 'col-lg-5 col-md-5 col-sm-5', - skipTableHead: true + searchSize: 'col-lg-6 col-md-6 col-sm-6', + scope: $scope }); - // Keep the table header fixed while allowing the table body to scroll and still use element - html = "
" + generator.buildHeader() + "
\n"; - $(html).insertBefore('#groups-container .list-table-container'); + /*SetContainerHeights({ + group_scope: $scope, + host_scope: hostScope, + reloadHosts: false + });*/ - $scope.groups = groups; - $scope.inventory_name = inventory_name; - - // Default the selected group to the first node - if ($scope.groups.length > 0) { - $scope.selected_tree_id = $scope.groups[0].id; - $scope.selected_group_id = $scope.groups[0].group_id; - $scope.groups[0].selected_class = 'selected'; - $scope.groups[0].active_class = 'active-row'; - $scope.selected_group_name = $scope.groups[0].name; - } else { - $scope.selected_tree_id = null; - $scope.selected_group_id = null; + if ($(window).width() > 1210) { + $scope.initial_height = GetGroupContainerHeight() - 20; + $('#groups-container .list-table-container').height($scope.initial_height); + rows = GetGroupContainerRows(); + //$('#hosts-container .well').height( height ); } - - // Resize the containers based on viewport width/height - SetContainerHeights({ scope: $scope, reloadHosts: false }); + else { + rows = 20; + } + hostScope.host_page_size = rows; + $scope.group_page_size = rows; // Add hosts view $scope.show_failures = false; InjectHosts({ - scope: $scope, - inventory_id: $scope.inventory_id, - tree_id: $scope.selected_tree_id, - group_id: $scope.selected_group_id + group_scope: $scope, + host_scope: hostScope, + inventory_id: $scope.inventory.id, + tree_id: null, + group_id: null, + pageSize: rows }); - // As the window shrinks and expands, apply ellipsis - setTimeout(function () { - // Hack to keep group name from slipping to a new line - $('#groups_table .name-column').each(function () { - var td_width, level_width, level_padding, level, pct; - td_width = $(this).width(); - level_width = $(this).find('.level').width(); - level_padding = parseInt($(this).find('.level').css('padding-left').replace(/px/, '')); - level = level_width + level_padding; - pct = (100 - Math.ceil((level / td_width) * 100)) + '%'; - $(this).find('.group-name').css({ - width: pct + SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups }); + PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows }); + $scope.search(InventoryGroups.iterator, null, true); + }); + + if ($scope.removePostRefresh) { + $scope.removePostRefresh(); + } + $scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) { + if (set === 'groups') { + $scope.groups.forEach( function(group, idx) { + var stat, hosts_status; + stat = GetSyncStatusMsg({ + status: group.summary_fields.inventory_source.status, + has_inventory_sources: group.has_inventory_sources, + source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null ) + }); // from helpers/Groups.js + $scope.groups[idx].status_class = stat['class']; + $scope.groups[idx].status_tooltip = stat.tooltip; + $scope.groups[idx].launch_tooltip = stat.launch_tip; + $scope.groups[idx].launch_class = stat.launch_class; + hosts_status = GetHostsStatusMsg({ + active_failures: group.hosts_with_active_failures, + total_hosts: group.total_hosts, + inventory_id: $scope.inventory.id, + group_id: group.id + }); // from helpers/Groups.js + $scope.groups[idx].hosts_status_tip = hosts_status.tooltip; + $scope.groups[idx].show_failures = hosts_status.failures; + $scope.groups[idx].hosts_status_class = hosts_status['class']; + + $scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null; + $scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null; + + }); + if ($scope.refreshHostsOnGroupRefresh) { + $scope.refreshHostsOnGroupRefresh = false; + HostsReload({ + scope: hostScope, + group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id, + pageSize: hostScope.host_page_size }); - }); - ApplyEllipsis('#groups_table .group-name a'); - ApplyEllipsis('#hosts_table .host-name a'); - }, 2500); //give the window time to display - WatchInventoryWindowResize({ scope: $scope }); - - inventoryAutoHelp = Store('inventoryAutoHelp'); - if (inventoryAutoHelp !== 'off' && $scope.autoShowGroupHelp) { - $scope.showGroupHelp({ - autoShow: true - }); - } - - }); - - // Called after tree data is reloaded on refresh button click. - if ($scope.removeGroupTreeRefreshed) { - $scope.removeGroupTreeRefreshed(); - } - $scope.removeGroupTreeRefreshed = $scope.$on('GroupTreeRefreshed', function () { - // Reapply ellipsis to groups - setTimeout(function () { - ApplyEllipsis('#groups_table .group-name a'); - }, 2500); - // Reselect the preveiously selected group node, causing host view to refresh. - $scope.showHosts($scope.selected_tree_id, $scope.selected_group_id, false); - }); - - // Group was deleted. Now we need to refresh the group view. - if ($scope.removeGroupDeleteCompleted) { - $scope.removeGroupDeleteCompleted(); - } - $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', function () { - $scope.selected_tree_id = 1; - $scope.selected_group_id = null; - BuildTree({ - scope: $scope, - inventory_id: $scope.inventory_id, - refresh: true - }); - }); - - // Respond to a group drag-n-drop - if ($scope.removeCopMoveGroup) { - $scope.removeCopyMoveGroup(); - } - $scope.removeCopyMoveGroup = $scope.$on('CopyMoveGroup', function (e, inbound_tree_id, target_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) { - // Clicked on group - if (tree_id !== null) { - Wait('start'); - $scope.selected_tree_id = tree_id; - $scope.selected_group_id = group_id; - $scope.hosts = []; - $scope.show_failures = show_failures; // turn on failed hosts filter in hosts view - for (var i = 0; i < $scope.groups.length; i++) { - if ($scope.groups[i].id === tree_id) { - $scope.groups[i].selected_class = 'selected'; - $scope.groups[i].active_class = 'active-row'; - $scope.selected_group_name = $scope.groups[i].name; - } else { - $scope.groups[i].selected_class = ''; - $scope.groups[i].active_class = ''; - } } - if (Empty($scope.inventory_id)) { - $scope.inventory_id = $scope.groups[0].inentory_id; + else { + Wait('stop'); } - HostsReload({ - scope: $scope, - group_id: group_id, - tree_id: tree_id, - inventory_id: $scope.inventory_id + + WatchInventoryWindowResize({ + group_scope: $scope, + host_scope: hostScope }); - } else { - Wait('stop'); + } + }); + + // Load Inventory + url = GetBasePath('inventory') + $routeParams.inventory_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + $scope.inventory = data; + $scope.$emit('InventoryLoaded'); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $routeParams.inventory_id + + ' GET returned status: ' + status }); + }); + + // Load group on selection + function loadGroups(url) { + SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url }); + PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size }); + $scope.search(InventoryGroups.iterator, null, true, false, true); + } + + function setActiveGroupBreadcrumb() { + $scope.group_breadcrumbs.forEach(function(crumb, idx) { + $scope.group_breadcrumbs[idx].active_class = ''; + }); + $scope.group_breadcrumbs[$scope.group_breadcrumbs.length - 1].active_class = 'active'; + $scope.refreshHostsOnGroupRefresh = true; + $scope.selected_group_id = ($scope.group_breadcrumbs[$scope.group_breadcrumbs.length - 1].id === 0) ? null : $scope.group_breadcrumbs[$scope.group_breadcrumbs.length - 1].id; + } + + $scope.refreshHosts = function() { + HostsReload({ + scope: hostScope, + group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id, + pageSize: hostScope.host_page_size + }); + }; + + $scope.refreshGroups = function() { + $scope.refreshHostsOnGroupRefresh = true; + $scope.search(InventoryGroups.iterator, null, true, false, true); + }; + + $scope.restoreSearch = function() { + // Restore search params and related stuff, plus refresh + // groups and hosts lists + SearchInit({ + scope: $scope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + }); + $scope.refreshHostsOnGroupRefresh = true; + $scope.search(InventoryGroups.iterator, null, true, false, true); + }; + + $scope.groupSelect = function(id) { + var group = Find({ list: $scope.groups, key: 'id', val: id }); + $scope.group_breadcrumbs.push(group); + setActiveGroupBreadcrumb(); + loadGroups(group.related.children, group.id); + }; + + $scope.breadcrumbGroupSelect = function(id) { + var i, url; + $scope.group_breadcrumbs.every(function(crumb, idx) { + if (crumb.id === id) { + i = idx; + return false; + } + return true; + }); + $scope.group_breadcrumbs = $scope.group_breadcrumbs.slice(0,i + 1); + if (id > 0) { + url = $scope.group_breadcrumbs[$scope.group_breadcrumbs.length - 1].related.children; + } + else { + url = $scope.inventory.related.root_groups; + } + setActiveGroupBreadcrumb(); + loadGroups(url); }; $scope.createGroup = function () { + PreviousSearchParams = Store('group_current_search_params'); GroupsEdit({ scope: $scope, - inventory_id: $scope.inventory_id, + inventory_id: $scope.inventory.id, group_id: $scope.selected_group_id, mode: 'add' }); }; - $scope.editGroup = function (group_id, tree_id) { + $scope.editGroup = function (id) { + PreviousSearchParams = Store('group_current_search_params'); GroupsEdit({ scope: $scope, - inventory_id: $scope.inventory_id, - group_id: group_id, - tree_id: tree_id, - groups_reload: true, + inventory_id: $scope.inventory.id, + group_id: id, mode: 'edit' }); }; @@ -715,7 +736,7 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, $log, $rootS // if no source, do nothing. } else if (group.status === 'updating') { Alert('Update in Progress', 'The inventory update process is currently running for group ' + - group.name + '. Use the Refresh button to monitor the status.', 'alert-info'); + group.name + ' Click the button to monitor the status.', 'alert-info'); } else { Wait('start'); Rest.setUrl(group.related.inventory_source); @@ -726,109 +747,164 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, $log, $rootS url: data.related.update, group_name: data.summary_fields.group.name, group_source: data.source, - tree_id: group.id, - group_id: group.group_id + group_id: group.id, }); }) .error(function (data, status) { - Wait('stop'); ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + - group.related.inventory_source + ' POST returned status: ' + status }); + group.related.inventory_source + ' GET returned status: ' + status }); }); } } }; - $scope.cancelUpdate = function (tree_id) { - GroupsCancelUpdate({ scope: $scope, id: tree_id }); + $scope.cancelUpdate = function (id) { + GroupsCancelUpdate({ scope: $scope, id: id }); }; - $scope.toggle = function (tree_id) { - // Expand/collapse nodes - ToggleChildren({ scope: $scope, list: list, id: tree_id }); - }; - - $scope.refreshGroups = function (tree_id, group_id) { - // Refresh the tree data when refresh button cicked - if (tree_id) { - $scope.selected_tree_id = tree_id; - $scope.selected_group_id = group_id; - } - BuildTree({ scope: $scope, inventory_id: $scope.inventory_id, refresh: true }); - }; - - $scope.viewUpdateStatus = function (tree_id, group_id) { + $scope.viewUpdateStatus = function (id) { ViewUpdateStatus({ scope: $scope, - tree_id: tree_id, - group_id: group_id + group_id: id }); }; - $scope.deleteGroup = function (tree_id, group_id) { + $scope.copyGroup = function(id) { + PreviousSearchParams = Store('group_current_search_params'); + GroupsCopy({ + scope: $scope, + group_id: id + }); + }; + + $scope.deleteGroup = function (id) { GroupsDelete({ scope: $scope, - tree_id: tree_id, - group_id: group_id, - inventory_id: $scope.inventory_id + group_id: id, + inventory_id: $scope.inventory.id }); }; - $scope.createHost = function () { - HostsEdit({ scope: $scope, mode: 'add', host_id: null, selected_group_id: $scope.selected_tree_id, inventory_id: $scope.inventory_id }); - }; - $scope.editInventoryProperties = function () { - EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory_id }); + EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id }); }; - $scope.editHost = function (host_id) { - HostsEdit({ scope: $scope, mode: 'edit', host_id: host_id, inventory_id: $scope.inventory_id }); + hostScope.createHost = function () { + HostsEdit({ + host_scope: hostScope, + group_scope: $scope, + mode: 'add', + host_id: null, + selected_group_id: $scope.selected_group_id, + inventory_id: $scope.inventory.id + }); }; - $scope.deleteHost = function (host_id, host_name) { - HostsDelete({ scope: $scope, host_id: host_id, host_name: host_name }); + hostScope.editHost = function (host_id) { + HostsEdit({ + host_scope: hostScope, + group_scope: $scope, + mode: 'edit', + host_id: host_id, + inventory_id: $scope.inventory.id + }); }; - $scope.toggleHostEnabled = function (host_id, external_source) { - ToggleHostEnabled({ scope: $scope, host_id: host_id, external_source: external_source }); + hostScope.deleteHost = function (host_id, host_name) { + HostsDelete({ + parent_scope: $scope, + host_scope: hostScope, + host_id: host_id, + host_name: host_name + }); + }; + + hostScope.copyHost = function(id) { + PreviousSearchParams = Store('group_current_search_params'); + HostsCopy({ + group_scope: $scope, + host_scope: hostScope, + host_id: id + }); + }; + + /*hostScope.restoreSearch = function() { + SearchInit({ + scope: hostScope, + set: PreviousSearchParams.set, + list: PreviousSearchParams.list, + url: PreviousSearchParams.defaultUrl, + iterator: PreviousSearchParams.iterator, + sort_order: PreviousSearchParams.sort_order, + setWidgets: false + }); + hostScope.search('host'); + };*/ + + hostScope.toggleHostEnabled = function (host_id, external_source) { + ToggleHostEnabled({ + parent_scope: $scope, + host_scope: hostScope, + host_id: host_id, + external_source: external_source + }); }; $scope.showGroupActivity = function () { var url, title, group; if ($scope.selected_group_id) { - group = Find({ - list: $scope.groups, - key: 'id', - val: $scope.selected_tree_id + $scope.group_breadcrumbs.every(function(crumb) { + if (crumb.id === $scope.selected_group_id) { + group = crumb; + return false; + } + return true; }); url = GetBasePath('activity_stream') + '?group__id=' + $scope.selected_group_id; title = 'Showing all activities for group ' + group.name; } else { - title = 'Showing all activities for all ' + $scope.inventory_name + ' groups'; - url = GetBasePath('activity_stream') + '?group__inventory__id=' + $scope.inventory_id; + title = 'Showing all activities for all ' + $scope.inventory.name + ' groups'; + url = GetBasePath('activity_stream') + '?group__inventory__id=' + $scope.inventory.id; } Stream({ scope: $scope, - inventory_name: $scope.inventory_name, + inventory_name: $scope.inventory.name, url: url, - title: title + title: title, + search_iterator: 'group', + onClose: 'GroupStreamClosed' }); }; - $scope.showHostActivity = function () { + if ($scope.removeGroupStreamClosed) { + $scope.removeGroupStreamClosed(); + } + $scope.removeGroupStreamClosed = $scope.$on('GroupStreamClosed', function() { + $scope.refreshGroups(); + }); + + hostScope.showHostActivity = function () { var url, title; - title = 'Showing all activities for all ' + $scope.inventory_name + ' hosts'; - url = GetBasePath('activity_stream') + '?host__inventory__id=' + $scope.inventory_id; + title = 'Showing all activities for all ' + $scope.inventory.name + ' hosts'; + url = GetBasePath('activity_stream') + '?host__inventory__id=' + $scope.inventory.id; Stream({ - scope: $scope, - inventory_name: $scope.inventory_name, + scope: hostScope, + inventory_name: $scope.inventory.name, url: url, - title: title + title: title, + search_iterator: 'host', + onClose: 'HostStreamClosed' }); }; - $scope.showJobSummary = function (job_id) { + if (hostScope.removeHostStreamClosed) { + hostScope.removeHostStreamClosed(); + } + hostScope.removeHostStreamClosed = hostScope.$on('HostStreamClosed', function() { + $scope.refreshGroups(); + }); + + hostScope.showJobSummary = function (job_id) { ShowJobSummary({ job_id: job_id }); @@ -848,19 +924,31 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, $log, $rootS ViewJob({ scope: $scope, id: id }); }; - //Load tree data for the first time - BuildTree({ - scope: $scope, - inventory_id: $scope.inventory_id, - refresh: false + $scope.showHosts = function (group_id, show_failures) { + // Clicked on group + if (group_id !== null) { + Wait('start'); + hostScope.show_failures = show_failures; + $scope.groupSelect(group_id); + hostScope.hosts = []; + $scope.show_failures = show_failures; // turn on failed hosts filter in hosts view + } else { + Wait('stop'); + } + }; + + if ($scope.removeGroupDeleteCompleted) { + $scope.removeGroupDeleteCompleted(); + } + $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', function() { + $scope.refreshGroups(); }); } -InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', '$log', '$rootScope', 'GenerateList', 'ClearScope', 'InventoryGroups', 'InventoryHosts', - 'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsEdit', 'GroupsDelete', 'Breadcrumbs', - 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren', 'ViewUpdateStatus', 'GroupsCancelUpdate', - 'Find', 'EditInventoryProperties', 'HostsEdit', 'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost', - 'Stream', 'GetBasePath', 'ShowJobSummary', 'ApplyEllipsis', 'WatchInventoryWindowResize', 'HelpDialog', 'InventoryGroupsHelp', 'Store', - 'ViewJob', 'SetContainerHeights' -]; +InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', 'GenerateList', 'ClearScope', 'Empty', 'Wait', 'Rest', 'Alert', 'LoadBreadCrumbs', + 'GetBasePath', 'ProcessErrors', 'Breadcrumbs', 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', + 'GetHostsStatusMsg', 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', + 'EditInventoryProperties', 'ToggleHostEnabled', 'Stream', 'ShowJobSummary', 'InventoryGroupsHelp', 'HelpDialog', 'ViewJob', 'WatchInventoryWindowResize', + 'SetContainerHeights', 'GetHostContainerRows', 'GetGroupContainerRows', 'GetGroupContainerHeight', 'GroupsCopy', 'HostsCopy' + ]; \ No newline at end of file diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 4c40324751..778bbe87d1 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -319,8 +319,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' // Cancel the update process if (Empty(group)) { group = Find({ list: scope.groups, key: 'id', val: id }); - scope.selected_tree_id = group.id; - scope.selected_group_id = group.group_id; + scope.selected_group_id = group.id; } if (group && (group.status === 'running' || group.status === 'pending')) { @@ -615,26 +614,23 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched .factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', '$compile', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate', - 'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find', - 'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', 'BuildTree', + 'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find', 'WatchInventoryWindowResize', + 'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', function ($rootScope, $location, $log, $routeParams, $compile, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait, - GetChoices, UpdateGroup, SourceChange, Find, ParseVariableString, ToJSON, GroupsScheduleListInit, - SourceForm, SetSchedulesInnerDialogSize, BuildTree) { + GetChoices, UpdateGroup, SourceChange, Find, WatchInventoryWindowResize, ParseVariableString, ToJSON, GroupsScheduleListInit, + SourceForm, SetSchedulesInnerDialogSize) { return function (params) { var parent_scope = params.scope, group_id = params.group_id, - tree_id = params.tree_id, mode = params.mode, // 'add' or 'edit' inventory_id = params.inventory_id, - groups_reload = params.groups_reload, generator = GenerateForm, group_created = false, defaultUrl, master = {}, choicesReady, - base = $location.path().replace(/^\//, '').split('/')[0], modal_scope = parent_scope.$new(), properties_scope = parent_scope.$new(), sources_scope = parent_scope.$new(), @@ -730,7 +726,7 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched height: y, autoOpen: false, minWidth: 440, - title: 'Edit Group', + title: (mode === 'edit') ? 'Edit Group' : 'Add Group', closeOnEscape: false, create: function () { $('.ui-dialog[aria-describedby="group-modal-dialog"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x'); @@ -1038,14 +1034,8 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched } parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() { // Clean up - // Change the selected group - if (groups_reload && parent_scope.selected_tree_id !== tree_id) { - parent_scope.showHosts(tree_id, group_id, false); - } else { - Wait('stop'); - } - //WatchInventoryWindowResize(); - parent_scope.removeAddTreeRefreshed(); + Wait('stop'); + WatchInventoryWindowResize(); if (modal_scope.searchCleanUp) { modal_scope.searchCleanup(); } @@ -1062,42 +1052,7 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched } modal_scope.removeSaveComplete = modal_scope.$on('SaveComplete', function (e, error) { if (!error) { - // Update the parent view with any changes - if (groups_reload) { - $log.debug('calling UpdateGroup group_id: ' + group_id + ' name: ' + properties_scope.name + ' description: ' + properties_scope.description + - 'has_inventory_sources: ' + ((sources_scope.source && sources_scope.source.value) ? 'true' : 'false') + ' source: ' + sources_scope.source.value ); - UpdateGroup({ - scope: parent_scope, - group_id: group_id, - properties: { - name: properties_scope.name, - description: properties_scope.description, - has_inventory_sources: (sources_scope.source && sources_scope.source.value) ? true : false, - source: (sources_scope.source && sources_scope.source.value) ? sources_scope.source.value : '' - } - }); - parent_scope.$emit('GroupTreeRefreshed'); - } else if (base === 'inventories') { - if (mode === 'add') { - BuildTree({ - scope: parent_scope, - inventory_id: inventory_id, - refresh: true, - new_group_id: group_id - }); - } - else { - parent_scope.$emit('GroupTreeRefreshed'); - } - } else if (base === 'home') { - parent_scope.restoreSearch(); - try { - $('#group-modal-dialog').dialog('close'); - } - catch(err) { - // ignore - } - } + modal_scope.cancelModal(); } }); @@ -1167,20 +1122,21 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched // Cancel modal_scope.cancelModal = function () { - Wait('stop'); try { $('#group-modal-dialog').dialog('close'); } catch(e) { //ignore } - - //if (modal_scope.searchCleanup) { - // modal_scope.searchCleanup(); - //} - //if (base === 'inventories') { - // WatchInventoryWindowResize(); - //} + if (modal_scope.searchCleanup) { + modal_scope.searchCleanup(); + } + if (parent_scope.restoreSearch) { + parent_scope.restoreSearch(); + } + else { + Wait('stop'); + } }; // Save @@ -1463,11 +1419,328 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched scope.$emit('DisassociateGroup'); } }; - }; } ]) +.factory('GetRootGroups', ['Rest', 'ProcessErrors', 'GetBasePath', function(Rest, ProcessErrors, GetBasePath) { + return function(params) { + var scope = params.scope, + inventory_id = params.inventory_id, + //group_id = params.group_id, + callback = params.callback, + url; + + url = GetBasePath('inventory') + inventory_id + '/root_groups/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + scope.$emit(callback, data.results); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status }); + }); + }; +}]) + +.factory('GroupsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'GenerateList', 'GroupList', 'SearchInit', + 'PaginateInit', 'GetRootGroups', + function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit, GetRootGroups) { + return function(params) { + + var group_id = params.group_id, + parent_scope = params.scope, + scope = parent_scope.$new(), + parent_group = parent_scope.selected_group_id, + buttonSet, url, group; + + buttonSet = [{ + label: "Cancel", + onClick: function() { + scope.cancel(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "group-copy-cancel-button" + },{ + label: "OK", + onClick: function() { + scope.performCopy(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "group-copy-ok-button" + }]; + + if (scope.removeGroupsCopyPostRefresh) { + scope.removeGroupsCopyPostRefresh(); + } + scope.removeGroupCopyPostRefresh = scope.$on('PostRefresh', function() { + scope.copy_groups.forEach(function(row, i) { + scope.copy_groups[i].checked = '0'; + }); + Wait('stop'); + $('#group-copy-dialog').dialog('open'); + $('#group-copy-ok-button').attr('disabled','disabled'); + + // prevent backspace from navigation when not in input or textarea field + $(document).on("keydown", function (e) { + if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) { + e.preventDefault(); + } + }); + + }); + + if (scope.removeCopyDialogReady) { + scope.removeCopyDialogReady(); + } + scope.removeCopyDialogReady = scope.$on('CopyDialogReady', function() { + var url = GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; + url += (parent_group) ? '?not__id__in=' + group_id + ',' + parent_group : '?not__id=' + group_id; + GenerateList.inject(GroupList, { + mode: 'lookup', + id: 'copy-select-container', + scope: scope + //, + //instructions: instructions + }); + SearchInit({ + scope: scope, + set: GroupList.name, + list: GroupList, + url: url + }); + PaginateInit({ + scope: scope, + list: GroupList, + url: url, + mode: 'lookup' + }); + scope.search(GroupList.iterator); + }); + + if (scope.removeShowDialog) { + scope.removeShowDialog(); + } + scope.removeShowDialog = scope.$on('ShowDialog', function() { + var d; + scope.name = group.name; + scope.copy_choice = "copy"; + d = angular.element(document.getElementById('group-copy-dialog')); + $compile(d)(scope); + + CreateDialog({ + id: 'group-copy-dialog', + scope: scope, + buttons: buttonSet, + width: 650, + height: 650, + minWidth: 600, + title: 'Copy or Move Group', + callback: 'CopyDialogReady', + onClose: function() { + scope.cancel(); + } + }); + }); + + if (scope.removeRootGroupsReady) { + scope.removeRootGroupsReady(); + } + scope.removeRootGroupsReady = scope.$on('RootGroupsReady', function(e, root_groups) { + scope.offer_root_group = true; + scope.use_root_group = false; + root_groups.every(function(row) { + if (row.id === group_id) { + scope.offer_root_group = false; + return false; + } + return true; + }); + url = GetBasePath('groups') + group_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + group = data; + scope.$emit('ShowDialog'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status }); + }); + }); + + Wait('start'); + + GetRootGroups({ + scope: scope, + group_id: group_id, + inventory_id: parent_scope.inventory.id, + callback: 'RootGroupsReady' + }); + + scope.cancel = function() { + $(document).off("keydown"); + try { + $('#group-copy-dialog').dialog('close'); + } + catch(e) { + // ignore + } + scope.searchCleanup(); + parent_scope.restoreSearch(); + scope.$destroy(); + }; + + scope['toggle_' + GroupList.iterator] = function (id) { + var count = 0, + list = GroupList; + scope[list.name].forEach( function(row, i) { + if (row.id === id) { + if (row.checked === '0') { + scope[list.name][i].checked = '1'; + scope[list.name][i].success_class = 'success'; + } + else { + scope[list.name][i].checked = '0'; + scope[list.name][i].success_class = ''; + } + } else { + scope[list.name][i].checked = '0'; + scope[list.name][i].success_class = ''; + } + }); + // Check if any rows are checked + scope[list.name].forEach(function(row) { + if (row.checked === '1') { + count++; + } + }); + if (count === 0) { + $('#group-copy-ok-button').attr('disabled','disabled'); + } + else { + $('#group-copy-ok-button').removeAttr('disabled'); + } + }; + + scope.toggleUseRootGroup = function() { + var list = GroupList; + //console.log("scope.use_root_group: " + scope.use_root_group); + if (scope.use_root_group) { + $('#group-copy-ok-button').removeAttr('disabled'); + } + else { + // check for group selection + $('#group-copy-ok-button').attr('disabled','disabled'); + scope[list.name].every(function(row) { + if (row.checked === '1') { + $('#group-copy-ok-button').removeAttr('disabled'); + return false; + } + return true; + }); + } + }; + + scope.performCopy = function() { + var list = GroupList, + target, + url; + + Wait('start'); + + if (scope.use_root_group) { + target = null; + } + else { + scope[list.name].every(function(row) { + if (row.checked === '1') { + target = row; + return false; + } + return true; + }); + } + + if (scope.copy_choice === 'move') { + // Respond to move + + // disassociate the group from the original parent + if (scope.removeGroupRemove) { + scope.removeGroupRemove(); + } + scope.removeGroupRemove = scope.$on('RemoveGroup', function () { + if (parent_group > 0) { + // Only remove a group from a parent when the parent is a group and not the inventory root + url = GetBasePath('groups') + parent_group + '/children/'; + Rest.setUrl(url); + Rest.post({ id: group.id, disassociate: 1 }) + .success(function () { + scope.cancel(); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to remove ' + group.name + ' from group ' + parent_group + '. POST returned: ' + status }); + }); + } else { + scope.cancel(); + } + }); + + // add the new group to the target + url = (target) ? + GetBasePath('groups') + target.id + '/children/' : + GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; + group = { + id: group.id, + name: group.name, + description: group.description, + inventory: parent_scope.inventory.id + }; + Rest.setUrl(url); + Rest.post(group) + .success(function () { + scope.$emit('RemoveGroup'); + }) + .error(function (data, status) { + var target_name = (target) ? target.name : 'inventory'; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status }); + }); + } + else { + // Respond to copy by adding the new group to the target + url = (target) ? + GetBasePath('groups') + target.id + '/children/' : + GetBasePath('inventory') + parent_scope.inventory.id + '/groups/'; + + group = { + id: group.id, + name: group.name, + description: group.description, + inventory: parent_scope.inventory.id + }; + + Rest.setUrl(url); + Rest.post(group) + .success(function () { + scope.cancel(); + }) + .error(function (data, status) { + var target_name = (target) ? target.name : 'inventory'; + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to add ' + group.name + ' to ' + target_name + '. POST returned: ' + status + }); + }); + } + }; + + }; +}]) + .factory('ShowUpdateStatus', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventoryStatusForm', 'Wait', function ($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js index d9c0e9e409..a000d52529 100644 --- a/awx/ui/static/js/helpers/Hosts.js +++ b/awx/ui/static/js/helpers/Hosts.js @@ -14,7 +14,8 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'HostListDefinition', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'AuthService', 'HostsHelper', 'InventoryHelper', 'RelatedSearchHelper', 'InventoryFormDefinition', 'SelectionHelper', - 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'LogViewerHelper' + 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'LogViewerHelper', + 'GroupListDefinition' ]) .factory('SetEnabledMsg', [ function() { @@ -167,18 +168,21 @@ function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateI return function(params) { var scope = params.scope, + parent_scope = params.parent_scope, group_id = params.group_id, inventory_id = params.inventory_id, list = InventoryHosts, + pageSize = (params.pageSize) ? params.pageSize : 20, + url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' : GetBasePath('inventory') + inventory_id + '/hosts/'; scope.search_place_holder='Search ' + scope.selected_group_name; - //if (scope.removePostRefresh) { - // scope.removePostRefresh(); - //} - scope.removePostRefresh = scope.$on('PostRefresh', function(e, set) { + if (scope.removeHostsReloadPostRefresh) { + scope.removeHostsReloadPostRefresh(); + } + scope.removeHostsReloadPostRefresh = scope.$on('PostRefresh', function(e, set) { if (set === 'hosts') { for (var i=0; i < scope.hosts.length; i++) { //Set tooltip for host enabled flag @@ -187,7 +191,9 @@ function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateI SetStatus({ scope: scope }); setTimeout(function() { ApplyEllipsis('#hosts_table .host-name a'); }, 2500); Wait('stop'); - scope.$emit('HostReloadComplete'); + if (parent_scope) { + parent_scope.$emit('HostReloadComplete'); + } } }); @@ -195,7 +201,7 @@ function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateI SetContainerHeights({ scope: scope, reloadHosts: false }); SearchInit({ scope: scope, set: 'hosts', list: list, url: url }); - PaginateInit({ scope: scope, list: list, url: url }); + PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize }); if ($routeParams.host_name) { scope[list.iterator + 'InputDisable'] = false; @@ -220,16 +226,18 @@ function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateI function(GenerateList, InventoryHosts, HostsReload) { return function(params) { - var scope = params.scope, + var group_scope = params.group_scope, + host_scope = params.host_scope, inventory_id = params.inventory_id, group_id = params.group_id, + pageSize = params.pageSize, generator = GenerateList; // Inject the list html - generator.inject(InventoryHosts, { scope: scope, mode: 'edit', id: 'hosts-container', breadCrumbs: false, searchSize: 'col-lg-6 col-md-6 col-sm-6' }); + generator.inject(InventoryHosts, { scope: host_scope, mode: 'edit', id: 'hosts-container', breadCrumbs: false, searchSize: 'col-lg-6 col-md-6 col-sm-6' }); // Load data - HostsReload({ scope: scope, group_id: group_id, inventory_id: inventory_id }); + HostsReload({ scope: host_scope, group_id: group_id, inventory_id: inventory_id, parent_scope: group_scope, cpageSize: pageSize }); }; }]) @@ -336,8 +344,7 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostList, Gener .factory('HostsCreate', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', - 'ToJSON', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'ToJSON', function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, ParseTypeChange, Wait, ToJSON) { return function(params) { @@ -437,13 +444,13 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener .factory('HostsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', - 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', 'Empty', + 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON, - ParseVariableString, CreateDialog, TextareaResize, Empty) { + ParseVariableString, CreateDialog, TextareaResize) { return function(params) { - var parent_scope = params.scope, + var parent_scope = params.parent_scope, host_id = params.host_id, inventory_id = params.inventory_id, mode = params.mode, // 'add' or 'edit' @@ -451,10 +458,10 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener generator = GenerateForm, form = HostForm, defaultUrl, - scope = parent_scope.$new(), + scope = params.host_scope, master = {}, relatedSets = {}, - group, buttons; + buttons, url; generator.inject(HostForm, { mode: 'edit', id: 'host-modal-dialog', breadCrumbs: false, related: false, scope: scope }); generator.reset(); @@ -581,24 +588,26 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener scope.$emit('hostLoaded'); }) .error( function(data, status) { - ProcessErrors(scope, data, status, form, + ProcessErrors(parent_scope, data, status, form, { hdr: 'Error!', msg: 'Failed to retrieve host: ' + host_id + '. GET returned status: ' + status }); }); } else { // Add mode - group = Find({ list: scope.groups, key: 'id', val: selected_group_id }); - if (!Empty(group)) { - scope.has_inventory_sources = group.has_inventory_sources; - scope.enabled = true; - scope.variables = '---'; - defaultUrl = GetBasePath('groups') + group.group_id + '/hosts/'; - scope.$emit('hostVariablesLoaded'); - } - else { - ProcessErrors(scope, null, status, null, { hdr: 'Error', - msg: 'Group lookup failed. Selected group id: ' + selected_group_id }); - } + url = GetBasePath('groups') + selected_group_id + '/'; + Rest.setUrl(url); + Rest.get() + .success( function(data) { + scope.has_inventory_sources = data.has_inventory_sources; + scope.enabled = true; + scope.variables = '---'; + defaultUrl = data.related.hosts; + scope.$emit('hostVariablesLoaded'); + }) + .error( function(data, status) { + ProcessErrors(parent_scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve group: ' + selected_group_id + '. GET returned status: ' + status }); + }); } if (scope.removeSaveCompleted) { @@ -608,12 +617,12 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener var host, old_name; if (mode === 'edit') { // Update the name on the list - host = Find({ list: parent_scope.hosts, key: 'id', val: host_id }); + host = Find({ list: scope.hosts, key: 'id', val: host_id }); old_name = host.name; host.name = scope.name; host.enabled = (scope.enabled) ? true : false; host.enabled_flag = host.enabled; - SetStatus({ scope: parent_scope, host: host }); + SetStatus({ scope: scope, host: host }); // Update any titles attributes created by ApplyEllipsis if (old_name) { setTimeout(function() { @@ -630,12 +639,7 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener } else { $('#host-modal-dialog').dialog('close'); - HostsReload({ - scope: parent_scope, - group_id: parent_scope.selected_group_id, - tree_id: parent_scope.selected_tree_id, - inventory_id: parent_scope.inventory_id - }); + parent_scope.refreshHosts(); } // Restore ellipsis response to window resize @@ -691,48 +695,38 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener }; scope.cancelModal = function() { - $('#host-modal-dialog').dialog('close'); + try { + $('#host-modal-dialog').dialog('close'); + } + catch(err) { + // ignore + } + parent_scope.refreshHosts(); }; }; }]) -.factory('HostsDelete', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'Prompt', 'ProcessErrors', 'GetBasePath', - 'HostsReload', 'Wait', 'Find', 'Empty', -function($rootScope, $location, $log, $routeParams, Rest, Alert, Prompt, ProcessErrors, GetBasePath, HostsReload, Wait, Find, Empty) { +.factory('HostsDelete', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'Wait', +function($rootScope, $location, $log, $routeParams, Rest, Alert, Prompt, ProcessErrors, GetBasePath, HostsReload, Wait) { return function(params) { // Remove the selected host from the current group by disassociating var action_to_take, body, - scope = params.scope, + scope = params.parent_scope, host_id = params.host_id, host_name = params.host_name, group, url_list = []; - function getChildren(tree_id) { - var parent, found, j; - for (j = 0; j < scope.groups.length; j++) { - if (scope.groups[j].id === tree_id || scope.groups[j].parent === parent) { - found = true; - url_list.push(GetBasePath('groups') + scope.groups[j].group_id + '/hosts/'); - parent = scope.groups[j].id; - } - else { - if (found) { - break; - } - } - } - } - - if (!Empty(scope.selected_group_id)) { - group = Find({ list: scope.groups, key: 'id', val: scope.selected_tree_id }); - getChildren(group.id); + if (scope.selected_group_id) { + //group = Find({ list: parent_scope.groups, key: 'id', val: parent_scope.selected_group_id }); + //getChildren(group.id); + url_list.push(GetBasePath('groups') + scope.selected_group_id + '/hosts/'); } else { - url_list.push(GetBasePath('inventory') + scope.inventory_id + '/hosts/'); + url_list.push(GetBasePath('inventory') + scope.inventory.id + '/hosts/'); } if (scope.removeHostsReload) { @@ -740,7 +734,7 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, Prompt, Process } scope.removeHostsReload = scope.$on('hostsReload', function() { $('#prompt-modal').modal('hide'); - scope.showHosts(scope.selected_tree_id, scope.selected_group_id, false); + scope.refreshHosts(); }); $('#prompt-modal').on('hidden.bs.modal', function(){ Wait('stop'); }); @@ -782,6 +776,243 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, Prompt, Process }; }]) +.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'GenerateList', 'GroupList', 'SearchInit', + 'PaginateInit', + function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit) { + return function(params) { + + var host_id = params.host_id, + group_scope = params.group_scope, + parent_scope = params.host_scope, + parent_group = group_scope.selected_group_id, + scope = parent_scope.$new(), + buttonSet, url, host; + + buttonSet = [{ + label: "Cancel", + onClick: function() { + scope.cancel(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "host-copy-cancel-button" + },{ + label: "OK", + onClick: function() { + scope.performCopy(); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "host-copy-ok-button" + }]; + + if (scope.removeHostCopyPostRefresh) { + scope.removeHostCopyPostRefresh(); + } + scope.removeHostCopyPostRefresh = scope.$on('PostRefresh', function() { + scope.copy_groups.forEach(function(row, i) { + scope.copy_groups[i].checked = '0'; + }); + Wait('stop'); + $('#host-copy-dialog').dialog('open'); + $('#host-copy-ok-button').attr('disabled','disabled'); + + // prevent backspace from navigation when not in input or textarea field + $(document).on("keydown", function (e) { + if (e.which === 8 && !$(e.target).is('input[type="text"], textarea')) { + e.preventDefault(); + } + }); + }); + + if (scope.removeHostCopyDialogReady) { + scope.removeHostCopyDialogReady(); + } + scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() { + var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/'; + GenerateList.inject(GroupList, { + mode: 'lookup', + id: 'copy-host-select-container', + scope: scope + //, + //instructions: instructions + }); + SearchInit({ + scope: scope, + set: GroupList.name, + list: GroupList, + url: url + }); + PaginateInit({ + scope: scope, + list: GroupList, + url: url, + mode: 'lookup' + }); + scope.search(GroupList.iterator, null, true, false); + }); + + if (scope.removeShowDialog) { + scope.removeShowDialog(); + } + scope.removeShowDialog = scope.$on('ShowDialog', function() { + var d; + scope.name = host.name; + scope.copy_choice = "copy"; + d = angular.element(document.getElementById('host-copy-dialog')); + $compile(d)(scope); + CreateDialog({ + id: 'host-copy-dialog', + scope: scope, + buttons: buttonSet, + width: 650, + height: 650, + minWidth: 600, + title: 'Copy or Move Host', + callback: 'HostCopyDialogReady', + onClose: function() { + scope.cancel(); + } + }); + }); + + Wait('start'); + + url = GetBasePath('hosts') + host_id + '/'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + host = data; + scope.$emit('ShowDialog'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. GET returned: ' + status }); + }); + + + scope.cancel = function() { + $(document).off("keydown"); + try { + $('#host-copy-dialog').dialog('close'); + } + catch(e) { + // ignore + } + scope.searchCleanup(); + group_scope.restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists + scope.$destroy(); + }; + + scope['toggle_' + GroupList.iterator] = function (id) { + var count = 0, + list = GroupList; + scope[list.name].forEach( function(row, i) { + if (row.id === id) { + if (row.checked === '0') { + scope[list.name][i].checked = '1'; + scope[list.name][i].success_class = 'success'; + } + else { + scope[list.name][i].checked = '0'; + scope[list.name][i].success_class = ''; + } + } else { + scope[list.name][i].checked = '0'; + scope[list.name][i].success_class = ''; + } + }); + // Check if any rows are checked + scope[list.name].forEach(function(row) { + if (row.checked === '1') { + count++; + } + }); + if (count === 0) { + $('#host-copy-ok-button').attr('disabled','disabled'); + } + else { + $('#host-copy-ok-button').removeAttr('disabled'); + } + }; + + scope.performCopy = function() { + var list = GroupList, + target, + url; + + Wait('start'); + + if (scope.use_root_group) { + target = null; + } + else { + scope[list.name].every(function(row) { + if (row.checked === '1') { + target = row; + return false; + } + return true; + }); + } + + if (scope.copy_choice === 'move') { + // Respond to move + + // disassociate the host from the original parent + if (scope.removeHostRemove) { + scope.removeHostRemove(); + } + scope.removeHostRemove = scope.$on('RemoveHost', function () { + if (parent_group > 0) { + // Only remove a host from a parent when the parent is a group and not the inventory root + url = GetBasePath('groups') + parent_group + '/hosts/'; + Rest.setUrl(url); + Rest.post({ id: host.id, disassociate: 1 }) + .success(function () { + scope.cancel(); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to remove ' + host.name + ' from group ' + parent_group + '. POST returned: ' + status }); + }); + } else { + scope.cancel(); + } + }); + + // add the new host to the target + url = GetBasePath('groups') + target.id + '/hosts/'; + Rest.setUrl(url); + Rest.post(host) + .success(function () { + scope.$emit('RemoveHost'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status }); + }); + } + else { + // Respond to copy by adding the new host to the target + url = GetBasePath('groups') + target.id + '/hosts/'; + Rest.setUrl(url); + Rest.post(host) + .success(function () { + scope.cancel(); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to add ' + host.name + ' to ' + target.name + '. POST returned: ' + status + }); + }); + } + }; + + + }; +}]) + .factory('EditHostGroups', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', function($rootScope, $location, $log, $routeParams, Rest, Alert, GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js index d0f6b5ca69..04db2bc5c6 100644 --- a/awx/ui/static/js/helpers/search.js +++ b/awx/ui/static/js/helpers/search.js @@ -279,7 +279,6 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) url += connect + scope[iterator + 'ExtraParms']; } url = url.replace(/\&\&/g, '&'); - if (calcOnly) { scope.$emit('searchParamsReady', url); } diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index 34c6dff9d9..e0f9f3533d 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -142,6 +142,7 @@ angular.module('InventoryGroupsDefinition', []) mode: 'all', ngClick: "copyGroup(group.id)", awToolTip: 'Copy or move group', + ngShow: "group.id > 0", dataPlacement: "top" }, "delete": { diff --git a/awx/ui/static/js/lists/InventoryHosts.js b/awx/ui/static/js/lists/InventoryHosts.js index b8a1e1e867..ef0c371331 100644 --- a/awx/ui/static/js/lists/InventoryHosts.js +++ b/awx/ui/static/js/lists/InventoryHosts.js @@ -76,6 +76,12 @@ angular.module('InventoryHostsDefinition', []) awToolTip: 'Edit host', dataPlacement: 'top' }, + copy: { + mode: 'all', + ngClick: "copyHost(host.id)", + awToolTip: 'Copy or move host to another group', + dataPlacement: "top" + }, "delete": { //label: 'Delete', ngClick: "deleteHost(host.id, host.name)", @@ -89,7 +95,7 @@ angular.module('InventoryHostsDefinition', []) create: { mode: 'all', ngClick: "createHost()", - ngHide: 'selected_tree_id == 1', //disable when 'All Hosts' selected + ngHide: '!selected_group_id', //disable when 'All Hosts' selected awToolTip: "Create a new host" }, stream: { diff --git a/awx/ui/static/js/widgets/Stream.js b/awx/ui/static/js/widgets/Stream.js index 5034a936ca..f08a06c4d1 100644 --- a/awx/ui/static/js/widgets/Stream.js +++ b/awx/ui/static/js/widgets/Stream.js @@ -92,7 +92,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti .factory('StreamBreadCrumbs', ['$rootScope', '$location', function ($rootScope, $location) { return function () { - // Load the breadcrumbs array. We have to do things a bit different than Utilities.LoadBreadcrumbs. + // Load the breadcrumbs array. We have to do things a bit different than Utilities.LoadBreadcrumbs. // Rather than botch that all up, we'll do our own thing here. $rootScope.breadcrumbs = []; var path, title, i, j, paths = $location.path().split('/'); @@ -188,7 +188,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti // labels obj1 = activity.object1; obj2 = activity.object2; - + // objects obj1_obj = (activity.summary_fields[obj1]) ? activity.summary_fields[obj1][0] : null; if (obj1 === obj2) { @@ -279,7 +279,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti n, rows, scope; if (activity) { - // Setup changes field + // Setup changes field activity.changes_stringified = JSON.stringify(activity.changes, null, '\t'); n = activity.changes_stringified.match(/\n/g); rows = (n) ? n.length : 1; @@ -300,7 +300,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti $('#form-modal').on('show.bs.modal', function () { $('#form-modal-body').css({ width: 'auto', //probably not needed - height: 'auto', //probably not needed + height: 'auto', //probably not needed 'max-height': '100%' }); }); @@ -349,7 +349,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti if (paths.length > 1 && /^\d+/.test(paths[paths.length - 1])) { type = paths[paths.length - 2]; type = (type === 'inventories') ? 'inventory' : type.replace(/s$/, ''); - //defaultUrl += '?object1=' + type + '&object1__id=' + + //defaultUrl += '?object1=' + type + '&object1__id=' + defaultUrl += '?' + type + '__id=' + paths[paths.length - 1]; } else if (paths.length > 1) { type = paths[paths.length - 1]; @@ -368,7 +368,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti // Fix inventory name. The way we're doing breadcrumbs doesn't support bind variables. if (inventory_name) { - itm = Find({ list: $rootScope.breadcrumbs, key: 'title', val: '{{ inventory_name }}' }); + itm = Find({ list: $rootScope.breadcrumbs, key: 'title', val: '{{ inventory.name }}' }); if (itm) { itm.title = inventory_name; } @@ -424,7 +424,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti scope.activities.forEach(function(activity, i) { var row = scope.activities[i], type, url; - + if (scope.activities[i].summary_fields.actor) { scope.activities[i].user = "" + scope.activities[i].summary_fields.actor.username + ""; @@ -436,7 +436,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti deleted = /^\_delete/; obj1 = scope.activities[i].object1; obj2 = scope.activities[i].object2; - + if ((obj1 === "schedule" || obj2 === "schedule") && activity.summary_fields.schedule) { if (activity.summary_fields.inventory_source) { type = 'inventory_source'; diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 621ab37d05..93759e04ce 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -36,6 +36,7 @@ @import "log-viewer.less"; @import "job-details.less"; @import "jobs.less"; +@import "inventory-edit.less"; html, body { height: 100%; } @@ -1823,7 +1824,7 @@ tr td button i { } #hosts-container.col-lg-6 { - margin-toop: 15px; + margin-top: 15px; padding-left: 15px; padding-right: 15px; } diff --git a/awx/ui/static/less/inventory-edit.less b/awx/ui/static/less/inventory-edit.less new file mode 100644 index 0000000000..b69d642c4a --- /dev/null +++ b/awx/ui/static/less/inventory-edit.less @@ -0,0 +1,123 @@ +/********************************************* + * Copyright (c) 2014 AnsibleWorks, Inc. + * + * inventory-edit.less + * + * custom animation mixins for ansible-ui + * + */ +#inventory_edit { + #breadcrumbs .nav-path { + margin-bottom: 8px; + } +} +.group-breadcrumbs { + list-style: none; + overflow: hidden; + padding: 0; + margin: 0 0 8px 0; +} +.group-breadcrumbs li { + float: left; + height: 26px; + margin-top: 3px; + margin-bottom: 3px; +} +.group-breadcrumbs li a { + color: @white; + font-weight: normal; + text-decoration: none; + padding: 3px 8px 3px 20px; + background: @blue-dark; /* fallback color */ + position: relative; + left: 0; + top: 0; + display: block; + float: left; +} +.group-breadcrumbs li.active a { + background: @grey; + color: @black; + font-weight: normal; +} +.group-breadcrumbs li a:after { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 13px dashed transparent; /* Go big on the size, and let overflow hide */ + border-bottom: 13px dashed transparent; + border-left: 11px solid @blue-dark; + position: absolute; + top: 50%; + margin-top: -13px; + left: 100%; + z-index: 2; +} +.group-breadcrumbs li.active a:after { + border-left: 13px solid @grey; +} +.group-breadcrumbs li a:before { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 13px dashed transparent; + border-bottom: 13px dashed transparent; + border-left: 11px solid @white; + position: absolute; + top: 50%; + margin-top: -13px; + margin-left: 1px; + left: 100%; + z-index: 1; +} +.group-breadcrumbs li.active a:before { + border-left: 11px solid @white; +} + +#group-copy-dialog, +#host-copy-dialog { + .highlight { + font-size: 16px; + font-weight: bold; + color: red; + padding-right: 5px; + } + .title { + font-weight: bold; + margin-bottom: 15px; + } + .well { + padding-left: 8px; + padding-right: 8px; + padding-top: 8px; + padding-bottom: 8px; + } + .page-row ul li a { + font-size: 12px; + } + .page-row .col-lg-8 { + width: 100%; + } + .page-row .col-md-8 { + width: 100%; + } +} + +#copy-group-radio-container .form-group { + margin-left: 20px; + margin-bottom: 10px; +} + +#copy-group-target-container .form-group { + margin-top: 10px; + margin-left: 20px; + margin-bottom: 15px; +} + +#group-list-container { + .list-actions { + margin-bottom: 0; + } +} diff --git a/awx/ui/static/lib/ansible/InventoryTree.js b/awx/ui/static/lib/ansible/InventoryTree.js index 8ea9b739b7..99b3bb6081 100644 --- a/awx/ui/static/lib/ansible/InventoryTree.js +++ b/awx/ui/static/lib/ansible/InventoryTree.js @@ -7,7 +7,7 @@ * Build data for the tree selector table used on inventory detail page. * */ - + 'use strict'; angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'PromptDialog']) @@ -101,14 +101,14 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P } function buildGroups(tree_data, parent, level) { - + var children, stat, hosts_status, group, sorted = SortNodes(tree_data), expand, show; - + sorted.forEach( function(row, i) { id++; - + stat = GetSyncStatusMsg({ status: sorted[i].summary_fields.inventory_source.status, has_inventory_sources: sorted[i].has_inventory_sources, @@ -131,7 +131,7 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P show = getShowState(sorted[i].id); if (show === null) { // this is a node we haven't seen before, so check the parent expand/collapse state - // If parent is not expanded, then child should be hidden. + // If parent is not expanded, then child should be hidden. show = true; if (parent > 0) { groups.every(function(g) { @@ -249,7 +249,7 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P ]) -// Update a group with a set of properties +// Update a group with a set of properties .factory('UpdateGroup', ['ApplyEllipsis', 'GetSyncStatusMsg', 'Empty', function (ApplyEllipsis, GetSyncStatusMsg, Empty) { return function (params) { @@ -258,9 +258,9 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P 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) { + if (scope.groups[i].id === group_id) { grp = scope.groups[i]; for (p in properties) { if (p === 'name') { @@ -291,12 +291,12 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P scope.groups[i][p] = properties[p]; } } - if (scope.groups[i].id === scope.selected_tree_id) { + /*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 @@ -406,7 +406,7 @@ angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper', 'P show: true }); - // Respond to move + // Respond to move scope.moveGroup = function () { var url, group, parent; $('#copy-prompt-modal').modal('hide'); diff --git a/awx/ui/static/lib/ansible/Utilities.js b/awx/ui/static/lib/ansible/Utilities.js index 76ad262b62..2089a3d4cd 100644 --- a/awx/ui/static/lib/ansible/Utilities.js +++ b/awx/ui/static/lib/ansible/Utilities.js @@ -19,7 +19,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) .factory('ClearScope', [ function () { return function () { - + $('#form-modal .modal-body').empty(); $('#form-modal2 .modal-body').empty(); @@ -59,7 +59,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) .factory('ToggleClass', function () { return function (selector, cssClass) { - // Toggles the existance of a css class on a given element + // Toggles the existance of a css class on a given element if ($(selector) && $(selector).hasClass(cssClass)) { $(selector).removeClass(cssClass); } else if ($(selector)) { @@ -69,7 +69,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) }) -/* +/* * Pass in the header and message you want displayed on TB modal dialog found in index.html. * Assumes an #id of 'alert-modal'. Pass in an optional TB alert class (i.e. alert-danger, alert-success, * alert-info...). Pass an optional function(){}, if you want a specific action to occur when user @@ -91,7 +91,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) backdrop: 'static' }); scope.disableButtons2 = (disableButtons) ? true : false; - + $('#alert-modal2').on('hidden.bs.modal', function () { if (action) { action(); @@ -238,7 +238,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) return (a === 'ies') ? 'y' : ''; } - //Keep a list of path/title mappings. When we see /organizations/XX in the path, for example, + //Keep a list of path/title mappings. When we see /organizations/XX in the path, for example, //we'll know the actual organization name it maps to. if (!Empty(crumb)) { found = false; @@ -288,7 +288,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } else { //if (/_/.test(paths[i])) { // replace '_' with space and uppercase each word - + //} //title = paths[i].charAt(0).toUpperCase() + paths[i].slice(1); title = paths[i].replace(/(?:^|_)\S/g, toUppercase).replace(/_/g, ' '); @@ -313,7 +313,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) .factory('HelpDialog', ['$rootScope', '$location', 'Store', function ($rootScope, $location, Store) { return function (params) { - + var defn = params.defn, current_step = params.step, autoShow = params.autoShow || false; @@ -463,9 +463,9 @@ angular.module('Utilities', ['RestServices', 'Utilities']) ]) -/* +/* * Split the current path by '/' and use the array elements from 0 up to and - * including idx as the new path. If no idx value supplied, use 0 to length - 1. + * including idx as the new path. If no idx value supplied, use 0 to length - 1. * */ .factory('ReturnToCaller', ['$location', 'Empty', @@ -496,9 +496,9 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } ]) -/* - * Display a spinning icon in the center of the screen to freeze the - * UI while waiting on async things to complete (i.e. API calls). +/* + * Display a spinning icon in the center of the screen to freeze the + * UI while waiting on async things to complete (i.e. API calls). * Wait('start' | 'stop'); * */ @@ -575,16 +575,16 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } ]) -/* +/* * Make an Options call to the API and retrieve dropdown options * * GetChoices({ * scope: Parent $scope - * url: API resource to access + * url: API resource to access * field: API element in the response object that contains the option list. * variable: Scope variable that will receive the list. * callback: Optional. Will issue scope.$emit(callback) on completion. - * choice_name: Optional. Used when list is found in a variable other than 'choices'. + * choice_name: Optional. Used when list is found in a variable other than 'choices'. * }) */ .factory('GetChoices', ['Rest', 'ProcessErrors', @@ -655,8 +655,8 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } ]) -/* - * DeugForm({ form:
, scope: }); +/* + * DebugForm({ form: , scope: }); * * Use to log the $pristine and $valid properties of each form element. Helpful when form * buttons fail to enable/disable properly. @@ -721,7 +721,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) .factory('ApplyEllipsis', [ function () { return function (selector) { - // Add a hidden element to the DOM. We'll use this to calc the px length of + // Add a hidden element to the DOM. We'll use this to calc the px length of // our target text. var tmp = $('#string-test'); if (!tmp.length) { diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js index dddf57898f..b5abf3af25 100644 --- a/awx/ui/static/lib/ansible/form-generator.js +++ b/awx/ui/static/lib/ansible/form-generator.js @@ -3,7 +3,7 @@ * * FormGenerator * - * Pass in a form definition and get back an html template. Use the.inject() method + * Pass in a form definition and get back an html template. Use the.inject() method * to add the template to the DOM and compile. * */ @@ -123,7 +123,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator try { $('#help-modal').empty().dialog('destroy'); } catch (e) { - //ignore any errors should the dialog not be initialized + //ignore any errors should the dialog not be initialized } if (options.modal) { @@ -134,7 +134,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator if (options.modal_title_id) { this.scope[options.modal_title_id] = (options.mode === 'add') ? form.addTitle : form.editTitle; } else { - this.scope.formModalHeader = (options.mode === 'add') ? form.addTitle : form.editTitle; //Default title for default modal + this.scope.formModalHeader = (options.mode === 'add') ? form.addTitle : form.editTitle; //Default title for default modal } } if (options.modal_selector) { @@ -189,8 +189,11 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator } }); } + if (!this.scope.$$phase) { - this.scope.$digest(); + setTimeout(function() { + this.scope.$digest(); + }, 100); } return this.scope; @@ -201,7 +204,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator // Get HTML without actually injecting into DOM. Caller is responsible for any injection. // Example: // html = GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); - + this.mode = options.mode; this.modal = (options.modal) ? true : false; this.setForm(form); @@ -351,7 +354,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator active: active, activate: function () { // Maintain in local storage of list of all accordions by page, recording - // the active panel for each. If user navigates away and comes back, + // the active panel for each. If user navigates away and comes back, // we can activate the last panely viewed. $('.jqui-accordion').each(function () { var active = $(this).accordion('option', 'active'), @@ -542,7 +545,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator return html; } - + if (field.type === 'alertblock') { html += "
\n"; html += "
\n", action; for (action in this.form.actions) { if (this.form.actions[action].mode === 'all' || this.form.actions[action].mode === options.mode) { @@ -1102,8 +1105,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator build: function (options) { // - // Generate HTML. Do NOT call this function directly. Called by inject(). Returns an HTML - // string to be injected into the current view. + // Generate HTML. Do NOT call this function directly. Called by inject(). Returns an HTML + // string to be injected into the current view. // var btn, button, fld, field, html = '', i, section, group, tab, sectionShow, offset, width; @@ -1115,7 +1118,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator html += this.breadCrumbs(options); } } - + if (this.form.collapse && this.form.collapseMode === options.mode) { html += "
\n"; } - + for (itm in form.related) { collection = form.related[itm]; html += "

" + (collection.title || collection.editTitle) + "

\n"; @@ -1370,7 +1373,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator } html += "
\n"; // accordion inner } - + if (!options.collapseAlreadyStarted) { html += "
\n"; // accordion body } @@ -1386,7 +1389,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator itm = params.related, collection = form.related[itm], act, action, fld, cnt, base, fAction; - + if (collection.instructions) { html += "
\n"; html += "\n"; diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index 6899e92fd1..b2dab18e4a 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -181,45 +181,41 @@ angular.module('ListGenerator', ['GeneratorHelpers']) if (options.showSearch=== undefined || options.showSearch === true) { html += "
\n"; - if (list.name !== 'groups') { - if (options.searchSize) { - html += SearchWidget({ - iterator: list.iterator, - template: list, - mini: true, - size: options.searchSize, - searchWidgets: list.searchWidgets - }); - } else if (options.mode === 'summary') { - html += SearchWidget({ - iterator: list.iterator, - template: list, - mini: true, - size: 'col-lg-6' - }); - } else if (options.mode === 'lookup' || options.id !== undefined) { - html += SearchWidget({ - iterator: list.iterator, - template: list, - mini: true, - size: 'col-lg-8' - }); - } else { - html += SearchWidget({ - iterator: list.iterator, - template: list, - mini: true - }); - } + if (options.searchSize) { + html += SearchWidget({ + iterator: list.iterator, + template: list, + mini: true, + size: options.searchSize, + searchWidgets: list.searchWidgets + }); + } else if (options.mode === 'summary') { + html += SearchWidget({ + iterator: list.iterator, + template: list, + mini: true, + size: 'col-lg-6' + }); + } else if (options.mode === 'lookup' || options.id !== undefined) { + html += SearchWidget({ + iterator: list.iterator, + template: list, + mini: true, + size: 'col-lg-8' + }); + } else { + html += SearchWidget({ + iterator: list.iterator, + template: list, + mini: true + }); } if (options.mode !== 'lookup') { //actions base = $location.path().replace(/^\//, '').split('/')[0]; html += "
- + + +
-
+
+
+
- +
- + + + + +
-
+
- + - +
- + \ No newline at end of file