mirror of
https://github.com/ansible/awx.git
synced 2026-05-17 14:27:42 -02:30
initial search integration
This commit is contained in:
@@ -150,3 +150,52 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search ---------------------------------------------------------------------------------
|
||||||
|
@at-jobz-top-search-key: @at-space-2x;
|
||||||
|
@at-jobz-bottom-search-key: @at-space-3x;
|
||||||
|
|
||||||
|
.jobz-searchKeyPaneContainer {
|
||||||
|
margin-top: @at-jobz-top-search-key;
|
||||||
|
margin-bottom: @at-jobz-bottom-search-key;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobz-searchKeyPane {
|
||||||
|
// background-color: @at-gray-f6;
|
||||||
|
background-color: @login-notice-bg;
|
||||||
|
color: @login-notice-text;
|
||||||
|
border-radius: @at-border-radius;
|
||||||
|
border: 1px solid @at-gray-b7;
|
||||||
|
// color: @at-gray-848992;
|
||||||
|
padding: 6px @at-padding-input 6px @at-padding-input;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobz-searchClearAll {
|
||||||
|
font-size: 10px;
|
||||||
|
padding-bottom: @at-space;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobz-Button-searchKey {
|
||||||
|
.at-mixin-Button();
|
||||||
|
|
||||||
|
background-color: @at-blue;
|
||||||
|
border-color: at-color-button-border-default;
|
||||||
|
color: @at-white;
|
||||||
|
|
||||||
|
&:hover, &:active {
|
||||||
|
color: @at-white;
|
||||||
|
background-color: @at-blue-hover;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: @at-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobz-tagz {
|
||||||
|
margin-top: @at-space;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ let page;
|
|||||||
let render;
|
let render;
|
||||||
let scroll;
|
let scroll;
|
||||||
let resource;
|
let resource;
|
||||||
|
let $state;
|
||||||
|
let qs;
|
||||||
|
|
||||||
let chain;
|
let chain;
|
||||||
|
|
||||||
@@ -19,7 +21,9 @@ function JobsIndexController (
|
|||||||
_render_,
|
_render_,
|
||||||
_$scope_,
|
_$scope_,
|
||||||
_$compile_,
|
_$compile_,
|
||||||
_$q_
|
_$q_,
|
||||||
|
_$state_,
|
||||||
|
_qs_,
|
||||||
) {
|
) {
|
||||||
vm = this || {};
|
vm = this || {};
|
||||||
|
|
||||||
@@ -59,6 +63,23 @@ function JobsIndexController (
|
|||||||
const stream = false; // TODO: Set in route
|
const stream = false; // TODO: Set in route
|
||||||
|
|
||||||
chain = $q.resolve();
|
chain = $q.resolve();
|
||||||
|
|
||||||
|
// search
|
||||||
|
$state = _$state_;
|
||||||
|
qs = _qs_;
|
||||||
|
|
||||||
|
vm.searchValue = '';
|
||||||
|
vm.searchRejected = null;
|
||||||
|
vm.searchKey = false;
|
||||||
|
vm.searchKeyExamples = searchKeyExamples;
|
||||||
|
vm.searchKeyFields = searchKeyFields;
|
||||||
|
|
||||||
|
vm.clearSearch = clearSearch;
|
||||||
|
vm.search = search;
|
||||||
|
vm.toggleSearchKey = toggleSearchKey;
|
||||||
|
vm.removeSearchTag = removeSearchTag;
|
||||||
|
vm.searchTags = getSearchTags(getCurrentQueryset());
|
||||||
|
|
||||||
render.requestAnimationFrame(() => init());
|
render.requestAnimationFrame(() => init());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +339,62 @@ function toggle (uuid, menu) {
|
|||||||
|
|
||||||
lines.removeClass('hidden');
|
lines.removeClass('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Search
|
||||||
|
//
|
||||||
|
|
||||||
|
const searchReloadOptions = { reload: true, inherit: false };
|
||||||
|
const searchKeyExamples = ['id:>1', 'task:set', 'created:>=2000-01-01'];
|
||||||
|
const searchKeyFields = ['changed', 'failed', 'host_name', 'stdout', 'task', 'role', 'playbook', 'play'];
|
||||||
|
|
||||||
|
function toggleSearchKey () {
|
||||||
|
vm.searchKey = !vm.searchKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentQueryset() {
|
||||||
|
const { job_event_search } = $state.params;
|
||||||
|
|
||||||
|
return qs.decodeArr(job_event_search);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchTags (queryset) {
|
||||||
|
return qs.createSearchTagsFromQueryset(queryset)
|
||||||
|
.filter(tag => !tag.startsWith('event'))
|
||||||
|
.filter(tag => !tag.startsWith('-event'))
|
||||||
|
.filter(tag => !tag.startsWith('page_size'))
|
||||||
|
.filter(tag => !tag.startsWith('order_by'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSearchTag (index) {
|
||||||
|
const searchTerm = vm.searchTags[index];
|
||||||
|
|
||||||
|
const currentQueryset = getCurrentQueryset();
|
||||||
|
const modifiedQueryset = qs.removeTermsFromQueryset(currentQueryset, searchTerm);
|
||||||
|
|
||||||
|
vm.searchTags = getSearchTags(modifiedQueryset);
|
||||||
|
|
||||||
|
$state.params.job_event_search = qs.encodeArr(modifiedQueryset);
|
||||||
|
$state.transitionTo($state.current, $state.params, searchReloadOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function search () {
|
||||||
|
const searchInputQueryset = qs.getSearchInputQueryset(vm.searchValue);
|
||||||
|
|
||||||
|
const currentQueryset = getCurrentQueryset();
|
||||||
|
const modifiedQueryset = qs.mergeQueryset(currentQueryset, searchInputQueryset);
|
||||||
|
|
||||||
|
vm.searchTags = getSearchTags(modifiedQueryset);
|
||||||
|
|
||||||
|
$state.params.job_event_search = qs.encodeArr(modifiedQueryset);
|
||||||
|
$state.transitionTo($state.current, $state.params, searchReloadOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch () {
|
||||||
|
vm.searchTags = [];
|
||||||
|
|
||||||
|
$state.params.job_event_search = '';
|
||||||
|
$state.transitionTo($state.current, $state.params, searchReloadOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
JobsIndexController.$inject = [
|
JobsIndexController.$inject = [
|
||||||
@@ -327,7 +404,9 @@ JobsIndexController.$inject = [
|
|||||||
'JobRenderService',
|
'JobRenderService',
|
||||||
'$scope',
|
'$scope',
|
||||||
'$compile',
|
'$compile',
|
||||||
'$q'
|
'$q',
|
||||||
|
'$state',
|
||||||
|
'QuerySet',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = JobsIndexController;
|
module.exports = JobsIndexController;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Controller from '~features/output/index.controller';
|
|||||||
import PageService from '~features/output/page.service';
|
import PageService from '~features/output/page.service';
|
||||||
import RenderService from '~features/output/render.service';
|
import RenderService from '~features/output/render.service';
|
||||||
import ScrollService from '~features/output/scroll.service';
|
import ScrollService from '~features/output/scroll.service';
|
||||||
|
import SearchKeyDirective from '~features/output/search-key.directive';
|
||||||
|
|
||||||
const Template = require('~features/output/index.view.html');
|
const Template = require('~features/output/index.view.html');
|
||||||
|
|
||||||
@@ -15,8 +16,8 @@ const PAGE_LIMIT = 3;
|
|||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 100;
|
||||||
const WS_PREFIX = 'ws';
|
const WS_PREFIX = 'ws';
|
||||||
|
|
||||||
function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams) {
|
function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJob, $stateParams, qs, Wait) {
|
||||||
const { id, type } = $stateParams;
|
const { id, type, job_event_search } = $stateParams;
|
||||||
|
|
||||||
let Resource;
|
let Resource;
|
||||||
let related = 'events';
|
let related = 'events';
|
||||||
@@ -43,14 +44,20 @@ function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJ
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const params = { page_size: PAGE_SIZE, order_by: 'start_line' };
|
||||||
|
|
||||||
|
if (job_event_search) {
|
||||||
|
const searchParams = qs.encodeQuerysetObject(qs.decodeArr(job_event_search));
|
||||||
|
|
||||||
|
Object.assign(params, searchParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
Wait('start');
|
||||||
return new Resource('get', id)
|
return new Resource('get', id)
|
||||||
.then(model => model.extend(related, {
|
.then(model => model.extend(related, {
|
||||||
pageCache: PAGE_CACHE,
|
pageCache: PAGE_CACHE,
|
||||||
pageLimit: PAGE_LIMIT,
|
pageLimit: PAGE_LIMIT,
|
||||||
params: {
|
params,
|
||||||
page_size: PAGE_SIZE,
|
|
||||||
order_by: 'start_line'
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
.then(model => {
|
.then(model => {
|
||||||
return {
|
return {
|
||||||
@@ -67,7 +74,9 @@ function resolveResource (Job, ProjectUpdate, AdHocCommand, SystemJob, WorkflowJ
|
|||||||
pageLimit: PAGE_LIMIT
|
pageLimit: PAGE_LIMIT
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
|
.catch(({ data, status }) => qs.error(data, status))
|
||||||
|
.finally(() => Wait('stop'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWebSocketConnection (SocketService, $stateParams) {
|
function resolveWebSocketConnection (SocketService, $stateParams) {
|
||||||
@@ -131,8 +140,8 @@ function getWebSocketResource (type) {
|
|||||||
function JobsRun ($stateRegistry) {
|
function JobsRun ($stateRegistry) {
|
||||||
const state = {
|
const state = {
|
||||||
name: 'jobz',
|
name: 'jobz',
|
||||||
url: '/jobz/:type/:id',
|
url: '/jobz/:type/:id?job_event_search',
|
||||||
route: '/jobz/:type/:id',
|
route: '/jobz/:type/:id?job_event_search',
|
||||||
data: {
|
data: {
|
||||||
activityStream: true,
|
activityStream: true,
|
||||||
activityStreamTarget: 'jobs'
|
activityStreamTarget: 'jobs'
|
||||||
@@ -152,6 +161,8 @@ function JobsRun ($stateRegistry) {
|
|||||||
'SystemJobModel',
|
'SystemJobModel',
|
||||||
'WorkflowJobModel',
|
'WorkflowJobModel',
|
||||||
'$stateParams',
|
'$stateParams',
|
||||||
|
'QuerySet',
|
||||||
|
'Wait',
|
||||||
resolveResource
|
resolveResource
|
||||||
],
|
],
|
||||||
ncyBreadcrumb: [
|
ncyBreadcrumb: [
|
||||||
@@ -179,6 +190,7 @@ angular
|
|||||||
.service('JobStrings', Strings)
|
.service('JobStrings', Strings)
|
||||||
.service('JobPageService', PageService)
|
.service('JobPageService', PageService)
|
||||||
.service('JobScrollService', ScrollService)
|
.service('JobScrollService', ScrollService)
|
||||||
|
.directive('atSearchKey', SearchKeyDirective)
|
||||||
.run(JobsRun);
|
.run(JobsRun);
|
||||||
|
|
||||||
export default MODULE_NAME;
|
export default MODULE_NAME;
|
||||||
|
|||||||
@@ -7,6 +7,51 @@
|
|||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<at-panel class="at-Stdout">
|
<at-panel class="at-Stdout">
|
||||||
|
<!-- search ============================================================================================================= -->
|
||||||
|
<form ng-submit="vm.search()">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control at-Input"
|
||||||
|
ng-class="{ 'at-Input--rejected': vm.searchRejected }"
|
||||||
|
ng-model="vm.searchValue"
|
||||||
|
ng-attr-placeholder="SEARCH"
|
||||||
|
ng-disabled="vm.searchDisabled">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn at-ButtonHollow--default at-Input-button"
|
||||||
|
ng-click="vm.search()"
|
||||||
|
ng-disabled="vm.searchDisabled"
|
||||||
|
type="button">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn jobz-Button-searchKey"
|
||||||
|
ng-if="vm.searchKey"
|
||||||
|
ng-disabled="vm.searchDisabled"
|
||||||
|
ng-click="vm.toggleSearchKey()"
|
||||||
|
type="button"> key
|
||||||
|
</button>
|
||||||
|
<button class="btn at-ButtonHollow--default at-Input-button"
|
||||||
|
ng-if="!vm.searchKey"
|
||||||
|
ng-disabled="vm.searchDisabled"
|
||||||
|
ng-click="vm.toggleSearchKey()"
|
||||||
|
type="button"> key
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="jobz-tagz">
|
||||||
|
<div class="LabelList-tagContainer" ng-repeat="tag in vm.searchTags track by $index">
|
||||||
|
<div class="LabelList-tag LabelList-tag--deletable"><span class="LabelList-name">{{ tag }}</span></div>
|
||||||
|
<div class="LabelList-deleteContainer" ng-click="vm.removeSearchTag($index)">
|
||||||
|
<i class="fa fa-times LabelList-tagDelete"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div><a href class="jobz-searchClearAll" ng-click="vm.clearSearch()" ng-show="!(vm.searchTags | isEmpty)">CLEAR ALL</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<at-search-key ng-show="vm.searchKey" fields="vm.searchKeyFields" examples="vm.searchKeyExamples"></at-search-key>
|
||||||
|
|
||||||
|
<!-- ==================================================================================================================== -->
|
||||||
<div class="at-Stdout-menuTop">
|
<div class="at-Stdout-menuTop">
|
||||||
<div class="pull-left" ng-click="vm.expand()">
|
<div class="pull-left" ng-click="vm.expand()">
|
||||||
<i class="at-Stdout-menuIcon fa"
|
<i class="at-Stdout-menuIcon fa"
|
||||||
|
|||||||
38
awx/ui/client/features/output/search-key.directive.js
Normal file
38
awx/ui/client/features/output/search-key.directive.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const templateUrl = require('~features/output/search-key.partial.html');
|
||||||
|
|
||||||
|
function atSearchKeyLink (scope, el, attrs, controllers) {
|
||||||
|
const [atSearchKeyController] = controllers;
|
||||||
|
|
||||||
|
atSearchKeyController.init(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AtSearchKeyController () {
|
||||||
|
const vm = this || {};
|
||||||
|
|
||||||
|
vm.init = scope => {
|
||||||
|
vm.examples = scope.examples || [];
|
||||||
|
vm.fields = scope.fields || [];
|
||||||
|
vm.relatedFields = scope.relatedFields || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AtSearchKeyController.$inject = ['$scope'];
|
||||||
|
|
||||||
|
|
||||||
|
function atSearchKey () {
|
||||||
|
return {
|
||||||
|
templateUrl,
|
||||||
|
restrict: 'E',
|
||||||
|
require: ['atSearchKey'],
|
||||||
|
controllerAs: 'vm',
|
||||||
|
link: atSearchKeyLink,
|
||||||
|
controller: AtSearchKeyController,
|
||||||
|
scope: {
|
||||||
|
examples: '=',
|
||||||
|
fields: '=',
|
||||||
|
relatedFields: '=',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default atSearchKey;
|
||||||
20
awx/ui/client/features/output/search-key.partial.html
Normal file
20
awx/ui/client/features/output/search-key.partial.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!-- todo: styling, css etc. - disposition according to project lib conventions -->
|
||||||
|
<div class="jobz-searchKeyPaneContainer">
|
||||||
|
<div class="jobz-searchKeyPane">
|
||||||
|
<div class="SmartSearch-keyRow">
|
||||||
|
<div class="SmartSearch-examples">
|
||||||
|
<div class="SmartSearch-examples--title"><b>EXAMPLES:</b></div>
|
||||||
|
<div class="SmartSearch-examples--search" ng-repeat="tag in vm.examples"> {{ tag }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="SmartSearch-keyRow">
|
||||||
|
<b>FIELDS:</b>
|
||||||
|
<span ng-repeat="field in vm.fields">{{ field }}<span ng-if="!$last">, </span></span>
|
||||||
|
</div>
|
||||||
|
<div class="SmartSearch-keyRow">
|
||||||
|
<b>ADDITIONAL INFORMATION:</b>
|
||||||
|
For additional information on advanced search search syntax please see the Ansible Tower
|
||||||
|
<a ng-attr-href="undefined" target="_blank">documentation</a>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user