diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index ba7a6bc53e..f3bb8ac9bc 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -196,11 +196,10 @@ function HomeHosts ($location, $routeParams, HomeHostList, GenerateList, Process // Process search params if ($routeParams['name']) { - scope[list.iterator + 'InputDisable'] = false; - scope[list.iterator + 'SearchValue'] = $routeParams['name']; - scope[list.iterator + 'SearchField'] = 'name'; - scope[list.iterator + 'SearchFieldLabel'] = list.fields['name'].label; - scope[list.iterator + 'SearchSelectValue'] = null; + scope[HomeHostList.iterator + 'InputDisable'] = false; + scope[HomeHostListiterator + 'SearchValue'] = $routeParams['name']; + scope[HomeHostList.iterator + 'SearchField'] = 'name'; + scope[lHomeHostList.iterator + 'SearchFieldLabel'] = list.fields['name'].label; } if ($routeParams['has_active_failures']) { diff --git a/awx/ui/static/js/forms/ActivityDetail.js b/awx/ui/static/js/forms/ActivityDetail.js index e6a6ba16d5..b5cadc53d3 100644 --- a/awx/ui/static/js/forms/ActivityDetail.js +++ b/awx/ui/static/js/forms/ActivityDetail.js @@ -20,6 +20,11 @@ angular.module('ActivityDetailDefinition', []) type: 'text', readonly: true }, + id: { + label: 'Event ID', + type: 'text', + readonly: true + }, operation: { label: 'Operation', type: 'text', diff --git a/awx/ui/static/js/helpers/refresh.js b/awx/ui/static/js/helpers/refresh.js index 0a6c29d0c1..5cbc3b95be 100644 --- a/awx/ui/static/js/helpers/refresh.js +++ b/awx/ui/static/js/helpers/refresh.js @@ -33,11 +33,16 @@ angular.module('RefreshHelper', ['RestServices', 'Utilities']) scope[iterator + 'PageCount'] = Math.ceil((data.count / scope[iterator + 'PageSize'])); scope[iterator + 'SearchSpin'] = false; scope[iterator + 'Loading'] = false; + for (var i=1; i <= 3; i++) { + var modifier = (i == 1) ? '' : i; + scope[iterator + 'HoldInput' + modifier] = false; + } scope[set] = data['results']; scope.$emit('PostRefresh'); }) .error ( function(data, status, headers, config) { scope[iterator + 'SearchSpin'] = false; + scope[iterator + 'HoldInput'] = false; ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status }); }); diff --git a/awx/ui/static/js/helpers/search.js b/awx/ui/static/js/helpers/search.js index ecb6775ee0..a638c524c3 100644 --- a/awx/ui/static/js/helpers/search.js +++ b/awx/ui/static/js/helpers/search.js @@ -16,7 +16,8 @@ */ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) - .factory('SearchInit', ['Alert', 'Rest', 'Refresh', '$location', function(Alert, Rest, Refresh, $location) { + .factory('SearchInit', ['Alert', 'Rest', 'Refresh', '$location', 'GetBasePath', 'Empty', '$timeout', + function(Alert, Rest, Refresh, $location, GetBasePath, Empty, $timeout) { return function(params) { var scope = params.scope; @@ -25,227 +26,355 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) var list = params.list; var iterator = (params.iterator) ? params.iterator : list.iterator; var sort_order; + + if (scope.searchTimer) { + $timeout.cancel(scope.searchTimer); + } - function setDefaults() { + function setDefaults(widget) { // Set default values + var modifier = (widget == undefined || widget == 1) ? '' : widget; + scope[iterator + 'SearchField' + modifier] = ''; + scope[iterator + 'SearchFieldLabel' + modifier] = ''; for (fld in list.fields) { - if (list.fields[fld].key) { - if (list.fields[fld].sourceModel) { - var fka = list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; - sort_order = (list.fields[fld].desc) ? '-' + fka : fka; - } - else { - sort_order = (list.fields[fld].desc) ? '-' + fld : fld; - } - if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) { - scope[iterator + 'SearchField'] = fld; - scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label; - } - break; + if (list.fields[fld].searchWidget == undefined && widget == 1 || + list.fields[fld].searchWidget == widget) { + if (list.fields[fld].key) { + if (list.fields[fld].sourceModel) { + var fka = list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; + sort_order = (list.fields[fld].desc) ? '-' + fka : fka; + } + else { + sort_order = (list.fields[fld].desc) ? '-' + fld : fld; + } + if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) { + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; + } + break; + } } } - if (!scope[iterator + 'SearchField']) { - // A field marked as key may not be 'searchable' + if (Empty(scope[iterator + 'SearchField' + modifier])) { + // A field marked as key may not be 'searchable'. Find the first searchable field. for (fld in list.fields) { - if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) { - scope[iterator + 'SearchField'] = fld; - scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label; - break; + if (list.fields[fld].searchWidget == undefined && widget == 1 || + list.fields[fld].searchWidget == widget) { + if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) { + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; + break; + } } } } - scope[iterator + 'SearchType'] = 'icontains'; - scope[iterator + 'SearchTypeLabel'] = 'Contains'; - scope[iterator + 'SearchParams'] = ''; - scope[iterator + 'SearchValue'] = ''; - scope[iterator + 'SelectShow'] = false; // show/hide the Select - scope[iterator + 'HideSearchType'] = false; - scope[iterator + 'InputDisable'] = false; - scope[iterator + 'ExtraParms'] = ''; + scope[iterator + 'SearchType' + modifier] = 'icontains'; + scope[iterator + 'SearchTypeLabel' + modifier] = 'Contains'; + scope[iterator + 'SearchParams' + modifier] = ''; + scope[iterator + 'SearchValue' + modifier] = ''; + scope[iterator + 'SelectShow' + modifier] = false; // show/hide the Select + scope[iterator + 'HideSearchType' + modifier] = false; + scope[iterator + 'InputDisable' + modifier] = false; + scope[iterator + 'ExtraParms' + modifier] = ''; + + scope[iterator + 'SearchPlaceholder' + modifier] = + (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) ? + list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder : 'Search'; + + scope[iterator + 'InputDisable' + modifier] = + (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject == 'all') ? true : false; - var f = scope[iterator + 'SearchField'] - if (list.fields[f].searchType && ( list.fields[f].searchType == 'boolean' - || list.fields[f].searchType == 'select')) { - scope[iterator + 'SelectShow'] = true; - scope[iterator + 'SearchSelectOpts'] = list.fields[f].searchOptions; - } - if (list.fields[f].searchType && list.fields[f].searchType == 'int') { - scope[iterator + 'HideSearchType'] = true; - } - if (list.fields[f].searchType && list.fields[f].searchType == 'gtzero') { - scope[iterator + "InputHide"] = true; + var f = scope[iterator + 'SearchField' + modifier]; + if (list.fields[f]) { + if ( list.fields[f].searchType && (list.fields[f].searchType == 'boolean' + || list.fields[f].searchType == 'select') ) { + scope[iterator + 'SelectShow' + modifier] = true; + scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[f].searchOptions; + } + if (list.fields[f].searchType && list.fields[f].searchType == 'int') { + scope[iterator + 'HideSearchType' + modifier] = true; + } + if (list.fields[f].searchType && list.fields[f].searchType == 'gtzero') { + scope[iterator + 'InputHide' + modifier] = true; + } } } - setDefaults(); + for (var i=1; i <= 3; i++) { + var modifier = (i == 1) ? '' : i; + if ( $('#search-widget-container' + modifier) ) { + setDefaults(i); + } + } // Functions to handle search widget changes - scope.setSearchField = function(iterator, fld, label) { - scope[iterator + 'SearchFieldLabel'] = label; - scope[iterator + 'SearchField'] = fld; - scope[iterator + 'SearchValue'] = ''; - scope[iterator + 'SelectShow'] = false; - scope[iterator + 'HideSearchType'] = false; - scope[iterator + 'InputHide'] = false; - scope[iterator + 'InputDisable'] = false; - scope[iterator + 'SearchType'] = 'icontains'; + scope.setSearchField = function(iterator, fld, label, widget) { + + var modifier = (widget == undefined || widget == 1) ? '' : widget; + scope[iterator + 'SearchFieldLabel' + modifier] = label; + scope[iterator + 'SearchField' + modifier] = fld; + scope[iterator + 'SearchValue' + modifier] = ''; + scope[iterator + 'SelectShow' + modifier] = false; + scope[iterator + 'HideSearchType' + modifier] = false; + scope[iterator + 'InputHide' + modifier] = false; + scope[iterator + 'SearchType' + modifier] = 'icontains'; + scope[iterator + 'SearchPlaceholder' + modifier] = (list.fields[fld].searchPlaceholder) ? list.fields[fld].searchPlaceholder : 'Search'; + scope[iterator + 'InputDisable' + modifier] = (list.fields[fld].searchObject == 'all') ? true : false; if (list.fields[fld].searchType && list.fields[fld].searchType == 'gtzero') { - scope[iterator + "InputDisable"] = true; + scope[iterator + "InputDisable" + modifier] = true; } else if (list.fields[fld].searchSingleValue){ // Query a specific attribute for one specific value // searchSingleValue: true // searchType: 'boolean|int|etc.' // searchValue: < value to match for boolean use 'true'|'false' > - scope[iterator + 'InputDisable'] = true; - scope[iterator + "SearchValue"] = list.fields[fld].searchValue; + scope[iterator + 'InputDisable' + modifier] = true; + scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; // For boolean type, SearchValue must be an object if (list.fields[fld].searchType == 'boolean' && list.fields[fld].searchValue == 'true') { - scope[iterator + "SearchSelectValue"] = { value: 1 }; + scope[iterator + "SearchSelectValue" + modifier] = { value: 1 }; } else if (list.fields[fld].searchType == 'boolean' && list.fields[fld].searchValue == 'false') { - scope[iterator + "SearchSelectValue"] = { value: 0 }; + scope[iterator + "SearchSelectValue" + modifier] = { value: 0 }; } else { - scope[iterator + "SearchSelectValue"] = { value: list.fields[fld].searchValue }; + scope[iterator + "SearchSelectValue" + modifier] = { value: list.fields[fld].searchValue }; } } else if (list.fields[fld].searchType == 'in') { - scope[iterator + "SearchType"] = 'in'; - scope[iterator + "SearchValue"] = list.fields[fld].searchValue; - scope[iterator + "InputDisable"] = true; + scope[iterator + "SearchType" + modifier] = 'in'; + scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; + scope[iterator + "InputDisable" + modifier] = true; } else if (list.fields[fld].searchType && (list.fields[fld].searchType == 'boolean' - || list.fields[fld].searchType == 'select')) { - scope[iterator + 'SelectShow'] = true; - scope[iterator + 'SearchSelectOpts'] = list.fields[fld].searchOptions; + || list.fields[fld].searchType == 'select' || list.fields[fld].searchType == 'select_or')) { + scope[iterator + 'SelectShow' + modifier] = true; + scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[fld].searchOptions; } else if (list.fields[fld].searchType && list.fields[fld].searchType == 'int') { - scope[iterator + 'HideSearchType'] = true; + scope[iterator + 'HideSearchType' + modifier] = true; } else if (list.fields[fld].searchType && list.fields[fld].searchType == 'isnull') { - scope[iterator + 'SearchType'] = 'isnull'; - scope[iterator + 'InputDisable'] = true; - scope[iterator + 'SearchValue'] = 'true'; + scope[iterator + 'SearchType' + modifier] = 'isnull'; + scope[iterator + 'InputDisable' + modifier] = true; + scope[iterator + 'SearchValue' + modifier] = 'true'; } scope.search(iterator); } - scope.resetSearch = function(iterator) { + scope.resetSearch = function(iterator, widget) { // Respdond to click of reset button - setDefaults(); + setDefaults(widget); // Force removal of search keys from the URL window.location = '/#' + $location.path(); + scope.search(iterator); } - scope.setSearchType = function(iterator, type, label) { - scope[iterator + 'SearchTypeLabel'] = label; - scope[iterator + 'SearchType'] = type; - scope.search(iterator); + //scope.setSearchType = function(iterator, type, label) { + // scope[iterator + 'SearchTypeLabel'] = label; + // scope[iterator + 'SearchType'] = type; + // scope.search(iterator); + // } + + + if (scope.removeDoSearch) { + scope.removeDoSearch(); + } + scope.removeDoSearch = scope.$on('doSearch', function(e, iterator, page, load, spin) { + // + // Execute the search + // + scope[iterator + 'SearchSpin'] = (spin == undefined || spin == true) ? true : false; + scope[iterator + 'Loading'] = (load == undefined || load == true) ? true : false; + var url = defaultUrl; + + //finalize and execute the query + scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; + if (/\/$/.test(url)) { + url += '?' + scope[iterator + 'SearchParams']; + } + else { + url += '&' + scope[iterator + 'SearchParams']; + } + url = url.replace(/\&\&/,'&'); + url += (scope[iterator + 'PageSize']) ? '&page_size=' + scope[iterator + 'PageSize'] : ""; + if (page) { + url += '&page=' + page; + } + if (scope[iterator + 'ExtraParms']) { + url += scope[iterator + 'ExtraParms']; + } + Refresh({ scope: scope, set: set, iterator: iterator, url: url }); + }); + + + if (scope.removePrepareSearch) { + scope.removePrepareSearch(); + } + scope.removePrepareSearch = scope.$on('prepareSearch', function(e, iterator, page, load, spin) { + // + // Start build the search key/value pairs. This will process the first search widget, if the + // selected field is an object type (used on activity stream). + // + scope[iterator + 'HoldInput'] = true; + scope[iterator + 'SearchParams'] = ''; + if (list.fields[scope[iterator + 'SearchField']].searchObject && + list.fields[scope[iterator + 'SearchField']].searchObject !== 'all') { + //This is specifically for activity stream. We need to identify which type of object is being searched + //and then provide a list of PK values corresponding to the list of objects the user is interested in. + var objs = list.fields[scope[iterator + 'SearchField']].searchObject; + var o = (objs == 'inventories') ? 'inventory' : objs.replace(/s$/,''); + scope[iterator + 'SearchParams'] = 'or__object1=' + o + '&or__object2=' + o; + if (scope[iterator + 'SearchValue']) { + var objUrl = GetBasePath('base') + objs + '/?name__icontains=' + scope[iterator + 'SearchValue']; + Rest.setUrl(objUrl); + Rest.get() + .success( function(data, status, headers, config) { + var list=''; + for (var i=0; i < data.results.length; i++) { + list += "," + data.results[i].id; + } + list = list.replace(/^\,/,''); + if (!Empty(list)) { + scope[iterator + 'SearchParams'] += '&or__object1_id__in=' + list + '&or__object2_id__in=' + list; + } + //scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + escape(sort_order) : ""; + scope.$emit('prepareSearch2', iterator, page, load, spin, 2); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Retrieving list of ' + obj + ' where name contains: ' + scope[iterator + 'SearchValue'] + + ' GET returned status: ' + status }); + }); + } + else { + scope.$emit('prepareSearch2', iterator, page, load, spin, 2); + } + } + else { + scope.$emit('prepareSearch2', iterator, page, load, spin, 1); + } + }); + + if (scope.removePrepareSearch2) { + scope.removePrepareSearch2(); + } + scope.removePrepareSearch2 = scope.$on('prepareSearch2', function(e, iterator, page, load, spin, startingWidget) { + // Continue building the search by examining the remaining search widgets. If we're looking at activity_stream, + // there's more than one. + for (var i=startingWidget; i <= 3; i++) { + var modifier = (i == 1) ? '' : i; + scope[iterator + 'HoldInput' + modifier] = true; + if ( $('#search-widget-container' + modifier) ) { + // if the search widget exists, add its parameters to the query + if ( (!scope[iterator + 'SelectShow' + modifier] && !Empty(scope[iterator + 'SearchValue' + modifier])) || + (scope[iterator + 'SelectShow' + modifier] && scope[iterator + 'SearchSelectValue' + modifier]) || + (list.fields[scope[iterator + 'SearchField' + modifier]] && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'gtzero') ) { + if (list.fields[scope[iterator + 'SearchField' + modifier]].searchField) { + scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField' + modifier]].searchField + '__'; + } + else if (list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel) { + // handle fields whose source is a related model e.g. inventories.organization + scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel + '__' + + list.fields[scope[iterator + 'SearchField' + modifier]].sourceField + '__'; + } + else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'select') && + (scope[iterator + 'SearchSelectValue' + modifier].value == '' || + scope[iterator + 'SearchSelectValue' + modifier].value == null) ) { + scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField' + modifier]; + } + else { + scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField' + modifier] + '__'; + } + + if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'int' || + list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'boolean' ) ) { + scope[iterator + 'SearchParams'] += 'int='; + } + else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'gtzero' ) { + scope[iterator + 'SearchParams'] += 'gt=0'; + } + else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'select') && + (scope[iterator + 'SearchSelectValue' + modifier].value == '' || + scope[iterator + 'SearchSelectValue' + modifier].value == null) ) { + scope[iterator + 'SearchParams'] += 'iexact='; + } + else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'or')) ) { + scope[iterator + 'SearchParams'] = ''; //start over + var val = scope[iterator + 'SearchValue' + modifier]; + for (var k=0; k < list.fields[scope[iterator + 'SearchField' + modifier]].searchFields.length; k++) { + scope[iterator + 'SearchParams'] += '&or__' + + list.fields[scope[iterator + 'SearchField' + modifier]].searchFields[k] + + '__icontains=' + escape(val); + } + scope[iterator + 'SearchParams'].replace(/^\&/,''); + } + else { + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType' + modifier] + '='; + } + + if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'boolean' + || list.fields[scope[iterator + 'SearchField' + modifier]].searchType == 'select') ) { + scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue' + modifier].value; + } + else { + if ( (!list.fields[scope[iterator + 'SearchField' + modifier]].searchType) || + (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && + list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'or') ) { + scope[iterator + 'SearchParams'] += escape(scope[iterator + 'SearchValue' + modifier]); + } + } + } + } + } + + if ( (iterator == 'inventory' && scope.inventoryFailureFilter) || + (iterator == 'host' && scope.hostFailureFilter) ) { + //Things that bypass the search widget. Should go back and add a second widget possibly on + //inventory pages and eliminate this + scope[iterator + 'SearchParams'] += '&has_active_failures=true'; + } + + if (sort_order) { + scope[iterator + 'SearchParams'] += (scope[iterator + 'SearchParams']) ? '&' : ''; + scope[iterator + 'SearchParams'] += 'order_by=' + escape(sort_order); + } + + scope.$emit('doSearch', iterator, page, load, spin); + }); + + scope.startSearch = function(iterator) { + //Called on each keydown event for seachValue field. Using a timer + //to prevent executing a search until user is finished typing. + if (scope.searchTimer) { + $timeout.cancel(scope.searchTimer); + } + scope.searchTimer = $timeout( + function() { + scope.$emit('prepareSearch', iterator); + } + , 1000); } scope.search = function(iterator, page, load, spin) { + // Called to initiate a searh. // Page is optional. Added to accomodate back function on Job Events detail. // Spin optional -set to false if spin not desired. // Load optional -set to false if loading message not desired - - scope[iterator + 'SearchSpin'] = (spin == undefined || spin == true) ? true : false; - scope[iterator + 'Loading'] = (load == undefined || load == true) ? true : false; - scope[iterator + 'SearchParms'] = ''; - var url = defaultUrl; - - if ( (scope[iterator + 'SelectShow'] == false && scope[iterator + 'SearchValue'] != '' && scope[iterator + 'SearchValue'] != undefined) || - (scope[iterator + 'SelectShow'] && scope[iterator + 'SearchSelectValue']) || - (list.fields[scope[iterator + 'SearchField']].searchType && list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero') ) { - - if (list.fields[scope[iterator + 'SearchField']].searchField) { - scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].searchField + '__'; - } - else if (list.fields[scope[iterator + 'SearchField']].sourceModel) { - // handle fields whose source is a related model e.g. inventories.organization - scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].sourceModel + '__' + - list.fields[scope[iterator + 'SearchField']].sourceField + '__'; - } - else if ( (list.fields[scope[iterator + 'SearchField']].searchType == 'select') && - (scope[iterator + 'SearchSelectValue'].value == '' || - scope[iterator + 'SearchSelectValue'].value == null) ) { - scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField']; - } - else { - scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__'; - } - - if ( list.fields[scope[iterator + 'SearchField']].searchType && - (list.fields[scope[iterator + 'SearchField']].searchType == 'int' || - list.fields[scope[iterator + 'SearchField']].searchType == 'boolean' ) ) { - scope[iterator + 'SearchParams'] += 'int='; - } - else if ( list.fields[scope[iterator + 'SearchField']].searchType && - list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero' ) { - scope[iterator + 'SearchParams'] += 'gt=0'; - } - else if ( (list.fields[scope[iterator + 'SearchField']].searchType == 'select') && - (scope[iterator + 'SearchSelectValue'].value == '' || - scope[iterator + 'SearchSelectValue'].value == null) ) { - scope[iterator + 'SearchParams'] += 'iexact='; - } - else if ( (list.fields[scope[iterator + 'SearchField']].searchType && - (list.fields[scope[iterator + 'SearchField']].searchType == 'or')) ) { - scope[iterator + 'SearchParams'] = ''; //start over - for (var k=0; k < list.fields[scope[iterator + 'SearchField']].searchFields.length; k++) { - scope[iterator + 'SearchParams'] += '&or__' + - list.fields[scope[iterator + 'SearchField']].searchFields[k] + - '__icontains=' + escape(scope[iterator + 'SearchValue']); - } - scope[iterator + 'SearchParams'].replace(/^\&/,''); - } - else { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType'] + '='; - } - - if ( list.fields[scope[iterator + 'SearchField']].searchType && - (list.fields[scope[iterator + 'SearchField']].searchType == 'boolean' - || list.fields[scope[iterator + 'SearchField']].searchType == 'select') ) { - scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value; - } - else { - if ( (list.fields[scope[iterator + 'SearchField']].searchType && - (list.fields[scope[iterator + 'SearchField']].searchType !== 'or')) ) { - scope[iterator + 'SearchParams'] += escape(scope[iterator + 'SearchValue']); - } - } - scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + escape(sort_order) : ''; - } - else { - scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + escape(sort_order) : ""; - } - - if ( (iterator == 'inventory' && scope.inventoryFailureFilter) || - (iterator == 'host' && scope.hostFailureFilter) ) { - scope[iterator + 'SearchParams'] += '&has_active_failures=true'; - } - - scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; - if (/\/$/.test(url)) { - url += '?' + scope[iterator + 'SearchParams']; - } - else { - url += '&' + scope[iterator + 'SearchParams']; - } - url = url.replace(/\&\&/,'&'); - url += (scope[iterator + 'PageSize']) ? '&page_size=' + scope[iterator + 'PageSize'] : ""; - if (page) { - url += '&page=' + page; - } - if (scope[iterator + 'ExtraParms']) { - url += scope[iterator + 'ExtraParms']; - } - Refresh({ scope: scope, set: set, iterator: iterator, url: url }); + scope.$emit('prepareSearch', iterator, page, load, spin); } + scope.sort = function(fld) { // reset sort icons back to 'icon-sort' on all columns // except the one clicked diff --git a/awx/ui/static/js/lists/Streams.js b/awx/ui/static/js/lists/Streams.js index 5fd025cd08..7ed2bf2d06 100644 --- a/awx/ui/static/js/lists/Streams.js +++ b/awx/ui/static/js/lists/Streams.js @@ -17,6 +17,8 @@ angular.module('StreamListDefinition', []) index: false, hover: true, "class": "table-condensed", + searchWidgetLabel: 'Object', + searchWidgetLabel2: 'Modified by', fields: { timestamp: { @@ -32,20 +34,16 @@ angular.module('StreamListDefinition', []) sourceModel: 'user', sourceField: 'username', awToolTip: "\{\{ userToolTip \}\}", - dataPlacement: 'top' + dataPlacement: 'top', + searchPlaceholder: 'Username', + searchWidget: 2 }, objects: { label: 'Objects', ngBindHtml: 'activity.objects', - sortField: "object1__name,object2__name", + nosort: true, searchable: false }, - object_name: { - label: 'Object name', - searchOnly: true, - searchType: 'or', - searchFields: ['object1__name', 'object2__name'] - }, description: { label: 'Description', ngBindHtml: 'activity.description', @@ -53,11 +51,68 @@ angular.module('StreamListDefinition', []) searchable: false }, system_event: { - label: 'System event?', + label: 'System', searchOnly: true, searchType: 'isnull', sourceModel: 'user', - sourceField: 'username' + sourceField: 'username', + searchWidget: 2 + }, + // The following fields exist to forces loading each type of object into the search + // dropdown + all_objects: { + label: 'All', + searchOnly: true, + searchObject: 'all', + searchPlaceholder: ' ' + }, + credential_search: { + label: 'Credential', + searchOnly: true, + searchObject: 'credentials', + searchPlaceholder: 'Credential name' + }, + group_search: { + label: 'Group', + searchOnly: true, + searchObject: 'groups', + searchPlaceholder: 'Group name' + }, + host_search: { + label: 'Host', + searchOnly: true, + searchObject: 'hosts', + searchPlaceholder: 'Host name' + }, + inventory_search: { + label: 'Inventory', + searchOnly: true, + searchObject: 'inventories', + searchPlaceholder: 'Inventory name' + }, + job_template_search: { + label: 'Job Template', + searchOnly: true, + searchObject: 'job_templates', + searchPlaceholder: 'Job template name' + }, + organization_search: { + label: 'Organization', + searchOnly: true, + searchObject: 'organizations', + searchPlaceholder: 'Organization name' + }, + project_search: { + label: 'Project', + searchOnly: true, + searchObject: 'projects', + searchPlaceholder: 'Project name' + }, + user_search: { + label: 'User', + searchOnly: true, + searchObject: 'users', + searchPlaceholder: 'Username' } }, diff --git a/awx/ui/static/js/widgets/Stream.js b/awx/ui/static/js/widgets/Stream.js index 498fef4a3b..32bd4f33ed 100644 --- a/awx/ui/static/js/widgets/Stream.js +++ b/awx/ui/static/js/widgets/Stream.js @@ -174,9 +174,10 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti }]) .factory('Stream', ['$rootScope', '$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'StreamList', 'SearchInit', - 'PaginateInit', 'GenerateList', 'FormatDate', 'ShowStream', 'HideStream', 'BuildDescription', 'FixUrl', 'ShowDetail', + 'PaginateInit', 'GenerateList', 'FormatDate', 'ShowStream', 'HideStream', 'BuildDescription', 'FixUrl', 'BuildUrl', + 'ShowDetail', function($rootScope, $location, Rest, GetBasePath, ProcessErrors, Wait, StreamList, SearchInit, PaginateInit, GenerateList, - FormatDate, ShowStream, HideStream, BuildDescription, FixUrl, ShowDetail) { + FormatDate, ShowStream, HideStream, BuildDescription, FixUrl, BuildUrl, ShowDetail) { return function(params) { var list = StreamList; @@ -188,7 +189,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti var type = (base == 'inventories') ? 'inventory' : base.replace(/s$/,''); defaultUrl += '?or__object1=' + type + '&or__object2=' + type; } - + // Push the current page onto browser histor. If user clicks back button, restore current page without // stream widget // window.history.pushState({}, "AnsibleWorks AWX", $location.path()); @@ -203,7 +204,8 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti mode: 'edit', id: 'stream-content', breadCrumbs: true, - searchSize: 'col-lg-4' + searchSize: 'col-lg-3', + secondWidget: true }); scope.closeStream = function() { @@ -241,7 +243,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti var deleted = /^\_delete/; if (scope['activities'][i].summary_fields.object1) { if ( !deleted.test(scope['activities'][i].summary_fields.object1.name) ) { - href = FixUrl(scope['activities'][i].related.object1); + href = BuildUrl(scope['activities'][i].summary_fields.object1); scope['activities'][i].objects = "" + scope['activities'][i].summary_fields.object1.name + ""; } else { @@ -250,7 +252,7 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti } if (scope['activities'][i].summary_fields.object2) { if ( !deleted.test(scope['activities'][i].summary_fields.object2.name) ) { - href = FixUrl(scope['activities'][i].related.object2); + href = BuildUrl(scope['activities'][i].summary_fields.object2); scope['activities'][i].objects += ", " + scope['activities'][i].summary_fields.object2.name + ""; } else { @@ -264,7 +266,17 @@ angular.module('StreamWidget', ['RestServices', 'Utilities', 'StreamListDefiniti // Initialize search and paginate pieces and load data SearchInit({ scope: scope, set: list.name, list: list, url: defaultUrl }); PaginateInit({ scope: scope, list: list, url: defaultUrl }); - scope.search(list.iterator); + scope.search(list.iterator); + + /* + scope.$watch(list.iterator + 'SearchField', function(newVal, oldVal) { + console.log('newVal: ' + newVal); + html += "" + html += "\n"; + });*/ + } }]); \ No newline at end of file diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index a41558c42e..89c362dfe1 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -151,6 +151,20 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Hos } } }) + + // awPlaceholder: Dynamic placeholder set to a scope variable you want watched. + // Value will be place in field placeholder attribute. + .directive('awPlaceholder', [ function() { + return { + require: 'ngModel', + link: function(scope, elm, attrs, ctrl) { + $(elm).attr('placeholder', scope[attrs.awPlaceholder]); + scope.$watch(attrs.awPlaceholder, function(newVal, oldVal) { + $(elm).attr('placeholder',newVal); + }); + } + } + }]) // lookup Validate lookup value against API // diff --git a/awx/ui/static/lib/ansible/generator-helpers.js b/awx/ui/static/lib/ansible/generator-helpers.js index 1c46a9f2f8..0b61efb61c 100644 --- a/awx/ui/static/lib/ansible/generator-helpers.js +++ b/awx/ui/static/lib/ansible/generator-helpers.js @@ -477,20 +477,20 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) var iterator = params.iterator; var form = params.template; var useMini = params.mini; - var label = (params.label) ? params.label : null; + //var label = (params.label) ? params.label : null; var html= ''; + var secondWidget = params.secondWidget; html += "
\n"; html += "
\n"; - html += (label) ? "" : ""; + html += "\" id=\"search-widget-container\">\n"; + html += (form.searchWidgetLabel) ? "" : ""; html += "
\n"; html += "
\n"; - // Use standard button on mobile html += "\n"; - - // Use link and hover activation on desktop - //html += ""; - //html += "\n"; - //html += "\n"; - //html += "\n"; - + html += "
    \n"; for ( var fld in form.fields) { - if (form.fields[fld].searchable == undefined || form.fields[fld].searchable == true) { - html += "
  • " + - form.fields[fld].searchLabel + "
  • \n"; - } - else { - html += form.fields[fld].label.replace(/\/g,' ') + "')\">" + - form.fields[fld].label.replace(/\/g,' ') + "\n"; - } - } + if ( (form.fields[fld].searchable == undefined || form.fields[fld].searchable == true) + && (form.fields[fld].searchWidget == undefined || form.fields[fld].searchWidget == 1) ) { + html += "
  • " + + form.fields[fld].searchLabel + "
  • \n"; + } + else { + html += form.fields[fld].label.replace(/\/g,' ') + "')\">" + + form.fields[fld].label.replace(/\/g,' ') + "\n"; + } + } } html += "
\n"; html += "
\n"; @@ -529,37 +524,71 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers']) html += "\">\n"; html += "\n"; - - /* - html += "
\n"; - html += "\n"; - html += "\n"; - html += "
\n"; - */ - + html += "\" ng-model=\"" + iterator + "SearchValue\" ng-keydown=\"startSearch('" + iterator + "')\" " + + "aw-placeholder=\"" + iterator + "SearchPlaceholder\" type=\"text\" ng-disabled=\"" + iterator + "InputDisable || " + iterator + + "HoldInput\">\n"; // Reset button html += "
\n"; - html += "\n"; html += "
\n"; html += "
\n"; - html += "
\n"; + + // Search Widget 2 + // Used on activity stream. Set 'searchWidget2: true' on fields to be included. + if (secondWidget) { + html += "
\n"; + html += (form.searchWidgetLabel2) ? "" : ""; + html += "
\n"; + html += "
\n"; + html += "\n"; + + html += "
    \n"; + for ( var fld in form.fields) { + if ( (form.fields[fld].searchable == undefined || form.fields[fld].searchable == true) + && form.fields[fld].searchWidget == 2 ) { + html += "
  • " + + form.fields[fld].searchLabel + "
  • \n"; + } + else { + html += form.fields[fld].label.replace(/\/g,' ') + "', 2)\">" + + form.fields[fld].label.replace(/\/g,' ') + "\n"; + } + } + } + html += "
\n"; + html += "
\n"; + + html += "\n"; + + // Reset button + html += "
\n"; + html += "\n"; + html += "
\n"; + html += "
\n"; + html += "
\n"; + } + + // Spinner html += "
\n"; + return html; } diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index 6ee4d26908..b0d2fcd659 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -194,7 +194,8 @@ angular.module('ListGenerator', ['GeneratorHelpers']) */ if (options.searchSize) { - html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: options.searchSize }); + html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: options.searchSize, + secondWidget: options.secondWidget }); } else if (options.mode == 'summary') { html += SearchWidget({ iterator: list.iterator, template: list, mini: true , size: 'col-lg-6' }); @@ -214,6 +215,7 @@ angular.module('ListGenerator', ['GeneratorHelpers']) if (options.searchSize) { // User supplied searchSize, calc the remaining var size = parseInt(options.searchSize.replace(/([A-Z]|[a-z]|\-)/g,'')); + size += (options.secondWidget) ? 3 : 0; html += 'col-lg-' + (11 - size); } else if (options.mode == 'summary') {