mirror of
https://github.com/ZwareBear/awx.git
synced 2026-04-06 08:01:48 -05:00
* Part 1: building new search components Directives: smart-search, column-sort, paginate Service: QuerySet Model: DjangoSearchModel * Part 2: Implementing new search components, de-implementing old search components Remove old code: * tagSearch directive * old pagination strategy * old column sorting strategy * lookup Add new directives to list/form generator: * smart-search, * paginate * column-sort Connect $state + dataset resolution * upgrade ui-router lib to v1.0.0-beta3 * Custom $urlMatcherFactory.type - queryset * Render lists, forms, related, lookups in named views * Provide html templates in list/form/lookup/related state definitions * Provide dataset through resolve block in state definitions Update utilities * isEmpty filter * use async validation strategy in awlookup directive * Part 3: State implementations (might split into per-module commits) * Support optional state definition flag: squashSearchUrl. *_search params are only URI-encoded if squashSearchUrl is falsey. * * Fix list badge counts * Clear search input after search term(s) applied * Chain of multiple search terms in one submission * Hook up activity stream * Hook up portal mode * Fix pagination range calculations * Hook up organization sub-list views * Hook up listDefinition.search defaults * Fix ng-disabled conditions reflecting RBAC access on form fields * Fix actively-editing indicator in generated lists * form generator - fix undefined span, remove dead event listeners * wrap hosts/groups lists in a panel, fix groups list error * Smart search directive: clear all search tags * Search tags - ‘Clear All’ text - 12px Search key - remove top padding/margin Search key - reverse bolding of relationship fields / label, add commas Search tags - remove padding-bottom Lookup modal - “X” close button styled incorrectly Lookup modal - List title not rendered Lookup modal - 20px margin between buttons * Portal Mode Fix default column-sort on jobs list Hide column-oort on job status column Apply custom search bar sizes * stateDefinition.factory Return ES6 Promise instead of $q promise. $q cannot be safely provided during module.config() phase Some generated state trees (inventory / inventoryManage) need to be reduced to one promise. Side-step issues caused by ui-router de-registering ALL registered states that match placeholder state name/url pattern. e.g. inventories.lazyLoad() would de-register inventoryManage states if a page refresh occured @ /#/inventories/** * Combine generated state trees: inventories + inventoryManage Hook up inventory sync schedule list/form add /form edit views * Hook up system job schedule list/add/edit states * Fix breadcrumb of generated states in /setup view Fix typo in scheduler search prefix * Remove old search system deritus from list definitions * Fix breadcrumb definitions in states registered in app.js config block * Transclude list action buttons in generated form lists * Lookup Modal passes acceptance criterea: Modal cancel/exit - don’t update form field’s ng-model Modal save - do update form field's ng-model Transclude generated list contents into <lookup-modal> directive Lookup modal test spec * Fix typo in merge conflict resolution * Disable failing unit tests pending revision * Integrate smart-search architechture into add-permissions modal * use a semicolon delimiter instead of comma to avoid collision with django __in comparator * Hook up Dashboard > Hosts states, update Dashboard Inventory/Project counts with new search filters * Misc bug splat Add 20px spacing around root ui-view Fix missing closing div in related views Remove dupe line in smart-search controller * Remove defunct LookupHelper code * Rebuild inventories list status tooltips on updates to dataset Code cleanup - remove defunct modules Remove LookupHelper / LookupInit code Remove pre-RBAC permissions module * Add mising stateTree / basePath properties to form definitions * Resolve i18n conflicts in list and form generator Freeze dependencies * Integrate sockets * Final bug splat: fix jobs > job details and jobs > scheduled routing fix mis-resolved merge conflicts swap console.info for $log.debug
466 lines
17 KiB
JavaScript
466 lines
17 KiB
JavaScript
/*************************************************
|
|
* Copyright (c) 2015 Ansible, Inc.
|
|
*
|
|
* All Rights Reserved
|
|
*************************************************/
|
|
|
|
|
|
/* jshint loopfunc: true */
|
|
/**
|
|
* @ngdoc function
|
|
* @name helpers.function:Hosts
|
|
* @description Routines that handle host add/edit/delete on the Inventory detail page.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import listGenerator from '../shared/list-generator/main';
|
|
|
|
export default
|
|
angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'HostListDefinition',
|
|
listGenerator.name, 'HostsHelper',
|
|
'InventoryHelper', 'InventoryFormDefinition', 'SelectionHelper',
|
|
'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'StandardOutHelper',
|
|
'GroupListDefinition'
|
|
])
|
|
|
|
.factory('SetEnabledMsg', [ function() {
|
|
return function(host) {
|
|
if (host.has_inventory_sources) {
|
|
// Inventory sync managed, so not clickable
|
|
host.enabledToolTip = (host.enabled) ? 'Host is available' : 'Host is not available';
|
|
}
|
|
else {
|
|
// Clickable
|
|
host.enabledToolTip = (host.enabled) ? 'Host is available. Click to toggle.' : 'Host is not available. Click to toggle.';
|
|
}
|
|
};
|
|
}])
|
|
|
|
.factory('SetHostStatus', ['SetEnabledMsg', function(SetEnabledMsg) {
|
|
return function(host) {
|
|
// Set status related fields on a host object
|
|
host.activeFailuresLink = '/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + host.inventory +
|
|
'&host_name=' + encodeURI(host.name);
|
|
if (host.has_active_failures === true) {
|
|
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
|
|
host.active_failures = 'failed';
|
|
}
|
|
else if (host.has_active_failures === false && host.last_job === null) {
|
|
host.has_active_failures = 'none';
|
|
host.badgeToolTip = "No job data available.";
|
|
host.active_failures = 'n/a';
|
|
}
|
|
else if (host.has_active_failures === false && host.last_job !== null) {
|
|
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
|
|
host.active_failures = 'success';
|
|
}
|
|
|
|
host.enabled_flag = host.enabled;
|
|
SetEnabledMsg(host);
|
|
|
|
};
|
|
}])
|
|
|
|
.factory('SetStatus', ['$filter', 'SetEnabledMsg', 'Empty', function($filter, SetEnabledMsg, Empty) {
|
|
return function(params) {
|
|
|
|
var scope = params.scope,
|
|
host = params.host,
|
|
i, html, title;
|
|
|
|
function ellipsis(a) {
|
|
if (a.length > 25) {
|
|
return a.substr(0,25) + '...';
|
|
}
|
|
return a;
|
|
}
|
|
|
|
function noRecentJobs() {
|
|
title = 'No job data';
|
|
html = "<p>No recent job data available for this host.</p>\n";
|
|
}
|
|
|
|
function setMsg(host) {
|
|
var j, job, jobs;
|
|
|
|
if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) {
|
|
if (host.has_active_failures === true) {
|
|
host.badgeToolTip = 'Most recent job failed. Click to view jobs.';
|
|
host.active_failures = 'error';
|
|
}
|
|
else {
|
|
host.badgeToolTip = "Most recent job successful. Click to view jobs.";
|
|
host.active_failures = 'successful';
|
|
}
|
|
if (host.summary_fields.recent_jobs.length > 0) {
|
|
// build html table of job status info
|
|
jobs = host.summary_fields.recent_jobs.sort(
|
|
function(a,b) {
|
|
// reverse numerical order
|
|
return -1 * (a - b);
|
|
});
|
|
title = "Recent Jobs";
|
|
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
|
|
html += "<thead>\n";
|
|
html += "<tr>\n";
|
|
html += "<th>Status</th>\n";
|
|
html += "<th>Finished</th>\n";
|
|
html += "<th>Name</th>\n";
|
|
html += "</tr>\n";
|
|
html += "</thead>\n";
|
|
html += "<tbody>\n";
|
|
for (j=0; j < jobs.length; j++) {
|
|
job = jobs[j];
|
|
html += "<tr>\n";
|
|
|
|
// SmartStatus-tooltips are named --success whereas icon-job uses successful
|
|
var iconStatus = (job.status === 'successful') ? 'success' : 'failed';
|
|
|
|
html += "<td><a href=\"#/jobs/" + job.id + "\"><i class=\"fa DashboardList-status SmartStatus-tooltip--" + iconStatus + " icon-job-" +
|
|
job.status + "\"></i></a></td>\n";
|
|
|
|
html += "<td>" + ($filter('longDate')(job.finished)).replace(/ /,'<br />') + "</td>\n";
|
|
|
|
html += "<td class=\"break\"><a href=\"#/jobs/" + job.id + "\" " +
|
|
"aw-tool-tip=\"" + job.status.charAt(0).toUpperCase() + job.status.slice(1) +
|
|
". Click for details\" data-placement=\"top\">" + ellipsis(job.name) + "</a></td>\n";
|
|
|
|
html += "</tr>\n";
|
|
}
|
|
html += "</tbody>\n";
|
|
html += "</table>\n";
|
|
}
|
|
else {
|
|
noRecentJobs();
|
|
}
|
|
}
|
|
else if (host.has_active_failures === false && host.last_job === null) {
|
|
host.badgeToolTip = "No job data available.";
|
|
host.active_failures = 'none';
|
|
noRecentJobs();
|
|
}
|
|
host.job_status_html = html;
|
|
host.job_status_title = title;
|
|
}
|
|
|
|
if (!Empty(host)) {
|
|
// update single host
|
|
setMsg(host);
|
|
SetEnabledMsg(host);
|
|
}
|
|
else {
|
|
// update all hosts
|
|
for (i=0; i < scope.hosts.length; i++) {
|
|
setMsg(scope.hosts[i]);
|
|
SetEnabledMsg(scope.hosts[i]);
|
|
}
|
|
}
|
|
|
|
};
|
|
}])
|
|
|
|
.factory('HostsReload', [ '$stateParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'Wait',
|
|
'SetHostStatus', 'SetStatus', 'ApplyEllipsis',
|
|
function($stateParams, Empty, InventoryHosts, GetBasePath, Wait, SetHostStatus, SetStatus,
|
|
ApplyEllipsis) {
|
|
return function(params) {
|
|
|
|
var scope = params.scope,
|
|
parent_scope = params.parent_scope;
|
|
|
|
// @issue: OLD SEARCH
|
|
// var list = InventoryHosts,
|
|
// group_id = params.group_id,
|
|
// inventory_id = params.inventory_id;
|
|
// pageSize = (params.pageSize) ? params.pageSize : 20,
|
|
//
|
|
// url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' :
|
|
// GetBasePath('inventory') + inventory_id + '/hosts/';
|
|
|
|
// @issue: OLD SEARCH
|
|
// scope.search_place_holder='Search ' + scope.selected_group_name;
|
|
|
|
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
|
|
scope.hosts[i].enabled_flag = scope.hosts[i].enabled;
|
|
}
|
|
SetStatus({ scope: scope });
|
|
setTimeout(function() { ApplyEllipsis('#hosts_table .host-name a'); }, 2500);
|
|
Wait('stop');
|
|
if (parent_scope) {
|
|
parent_scope.$emit('HostReloadComplete');
|
|
}
|
|
}
|
|
});
|
|
|
|
// @issue: OLD SEARCH
|
|
// SearchInit({ scope: scope, set: 'hosts', list: list, url: url });
|
|
// PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize });
|
|
//
|
|
// if ($stateParams.host_name) {
|
|
// scope[list.iterator + 'InputDisable'] = false;
|
|
// scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
|
|
// scope[list.iterator + 'SearchField'] = 'name';
|
|
// scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
|
|
// scope[list.iterator + 'SearchSelectValue'] = null;
|
|
// }
|
|
//
|
|
// if (scope.show_failures) {
|
|
// scope[list.iterator + 'InputDisable'] = true;
|
|
// scope[list.iterator + 'SearchValue'] = 'true';
|
|
// scope[list.iterator + 'SearchField'] = 'has_active_failures';
|
|
// scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label;
|
|
// scope[list.iterator + 'SearchSelectValue'] = { value: 1 };
|
|
// }
|
|
// scope.search(list.iterator, null, true);
|
|
};
|
|
}])
|
|
|
|
.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList',
|
|
function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList) {
|
|
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() {
|
|
// @issue: OLD SEARCH
|
|
// var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/';
|
|
|
|
GenerateList.inject(GroupList, {
|
|
mode: 'lookup',
|
|
id: 'copy-host-select-container',
|
|
scope: scope
|
|
//,
|
|
//instructions: instructions
|
|
});
|
|
|
|
// @issue: OLD SEARCH
|
|
// 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
|
|
}
|
|
|
|
// @issue: OLD SEARCH
|
|
// scope.searchCleanup();
|
|
|
|
// @issue: OLD SEARCH
|
|
// 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) {
|
|
scope[list.name][i].success_class = 'success';
|
|
}
|
|
else {
|
|
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) {
|
|
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
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
};
|
|
}]);
|