From 37f23b6eaef05ca323113a721a6955040b0512e1 Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Fri, 23 Nov 2018 17:01:45 +0100 Subject: [PATCH 01/16] new directive wz-tag-filter --- public/directives/index.js | 2 + .../wz-tag-filter/wz-tag-filter.html | 24 ++++ .../directives/wz-tag-filter/wz-tag-filter.js | 130 ++++++++++++++++++ .../wz-tag-filter/wz-tag-filter.less | 92 +++++++++++++ public/templates/agents-prev/agents-prev.html | 45 +++--- 5 files changed, 270 insertions(+), 23 deletions(-) create mode 100644 public/directives/wz-tag-filter/wz-tag-filter.html create mode 100644 public/directives/wz-tag-filter/wz-tag-filter.js create mode 100644 public/directives/wz-tag-filter/wz-tag-filter.less diff --git a/public/directives/index.js b/public/directives/index.js index 78d66ac26c..8d3d89fbaa 100644 --- a/public/directives/index.js +++ b/public/directives/index.js @@ -18,3 +18,5 @@ import './wz-welcome-card/wz-welcome-card'; import './wz-no-config/wz-no-config'; import './wz-config-item/wz-config-item'; import './wz-config-item/wz-config-item.less'; +import './wz-tag-filter/wz-tag-filter'; +import './wz-tag-filter/wz-tag-filter.less'; diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html new file mode 100644 index 0000000000..a084dbee4c --- /dev/null +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -0,0 +1,24 @@ +
+ +
+
    +
  • +
    + {{tag}} + × +
    +
  • +
+ +
+

{{autocompleteContent.title}}

+
    +
  • + {{element}} +
  • +
+
+
+
\ No newline at end of file diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js new file mode 100644 index 0000000000..c41009c849 --- /dev/null +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -0,0 +1,130 @@ +/* + * Wazuh app - Wazuh search and filter by tags bar + * Copyright (C) 2018 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import template from './wz-tag-filter.html'; +import { DataFactory } from '../../services/data-factory'; +import { uiModules } from 'ui/modules'; + +const app = uiModules.get('app/wazuh', []); + +app.directive('wzTagFilter', function () { + return { + restrict: 'E', + scope: { + path: '=path', + keys: '=keys', + callback: '&' + }, + controller( + $scope, + $timeout, + apiReq + ) { + const instance = new DataFactory( + apiReq, + $scope.path, + {} + ); + + $scope.tagList = []; + $scope.newTag = ''; + $scope.isAutocomplete = false; + $scope.dataModel = []; + + $scope.addTag = (flag = false) => { + var input = document.getElementById('wz-search-filter-bar-input'); + input.blur(); + $scope.tagList.push($scope.newTag); + + $scope.showAutocomplete(flag); + const term = $scope.newTag.split(':'); + const filter = { name: term[0], value: term[1] }; + $scope.callback({ 'filter': filter }); + $scope.newTag = ''; + }; + + $scope.addTagKey = (key) => { + $scope.newTag = key + ':'; + $scope.showAutocomplete(true); + }; + + $scope.addTagValue = (value) => { + $scope.newTag += value; + $scope.addTag(); + }; + + $scope.removeTag = (idx) => { + const term = $scope.tagList[idx].split(':'); + const filter = { name: term[0], value: 'all' }; + $scope.callback({ 'filter': filter }); + $scope.tagList.splice(idx, 1); + $scope.showAutocomplete(false); + }; + + $scope.showAutocomplete = (flag) => { + if (flag) { + $scope.getAutocompleteContent(); + } + $scope.isAutocomplete = flag; + index_autocomplete(flag); + }; + + $scope.getAutocompleteContent = () => { + const isKey = $scope.newTag.indexOf(':') === -1; + $scope.autocompleteContent = { 'title': '', 'isKey': isKey, 'list': [] }; + $scope.autocompleteContent.title = isKey ? 'Tag keys' : 'Values'; + if (isKey) { + $scope.keys.forEach(function (key) { + $scope.autocompleteContent.list.push(key); + }); + } else { + const model = $scope.dataModel.find(function (x) { return x.key === $scope.newTag.split(':')[0] }) + if (model) { + $scope.autocompleteContent.list = model.list; + } + } + }; + + $scope.addSearchKey = (e) => { + if (e.key === ':') { + $scope.getAutocompleteContent(); + } + }; + + function index_autocomplete(flag = true) { + $timeout(function () { + var autocomplete = document.getElementById('wz-search-filter-bar-autocomplete'); + var input = document.getElementById('wz-search-filter-bar-input'); + autocomplete.style.left = input.offsetLeft + 'px'; + if (flag) { + input.focus(); + } + }, 100); + } + + const load = async () => { + try { + const result = await instance.fetch(); + $scope.keys.forEach(function (key) { + $scope.dataModel.push({ 'key': key, 'list': result.items.map(function (x) { return x[key] }) }); + }); + return; + } catch (error) { + return Promise.reject(error); + } + }; + + load(); + }, + template + }; +}); diff --git a/public/directives/wz-tag-filter/wz-tag-filter.less b/public/directives/wz-tag-filter/wz-tag-filter.less new file mode 100644 index 0000000000..dc9a167fec --- /dev/null +++ b/public/directives/wz-tag-filter/wz-tag-filter.less @@ -0,0 +1,92 @@ +/* + * Wazuh app - Search and filter by tags bar stylesheet + * Copyright (C) 2018 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +/* -------------------------------------------------------------------------- */ +/* ----------- Wazuh search and filter by tags bar stylesheet --------------- */ +/* -------------------------------------------------------------------------- */ + +.wz-search-filter-bar { + padding: 8px; + margin-top: 15px; +} + +.wz-search-filter-bar .fa-search{ + padding: 5px; + float: left; + opacity: .5; +} + +.wz-tags { + height: 30px; +} + +.wz-tag-item { + color: white; + border-radius: 3px; + float: left; + padding: 3px 5px; + background: #0079a5; + margin: 1px 3px; +} + +.wz-tag-remove-button{ + color: #005571; +} +.wz-search-filter-bar-input{ + border: none; + padding: 5px; + width: auto; + margin-left: 5px; +} + +.wz-search-filter-bar-autocomplete{ + min-width: 200px; + background: #000000d9; + position: absolute; + border: 1px solid black; + border-radius: 5px; + padding: 10px 0; +} + +.wz-search-filter-bar-autocomplete p b{ + color: white; + font-weight: 700; + padding: 10px; +} +.wz-search-filter-bar-autocomplete ul li{ + color: white; + padding: 0 10px; +} + +.wz-search-filter-bar-autocomplete ul li span{ + padding-left: 5px; +} + +.wz-search-filter-bar-autocomplete ul li:hover{ + background: black; + color:#0079a5; + cursor: pointer; +} +/* @media (min-width: 1280px) { + .wz-configuration-item { + padding: 5px 0; + } + + .wz-configuration-label { + text-align: right; + } + + .wz-configuration-value { + margin: 0 0 0 30px; + } +} + */ \ No newline at end of file diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index 8a1d03fc79..ea4e9ebf23 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -34,14 +34,15 @@

Last registered agent

- -

{{ctrl.lastAgent.name}} (manager)

+ +

{{ctrl.lastAgent.name}} + (manager)

Higher activity

- +

{{ctrl.mostActiveAgent.name}}

@@ -50,8 +51,8 @@
- @@ -60,8 +61,8 @@
-
-
-
-
- +
@@ -106,13 +107,11 @@
+ +
- +
@@ -122,4 +121,4 @@ - + \ No newline at end of file From 381320d222dcfdcb57fe810864589c9826ad475a Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Mon, 26 Nov 2018 12:56:46 +0100 Subject: [PATCH 02/16] some fixes and specify search and filter tags in query --- .../wz-tag-filter/wz-tag-filter.html | 11 ++-- .../directives/wz-tag-filter/wz-tag-filter.js | 47 ++++++++------ .../wz-tag-filter/wz-tag-filter.less | 19 +++++- public/templates/agents-prev/agents-prev.html | 61 +------------------ 4 files changed, 55 insertions(+), 83 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html index a084dbee4c..bf8230827e 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.html +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -1,22 +1,23 @@
-
+
  • - {{tag}} + + {{tag.value}} ×
-
+ placeholder='Add filter or search' ng-keyup='$event.keyCode == 13 ? addTag(true) : addSearchKey($event)' /> +

{{autocompleteContent.title}}

  • - {{element}} + {{element}}
diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index c41009c849..96bc34b7d5 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -22,7 +22,8 @@ app.directive('wzTagFilter', function () { scope: { path: '=path', keys: '=keys', - callback: '&' + search: '&', + filter: '&' }, controller( $scope, @@ -43,12 +44,18 @@ app.directive('wzTagFilter', function () { $scope.addTag = (flag = false) => { var input = document.getElementById('wz-search-filter-bar-input'); input.blur(); - $scope.tagList.push($scope.newTag); - - $scope.showAutocomplete(flag); const term = $scope.newTag.split(':'); - const filter = { name: term[0], value: term[1] }; - $scope.callback({ 'filter': filter }); + const obj = { name: term[0], value: term[1] }; + const isFilter = obj.value; + const tag = { + 'value': $scope.newTag, + 'type': isFilter ? 'filter' : 'search' + }; + const idxSearch = $scope.tagList.map(function (x) { return x.type; }).indexOf('search'); + if (!isFilter && idxSearch !== -1) { $scope.removeTag(idxSearch) }; + $scope.tagList.push(tag); + $scope.showAutocomplete(flag); + isFilter ? $scope.filter({ 'filter': obj }) : $scope.search({ 'search': obj.name }); $scope.newTag = ''; }; @@ -58,14 +65,15 @@ app.directive('wzTagFilter', function () { }; $scope.addTagValue = (value) => { + $scope.newTag = $scope.newTag.substring(0, $scope.newTag.indexOf(':') + 1); $scope.newTag += value; $scope.addTag(); }; $scope.removeTag = (idx) => { - const term = $scope.tagList[idx].split(':'); - const filter = { name: term[0], value: 'all' }; - $scope.callback({ 'filter': filter }); + const term = $scope.tagList[idx].value.split(':'); + const obj = term[1] ? { name: term[0], value: 'all' } : ''; + obj.value ? $scope.filter({ 'filter': obj }) : $scope.search({ 'search': obj.name }); $scope.tagList.splice(idx, 1); $scope.showAutocomplete(false); }; @@ -79,25 +87,30 @@ app.directive('wzTagFilter', function () { }; $scope.getAutocompleteContent = () => { - const isKey = $scope.newTag.indexOf(':') === -1; + const term = $scope.newTag.split(':'); + const isKey = !term[1] && $scope.newTag.indexOf(':') === -1; $scope.autocompleteContent = { 'title': '', 'isKey': isKey, 'list': [] }; - $scope.autocompleteContent.title = isKey ? 'Tag keys' : 'Values'; + $scope.autocompleteContent.title = isKey ? 'Filter keys' : 'Values'; if (isKey) { + const regex = new RegExp('^' + term[0], 'i'); $scope.keys.forEach(function (key) { - $scope.autocompleteContent.list.push(key); + if (regex.test(key)) { + $scope.autocompleteContent.list.push(key); + } }); } else { + const regex = new RegExp('^' + term[1], 'i'); const model = $scope.dataModel.find(function (x) { return x.key === $scope.newTag.split(':')[0] }) if (model) { - $scope.autocompleteContent.list = model.list; + model.list = Array.isArray(model.list[0]) ? model.list[0] : model.list; + $scope.autocompleteContent.list = model.list.filter(function (x) { return regex.test(x) }); } } + }; $scope.addSearchKey = (e) => { - if (e.key === ':') { - $scope.getAutocompleteContent(); - } + $scope.getAutocompleteContent(); }; function index_autocomplete(flag = true) { @@ -115,7 +128,7 @@ app.directive('wzTagFilter', function () { try { const result = await instance.fetch(); $scope.keys.forEach(function (key) { - $scope.dataModel.push({ 'key': key, 'list': result.items.map(function (x) { return x[key] }) }); + $scope.dataModel.push({ 'key': key, 'list': result.items.map(function (x) { return key.split('.').reduce((a, b) => a ? a[b] : '', x) }) }); }); return; } catch (error) { diff --git a/public/directives/wz-tag-filter/wz-tag-filter.less b/public/directives/wz-tag-filter/wz-tag-filter.less index dc9a167fec..e7e697ada9 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.less +++ b/public/directives/wz-tag-filter/wz-tag-filter.less @@ -19,14 +19,26 @@ margin-top: 15px; } -.wz-search-filter-bar .fa-search{ - padding: 5px; +.wz-search-filter-bar > .fa-search{ + padding: 8px 3px; float: left; opacity: .5; + width: calc(~'0% + 25px'); } .wz-tags { height: 30px; + width: calc(~'100% - 25px'); + display: flex; +} + +.wz-tags > ul{ + flex: 0 1 auto; + display: flex; +} + +.wz-tags > ul li{ + white-space: nowrap; } .wz-tag-item { @@ -46,6 +58,7 @@ padding: 5px; width: auto; margin-left: 5px; + flex: 1; } .wz-search-filter-bar-autocomplete{ @@ -55,6 +68,7 @@ border: 1px solid black; border-radius: 5px; padding: 10px 0; + margin-top: 30px; } .wz-search-filter-bar-autocomplete p b{ @@ -62,6 +76,7 @@ font-weight: 700; padding: 10px; } + .wz-search-filter-bar-autocomplete ul li{ color: white; padding: 0 10px; diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index ea4e9ebf23..3279aa7e70 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -49,65 +49,8 @@
-
-
- -
- -
- -
- -
- -
-
- -
-
- -
-
- -
- - -
- - +
Date: Mon, 3 Dec 2018 17:24:57 +0100 Subject: [PATCH 03/16] Visualize groupings of tags in filter bar --- public/controllers/agent/agents-preview.js | 4 + public/directives/wz-table/lib/data.js | 31 ++++- public/directives/wz-table/lib/listeners.js | 4 + .../directives/wz-table/wz-table-directive.js | 18 ++- .../wz-tag-filter/wz-tag-filter.html | 20 ++-- .../directives/wz-tag-filter/wz-tag-filter.js | 106 +++++++++++++++--- .../wz-tag-filter/wz-tag-filter.less | 20 +++- public/services/data-factory.js | 3 +- public/templates/agents-prev/agents-prev.html | 2 +- 9 files changed, 180 insertions(+), 28 deletions(-) diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index a50b511b7e..960b97391f 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -80,6 +80,10 @@ export class AgentsPreviewController { this.$scope.$broadcast('wazuhFilter', { filter }); } + query(query) { + this.$scope.$broadcast('wazuhQuery', { query }); + } + showAgent(agent) { this.shareAgent.setAgent(agent); this.$location.path('/agents'); diff --git a/public/directives/wz-table/lib/data.js b/public/directives/wz-table/lib/data.js index 30624ecf79..dabfcc3577 100644 --- a/public/directives/wz-table/lib/data.js +++ b/public/directives/wz-table/lib/data.js @@ -64,10 +64,37 @@ export async function filterData( $scope.wazuh_table_loading = false; $scope.error = `Error filtering by ${ filter ? filter.value : 'undefined' - } - ${error.message || error}.`; + } - ${error.message || error}.`; errorHandler.handle( `Error filtering by ${ - filter ? filter.value : 'undefined' + filter ? filter.value : 'undefined' + }. ${error.message || error}`, + 'Data factory' + ); + } + if (!$scope.$$phase) $scope.$digest(); + return; +} + +export async function queryData( + query, + $scope, + fetch, + errorHandler +) { + try { + $scope.error = false; + $scope.wazuh_table_loading = true; + await fetch({ 'query': query }); + $scope.wazuh_table_loading = false; + } catch (error) { + $scope.wazuh_table_loading = false; + $scope.error = `Query error ${ + query ? query.value : 'undefined' + } - ${error.message || error}.`; + errorHandler.handle( + `Query error ${ + query ? query.value : 'undefined' }. ${error.message || error}`, 'Data factory' ); diff --git a/public/directives/wz-table/lib/listeners.js b/public/directives/wz-table/lib/listeners.js index 658afa2017..6cc93f0405 100644 --- a/public/directives/wz-table/lib/listeners.js +++ b/public/directives/wz-table/lib/listeners.js @@ -20,6 +20,10 @@ export function wazuhFilter(parameters, filter) { return filter(parameters.filter); } +export function wazuhQuery(parameters, query) { + return query(parameters.query); +} + export function wazuhSearch(parameters, instance, search) { try { const matchesSpecificPath = diff --git a/public/directives/wz-table/wz-table-directive.js b/public/directives/wz-table/wz-table-directive.js index 0004abf6c4..b2e5cbe126 100644 --- a/public/directives/wz-table/wz-table-directive.js +++ b/public/directives/wz-table/wz-table-directive.js @@ -19,14 +19,14 @@ import { parseValue } from './lib/parse-value'; import * as pagination from './lib/pagination'; import { sort } from './lib/sort'; import * as listeners from './lib/listeners'; -import { searchData, filterData } from './lib/data'; +import { searchData, filterData, queryData } from './lib/data'; import { clickAction } from './lib/click-action'; import { initTable } from './lib/init'; import { checkGap } from './lib/check-gap'; const app = uiModules.get('app/wazuh', []); -app.directive('wzTable', function() { +app.directive('wzTable', function () { return { restrict: 'E', scope: { @@ -131,6 +131,14 @@ app.directive('wzTable', function() { errorHandler ); + const query = async query => + queryData( + query, + $scope, + fetch, + errorHandler + ); + const realTimeFunction = async () => { try { $scope.error = false; @@ -180,7 +188,7 @@ app.directive('wzTable', function() { $scope.prevPage = () => pagination.prevPage($scope); $scope.nextPage = async currentPage => pagination.nextPage(currentPage, $scope, errorHandler, fetch); - $scope.setPage = function() { + $scope.setPage = function () { $scope.currentPage = this.n; $scope.nextPage(this.n); }; @@ -200,6 +208,10 @@ app.directive('wzTable', function() { listeners.wazuhSearch(parameters, instance, search) ); + $scope.$on('wazuhQuery', (event, parameters) => + listeners.wazuhQuery(parameters, query) + ); + $scope.$on('wazuhRemoveFilter', (event, parameters) => listeners.wazuhRemoveFilter(parameters, instance, wzTableFilter, init) ); diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html index bf8230827e..a4f8bf0e4d 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.html +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -2,12 +2,18 @@
    -
  • -
    - - {{tag.value}} - × +
  • +
    +
    +
    + + {{tag.value.name}} : {{tag.value.value}} + × +
    + OR +
    + AND

{{autocompleteContent.title}}

-
    -
  • +
      +
    • {{element}}
    diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 96bc34b7d5..5f16770b65 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -23,7 +23,8 @@ app.directive('wzTagFilter', function () { path: '=path', keys: '=keys', search: '&', - filter: '&' + filter: '&', + query: '&' }, controller( $scope, @@ -37,6 +38,7 @@ app.directive('wzTagFilter', function () { ); $scope.tagList = []; + $scope.groupedTagList = []; $scope.newTag = ''; $scope.isAutocomplete = false; $scope.dataModel = []; @@ -48,17 +50,57 @@ app.directive('wzTagFilter', function () { const obj = { name: term[0], value: term[1] }; const isFilter = obj.value; const tag = { - 'value': $scope.newTag, + 'id': generateUID(), + 'key': obj.name, + 'value': obj, 'type': isFilter ? 'filter' : 'search' }; - const idxSearch = $scope.tagList.map(function (x) { return x.type; }).indexOf('search'); - if (!isFilter && idxSearch !== -1) { $scope.removeTag(idxSearch) }; - $scope.tagList.push(tag); + const idxSearch = $scope.tagList.find(function (x) { return x.type === 'search' }); + if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id) }; + if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { + $scope.tagList.push(tag); + $scope.groupedTagList = groupBy($scope.tagList, 'key'); + isFilter ? $scope.query({ 'query': buildQuery($scope.groupedTagList) }) : $scope.search({ 'search': obj.name }); + } $scope.showAutocomplete(flag); - isFilter ? $scope.filter({ 'filter': obj }) : $scope.search({ 'search': obj.name }); $scope.newTag = ''; }; + function buildQuery(groups, idx1) { + let queryString = "?search=''&q="; + groups.forEach(function (group) { + queryString += "(" + group.forEach(function (tag, idx2) { + queryString += tag.key + ":" + tag.value.value; + if (idx2 != group.length - 1) { + queryString += ","; + } + }); + queryString += ")" + if (idx1 != groups.length - 1) { + queryString += ";"; + } + }); + console.log(queryString); + return queryString; + } + + function groupBy(collection, property) { + var i = 0, val, index, + values = [], result = []; + for (; i < collection.length; i++) { + val = collection[i][property]; + index = values.indexOf(val); + if (index > -1) + result[index].push(collection[i]); + else { + values.push(val); + result.push([collection[i]]); + } + } + return result; + } + $scope.addTagKey = (key) => { $scope.newTag = key + ':'; $scope.showAutocomplete(true); @@ -70,11 +112,11 @@ app.directive('wzTagFilter', function () { $scope.addTag(); }; - $scope.removeTag = (idx) => { - const term = $scope.tagList[idx].value.split(':'); - const obj = term[1] ? { name: term[0], value: 'all' } : ''; - obj.value ? $scope.filter({ 'filter': obj }) : $scope.search({ 'search': obj.name }); - $scope.tagList.splice(idx, 1); + $scope.removeTag = (id) => { + const term = $scope.tagList.find(function (x) { return x.id === id }); + term.type === 'filter' ? $scope.filter({ 'filter': { name: term.key, value: 'all' } }) : $scope.search({ 'search': '' }); + $scope.tagList.splice($scope.tagList.findIndex(function (x) { return x.id === id }), 1); + $scope.groupedTagList = groupBy($scope.tagList, 'key'); $scope.showAutocomplete(false); }; @@ -103,10 +145,9 @@ app.directive('wzTagFilter', function () { const model = $scope.dataModel.find(function (x) { return x.key === $scope.newTag.split(':')[0] }) if (model) { model.list = Array.isArray(model.list[0]) ? model.list[0] : model.list; - $scope.autocompleteContent.list = model.list.filter(function (x) { return regex.test(x) }); + $scope.autocompleteContent.list = [...new Set(model.list.filter(function (x) { return regex.test(x) }))]; } } - }; $scope.addSearchKey = (e) => { @@ -136,6 +177,45 @@ app.directive('wzTagFilter', function () { } }; + function generateUID() { + // I generate the UID from two parts here + // to ensure the random number provide enough bits. + var firstPart = (Math.random() * 46656) | 0; + var secondPart = (Math.random() * 46656) | 0; + firstPart = ("000" + firstPart.toString(36)).slice(-3); + secondPart = ("000" + secondPart.toString(36)).slice(-3); + return firstPart + secondPart; + } + + $('#wz-search-filter-bar-input').bind('keydown', function (e) { + //let liSelected = document.getElementById('wz-search-filter-bar-autocomplete-list li.selected'); + let liSelected = $('#wz-search-filter-bar-autocomplete-list li.selected'); + if (e.which === 40) { + if (liSelected != 0) { + liSelected.removeClass('selected'); + next = liSelected.next(); + if (next.length > 0) { + liSelected = next.addClass('selected'); + } else { + liSelected = li.eq(0).addClass('selected'); + } + } else { + liSelected = li.eq(0).addClass('selected'); + } + } else if (e.which === 38) { + if (liSelected) { + liSelected.removeClass('selected'); + next = liSelected.prev(); + if (next.length > 0) { + liSelected = next.addClass('selected'); + } else { + liSelected = li.last().addClass('selected'); + } + } else { + liSelected = li.last().addClass('selected'); + } + } + }); load(); }, template diff --git a/public/directives/wz-tag-filter/wz-tag-filter.less b/public/directives/wz-tag-filter/wz-tag-filter.less index e7e697ada9..0a8381137c 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.less +++ b/public/directives/wz-tag-filter/wz-tag-filter.less @@ -39,6 +39,16 @@ .wz-tags > ul li{ white-space: nowrap; + display: flex; +} + +.wz-tags > ul li .grouped{ + background: #d9d9d9; + border-radius: 3px; + padding: 3px; + height: 36px; + margin-top: -3px; + display: flex; } .wz-tag-item { @@ -50,6 +60,14 @@ margin: 1px 3px; } +.wz-tag-item-connector { + font-size: 10px; + line-height: 30px; + color: #005571; + font-weight: bold; + padding: 0px 5px; +} + .wz-tag-remove-button{ color: #005571; } @@ -86,7 +104,7 @@ padding-left: 5px; } -.wz-search-filter-bar-autocomplete ul li:hover{ +.wz-search-filter-bar-autocomplete ul li:hover, .wz-search-filter-bar-autocomplete ul li.selected{ background: black; color:#0079a5; cursor: pointer; diff --git a/public/services/data-factory.js b/public/services/data-factory.js index 3ebcfc6b46..f1436fc3b9 100644 --- a/public/services/data-factory.js +++ b/public/services/data-factory.js @@ -67,9 +67,10 @@ export class DataFactory { this.serializeFilters(parameters); // Fetch next items + const path = !options.query ? this.path : this.path + options.query; const firstPage = await this.httpClient.request( 'GET', - this.path, + path, parameters ); this.items = this.items.filter(item => !!item); diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index 3279aa7e70..ddff2cb842 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -50,7 +50,7 @@
+ search="ctrl.search(search)" filter="ctrl.filter(filter)" query="ctrl.query(query)">
Date: Tue, 4 Dec 2018 10:27:59 +0100 Subject: [PATCH 04/16] Calling api with query --- public/controllers/agent/agents-preview.js | 4 +- public/directives/wz-table/lib/data.js | 13 ++++- public/directives/wz-table/lib/listeners.js | 2 +- .../directives/wz-table/wz-table-directive.js | 5 +- .../directives/wz-tag-filter/wz-tag-filter.js | 50 ++++++++++++------- public/services/data-factory.js | 3 +- public/templates/agents-prev/agents-prev.html | 2 +- 7 files changed, 52 insertions(+), 27 deletions(-) diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index 960b97391f..b6349a3207 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -80,8 +80,8 @@ export class AgentsPreviewController { this.$scope.$broadcast('wazuhFilter', { filter }); } - query(query) { - this.$scope.$broadcast('wazuhQuery', { query }); + query(query, search) { + this.$scope.$broadcast('wazuhQuery', { query, search }); } showAgent(agent) { diff --git a/public/directives/wz-table/lib/data.js b/public/directives/wz-table/lib/data.js index dabfcc3577..4a2b16e92c 100644 --- a/public/directives/wz-table/lib/data.js +++ b/public/directives/wz-table/lib/data.js @@ -78,6 +78,9 @@ export async function filterData( export async function queryData( query, + term, + instance, + wzTableFilter, $scope, fetch, errorHandler @@ -85,7 +88,15 @@ export async function queryData( try { $scope.error = false; $scope.wazuh_table_loading = true; - await fetch({ 'query': query }); + instance.removeFilters(); + if (term) { + instance.addFilter('search', term); + } + if (query) { + instance.addFilter('q', query); + } + wzTableFilter.set(instance.filters); + await fetch(); $scope.wazuh_table_loading = false; } catch (error) { $scope.wazuh_table_loading = false; diff --git a/public/directives/wz-table/lib/listeners.js b/public/directives/wz-table/lib/listeners.js index 6cc93f0405..1428895cce 100644 --- a/public/directives/wz-table/lib/listeners.js +++ b/public/directives/wz-table/lib/listeners.js @@ -21,7 +21,7 @@ export function wazuhFilter(parameters, filter) { } export function wazuhQuery(parameters, query) { - return query(parameters.query); + return query(parameters.query, parameters.search); } export function wazuhSearch(parameters, instance, search) { diff --git a/public/directives/wz-table/wz-table-directive.js b/public/directives/wz-table/wz-table-directive.js index b2e5cbe126..f411d8f902 100644 --- a/public/directives/wz-table/wz-table-directive.js +++ b/public/directives/wz-table/wz-table-directive.js @@ -131,9 +131,12 @@ app.directive('wzTable', function () { errorHandler ); - const query = async query => + const query = async (query, search) => queryData( query, + search, + instance, + wzTableFilter, $scope, fetch, errorHandler diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 5f16770b65..05cb27c2ce 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -22,8 +22,6 @@ app.directive('wzTagFilter', function () { scope: { path: '=path', keys: '=keys', - search: '&', - filter: '&', query: '&' }, controller( @@ -60,29 +58,44 @@ app.directive('wzTagFilter', function () { if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { $scope.tagList.push(tag); $scope.groupedTagList = groupBy($scope.tagList, 'key'); - isFilter ? $scope.query({ 'query': buildQuery($scope.groupedTagList) }) : $scope.search({ 'search': obj.name }); + buildQuery($scope.groupedTagList); } $scope.showAutocomplete(flag); $scope.newTag = ''; }; - function buildQuery(groups, idx1) { - let queryString = "?search=''&q="; - groups.forEach(function (group) { - queryString += "(" - group.forEach(function (tag, idx2) { - queryString += tag.key + ":" + tag.value.value; - if (idx2 != group.length - 1) { - queryString += ","; + function buildQuery(groups) { + let queryObj = { + 'query': '', + 'search': '' + }; + let first = true; + groups.forEach(function (group, idx1) { + const search = group.find(function (x) { return x.type === 'search' }); + if (search) { + queryObj.search = search.value.name; + } + else { + if (!first) { + queryObj.query += ";"; } - }); - queryString += ")" - if (idx1 != groups.length - 1) { - queryString += ";"; + const twoOrMoreElements = group.length > 1; + if (twoOrMoreElements) { + queryObj.query += "(" + } + group.filter(function (x) { return x.type === 'filter' }).forEach(function (tag, idx2) { + queryObj.query += tag.key + "=" + tag.value.value; + if (idx2 != group.length - 1) { + queryObj.query += ","; + } + }); + if (twoOrMoreElements) { + queryObj.query += ")" + } + first = false; } }); - console.log(queryString); - return queryString; + $scope.query({ 'query': queryObj.query, 'search': queryObj.search }); } function groupBy(collection, property) { @@ -113,10 +126,9 @@ app.directive('wzTagFilter', function () { }; $scope.removeTag = (id) => { - const term = $scope.tagList.find(function (x) { return x.id === id }); - term.type === 'filter' ? $scope.filter({ 'filter': { name: term.key, value: 'all' } }) : $scope.search({ 'search': '' }); $scope.tagList.splice($scope.tagList.findIndex(function (x) { return x.id === id }), 1); $scope.groupedTagList = groupBy($scope.tagList, 'key'); + buildQuery($scope.groupedTagList); $scope.showAutocomplete(false); }; diff --git a/public/services/data-factory.js b/public/services/data-factory.js index f1436fc3b9..3ebcfc6b46 100644 --- a/public/services/data-factory.js +++ b/public/services/data-factory.js @@ -67,10 +67,9 @@ export class DataFactory { this.serializeFilters(parameters); // Fetch next items - const path = !options.query ? this.path : this.path + options.query; const firstPage = await this.httpClient.request( 'GET', - path, + this.path, parameters ); this.items = this.items.filter(item => !!item); diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index ddff2cb842..95fe3c8937 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -50,7 +50,7 @@
+ query="ctrl.query(query, search)">
Date: Tue, 4 Dec 2018 12:08:27 +0100 Subject: [PATCH 05/16] Navigate across autocomplete with ketyboard arrow keys --- .../wz-tag-filter/wz-tag-filter.html | 13 ++-- .../directives/wz-tag-filter/wz-tag-filter.js | 64 ++++++++++--------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html index a4f8bf0e4d..34686f493e 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.html +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -3,11 +3,12 @@
  • -
    +
    - - {{tag.value.name}} : {{tag.value.value}} + + {{tag.value.name}} : + {{tag.value.value}} ×
    OR @@ -17,13 +18,13 @@
+ ng-focus='showAutocomplete(true)' ng-blur='showAutocomplete(false)' ng-class='{'invalid-tag': newTag.invalid}' + placeholder='Add filter or search' ng-keyup='!autocompleteEnter && $event.keyCode == 13 ? addTag(true) : addSearchKey($event)' />

{{autocompleteContent.title}}

  • - {{element}} + {{element}}
diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 05cb27c2ce..3665febc82 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -77,20 +77,20 @@ app.directive('wzTagFilter', function () { } else { if (!first) { - queryObj.query += ";"; + queryObj.query += ';'; } const twoOrMoreElements = group.length > 1; if (twoOrMoreElements) { - queryObj.query += "(" + queryObj.query += '(' } group.filter(function (x) { return x.type === 'filter' }).forEach(function (tag, idx2) { - queryObj.query += tag.key + "=" + tag.value.value; + queryObj.query += tag.key + '=' + tag.value.value; if (idx2 != group.length - 1) { - queryObj.query += ","; + queryObj.query += ','; } }); if (twoOrMoreElements) { - queryObj.query += ")" + queryObj.query += ')' } first = false; } @@ -163,6 +163,9 @@ app.directive('wzTagFilter', function () { }; $scope.addSearchKey = (e) => { + if ($scope.autocompleteEnter) { + $scope.autocompleteEnter = false; + } $scope.getAutocompleteContent(); }; @@ -174,6 +177,7 @@ app.directive('wzTagFilter', function () { if (flag) { input.focus(); } + $('#wz-search-filter-bar-autocomplete-list li').removeClass('selected'); }, 100); } @@ -194,37 +198,35 @@ app.directive('wzTagFilter', function () { // to ensure the random number provide enough bits. var firstPart = (Math.random() * 46656) | 0; var secondPart = (Math.random() * 46656) | 0; - firstPart = ("000" + firstPart.toString(36)).slice(-3); - secondPart = ("000" + secondPart.toString(36)).slice(-3); + firstPart = ('000' + firstPart.toString(36)).slice(-3); + secondPart = ('000' + secondPart.toString(36)).slice(-3); return firstPart + secondPart; } $('#wz-search-filter-bar-input').bind('keydown', function (e) { - //let liSelected = document.getElementById('wz-search-filter-bar-autocomplete-list li.selected'); - let liSelected = $('#wz-search-filter-bar-autocomplete-list li.selected'); - if (e.which === 40) { - if (liSelected != 0) { - liSelected.removeClass('selected'); - next = liSelected.next(); - if (next.length > 0) { - liSelected = next.addClass('selected'); - } else { - liSelected = li.eq(0).addClass('selected'); - } - } else { - liSelected = li.eq(0).addClass('selected'); + let $current = $('#wz-search-filter-bar-autocomplete-list li.selected'); + if ($current.length === 0) { + $('#wz-search-filter-bar-autocomplete-list li').first().addClass('selected'); + $current = $('#wz-search-filter-bar-autocomplete-list li.selected'); + } else { + var $next; + switch (e.keyCode) { + case 13: // enter + $scope.autocompleteEnter = true; + $scope.autocompleteContent.isKey ? $scope.addTagKey($current.text().trim()) : $scope.addTagValue($current.text().trim()); + break; + case 38: // if the UP key is pressed + $next = $current.prev(); + break; + case 40: // if the DOWN key is pressed + $next = $current.next(); + break; } - } else if (e.which === 38) { - if (liSelected) { - liSelected.removeClass('selected'); - next = liSelected.prev(); - if (next.length > 0) { - liSelected = next.addClass('selected'); - } else { - liSelected = li.last().addClass('selected'); - } - } else { - liSelected = li.last().addClass('selected'); + if ($next && $next.length > 0 && (e.keyCode === 38 || e.keyCode === 40)) { + var input = document.getElementById('wz-search-filter-bar-input'); + input.focus(); + $('#wz-search-filter-bar-autocomplete-list li').removeClass('selected'); + $next.addClass('selected'); } } }); From 2f4658c616652994b4923cfe1115f60c4ff78c8b Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Tue, 4 Dec 2018 12:40:56 +0100 Subject: [PATCH 06/16] Handled scrollbar when filter is too long --- .../wz-tag-filter/wz-tag-filter.html | 4 ++-- .../directives/wz-tag-filter/wz-tag-filter.js | 3 ++- .../wz-tag-filter/wz-tag-filter.less | 22 +++++-------------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html index 34686f493e..2d5a1aebb7 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.html +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -1,4 +1,4 @@ -
+
    @@ -18,7 +18,7 @@

{{autocompleteContent.title}}

diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 3665febc82..2913b00386 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -171,9 +171,10 @@ app.directive('wzTagFilter', function () { function index_autocomplete(flag = true) { $timeout(function () { + var bar = document.getElementById('wz-search-filter-bar'); var autocomplete = document.getElementById('wz-search-filter-bar-autocomplete'); var input = document.getElementById('wz-search-filter-bar-input'); - autocomplete.style.left = input.offsetLeft + 'px'; + autocomplete.style.left = input.offsetLeft - bar.scrollLeft + 'px'; if (flag) { input.focus(); } diff --git a/public/directives/wz-tag-filter/wz-tag-filter.less b/public/directives/wz-tag-filter/wz-tag-filter.less index 0a8381137c..24736b5961 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.less +++ b/public/directives/wz-tag-filter/wz-tag-filter.less @@ -14,12 +14,13 @@ /* ----------- Wazuh search and filter by tags bar stylesheet --------------- */ /* -------------------------------------------------------------------------- */ -.wz-search-filter-bar { +#wz-search-filter-bar { padding: 8px; margin-top: 15px; + overflow: auto; } -.wz-search-filter-bar > .fa-search{ +#wz-search-filter-bar > .fa-search{ padding: 8px 3px; float: left; opacity: .5; @@ -54,7 +55,7 @@ .wz-tag-item { color: white; border-radius: 3px; - float: left; + display: inline-block; padding: 3px 5px; background: #0079a5; margin: 1px 3px; @@ -76,6 +77,7 @@ padding: 5px; width: auto; margin-left: 5px; + min-width: 225px; flex: 1; } @@ -109,17 +111,3 @@ color:#0079a5; cursor: pointer; } -/* @media (min-width: 1280px) { - .wz-configuration-item { - padding: 5px 0; - } - - .wz-configuration-label { - text-align: right; - } - - .wz-configuration-value { - margin: 0 0 0 30px; - } -} - */ \ No newline at end of file From 3df189f7b90c95ef34f39db49d51d9fdb0502200 Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Tue, 4 Dec 2018 13:39:02 +0100 Subject: [PATCH 07/16] Fix typo --- public/directives/wz-tag-filter/wz-tag-filter.js | 4 ++-- public/templates/agents-prev/agents-prev.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 2913b00386..5eb3df6e97 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -22,7 +22,7 @@ app.directive('wzTagFilter', function () { scope: { path: '=path', keys: '=keys', - query: '&' + queryFn: '&' }, controller( $scope, @@ -95,7 +95,7 @@ app.directive('wzTagFilter', function () { first = false; } }); - $scope.query({ 'query': queryObj.query, 'search': queryObj.search }); + $scope.queryFn({ 'q': queryObj.query, 'search': queryObj.search }); } function groupBy(collection, property) { diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index 95fe3c8937..e9305a40ad 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -50,7 +50,7 @@
+ query-fn="ctrl.query(q, search)">
Date: Tue, 4 Dec 2018 16:03:27 +0100 Subject: [PATCH 08/16] Request changes --- public/controllers/agent/agents-preview.js | 8 -- .../directives/wz-tag-filter/wz-tag-filter.js | 132 ++++++++++-------- 2 files changed, 71 insertions(+), 69 deletions(-) diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index b6349a3207..e980a0a0c6 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -72,14 +72,6 @@ export class AgentsPreviewController { this.load(); } - search(term) { - this.$scope.$broadcast('wazuhSearch', { term }); - } - - filter(filter) { - this.$scope.$broadcast('wazuhFilter', { filter }); - } - query(query, search) { this.$scope.$broadcast('wazuhQuery', { query, search }); } diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 5eb3df6e97..61ca175b4a 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -27,7 +27,9 @@ app.directive('wzTagFilter', function () { controller( $scope, $timeout, - apiReq + apiReq, + $document, + errorHandler ) { const instance = new DataFactory( apiReq, @@ -42,64 +44,72 @@ app.directive('wzTagFilter', function () { $scope.dataModel = []; $scope.addTag = (flag = false) => { - var input = document.getElementById('wz-search-filter-bar-input'); - input.blur(); - const term = $scope.newTag.split(':'); - const obj = { name: term[0], value: term[1] }; - const isFilter = obj.value; - const tag = { - 'id': generateUID(), - 'key': obj.name, - 'value': obj, - 'type': isFilter ? 'filter' : 'search' - }; - const idxSearch = $scope.tagList.find(function (x) { return x.type === 'search' }); - if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id) }; - if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { - $scope.tagList.push(tag); - $scope.groupedTagList = groupBy($scope.tagList, 'key'); - buildQuery($scope.groupedTagList); + try { + const input = $document[0].getElementById('wz-search-filter-bar-input'); + input.blur(); + const term = $scope.newTag.split(':'); + const obj = { name: term[0], value: term[1] }; + const isFilter = obj.value; + const tag = { + 'id': generateUID(), + 'key': obj.name, + 'value': obj, + 'type': isFilter ? 'filter' : 'search' + }; + const idxSearch = $scope.tagList.find(function (x) { return x.type === 'search' }); + if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id) }; + if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { + $scope.tagList.push(tag); + $scope.groupedTagList = groupBy($scope.tagList, 'key'); + buildQuery($scope.groupedTagList); + } + $scope.showAutocomplete(flag); + $scope.newTag = ''; + } catch (error) { + errorHandler.handle(error, 'Add filter'); } - $scope.showAutocomplete(flag); - $scope.newTag = ''; }; - function buildQuery(groups) { - let queryObj = { - 'query': '', - 'search': '' - }; - let first = true; - groups.forEach(function (group, idx1) { - const search = group.find(function (x) { return x.type === 'search' }); - if (search) { - queryObj.search = search.value.name; - } - else { - if (!first) { - queryObj.query += ';'; - } - const twoOrMoreElements = group.length > 1; - if (twoOrMoreElements) { - queryObj.query += '(' + const buildQuery = groups => { + try { + let queryObj = { + 'query': '', + 'search': '' + }; + let first = true; + groups.forEach(function (group, idx1) { + const search = group.find(function (x) { return x.type === 'search' }); + if (search) { + queryObj.search = search.value.name; } - group.filter(function (x) { return x.type === 'filter' }).forEach(function (tag, idx2) { - queryObj.query += tag.key + '=' + tag.value.value; - if (idx2 != group.length - 1) { - queryObj.query += ','; + else { + if (!first) { + queryObj.query += ';'; + } + const twoOrMoreElements = group.length > 1; + if (twoOrMoreElements) { + queryObj.query += '(' } - }); - if (twoOrMoreElements) { - queryObj.query += ')' + group.filter(function (x) { return x.type === 'filter' }).forEach(function (tag, idx2) { + queryObj.query += tag.key + '=' + tag.value.value; + if (idx2 != group.length - 1) { + queryObj.query += ','; + } + }); + if (twoOrMoreElements) { + queryObj.query += ')' + } + first = false; } - first = false; - } - }); - $scope.queryFn({ 'q': queryObj.query, 'search': queryObj.search }); + }); + $scope.queryFn({ 'q': queryObj.query, 'search': queryObj.search }); + } catch (error) { + errorHandler.handle(error, 'Query filter request'); + } } - function groupBy(collection, property) { - var i = 0, val, index, + const groupBy = (collection, property) => { + let i = 0, val, index, values = [], result = []; for (; i < collection.length; i++) { val = collection[i][property]; @@ -137,7 +147,7 @@ app.directive('wzTagFilter', function () { $scope.getAutocompleteContent(); } $scope.isAutocomplete = flag; - index_autocomplete(flag); + indexAutocomplete(flag); }; $scope.getAutocompleteContent = () => { @@ -169,11 +179,11 @@ app.directive('wzTagFilter', function () { $scope.getAutocompleteContent(); }; - function index_autocomplete(flag = true) { + const indexAutocomplete = (flag = true) => { $timeout(function () { - var bar = document.getElementById('wz-search-filter-bar'); - var autocomplete = document.getElementById('wz-search-filter-bar-autocomplete'); - var input = document.getElementById('wz-search-filter-bar-input'); + const bar = $document[0].getElementById('wz-search-filter-bar'); + const autocomplete = $document[0].getElementById('wz-search-filter-bar-autocomplete'); + const input = $document[0].getElementById('wz-search-filter-bar-input'); autocomplete.style.left = input.offsetLeft - bar.scrollLeft + 'px'; if (flag) { input.focus(); @@ -194,11 +204,11 @@ app.directive('wzTagFilter', function () { } }; - function generateUID() { + const generateUID = () => { // I generate the UID from two parts here // to ensure the random number provide enough bits. - var firstPart = (Math.random() * 46656) | 0; - var secondPart = (Math.random() * 46656) | 0; + let firstPart = (Math.random() * 46656) | 0; + let secondPart = (Math.random() * 46656) | 0; firstPart = ('000' + firstPart.toString(36)).slice(-3); secondPart = ('000' + secondPart.toString(36)).slice(-3); return firstPart + secondPart; @@ -210,7 +220,7 @@ app.directive('wzTagFilter', function () { $('#wz-search-filter-bar-autocomplete-list li').first().addClass('selected'); $current = $('#wz-search-filter-bar-autocomplete-list li.selected'); } else { - var $next; + let $next; switch (e.keyCode) { case 13: // enter $scope.autocompleteEnter = true; @@ -224,7 +234,7 @@ app.directive('wzTagFilter', function () { break; } if ($next && $next.length > 0 && (e.keyCode === 38 || e.keyCode === 40)) { - var input = document.getElementById('wz-search-filter-bar-input'); + const input = $document[0].getElementById('wz-search-filter-bar-input'); input.focus(); $('#wz-search-filter-bar-autocomplete-list li').removeClass('selected'); $next.addClass('selected'); From b091c9ac3e84430812c61c6d54197af26e48d70c Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Tue, 4 Dec 2018 17:29:29 +0100 Subject: [PATCH 09/16] Model of available fileds as directive input parameter --- public/controllers/agent/agents-preview.js | 12 ++++++++++++ public/directives/wz-tag-filter/wz-tag-filter.js | 15 ++++++++------- public/templates/agents-prev/agents-prev.html | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index e980a0a0c6..48f56dc7e6 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -124,6 +124,18 @@ export class AgentsPreviewController { const [agentsUnique, agentsTop] = data; const unique = agentsUnique.data.result; + this.searchBarModel = { + 'group': unique.groups, + 'node_name': unique.nodes, + 'version': unique.versions, + 'os.platform': unique.osPlatforms.map(x => x.platform), + 'os.version': unique.osPlatforms.map(x => x.version), + 'os.name': unique.osPlatforms.map(x => x.name), + }; + this.searchBarModel['os.name'] = Array.from(new Set(this.searchBarModel['os.name'])); + this.searchBarModel['os.version'] = Array.from(new Set(this.searchBarModel['os.version'])); + this.searchBarModel['os.platform'] = Array.from(new Set(this.searchBarModel['os.platform'])); + this.groups = unique.groups; this.nodes = unique.nodes.map(item => ({ id: item })); this.versions = unique.versions.map(item => ({ id: item })); diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 61ca175b4a..7bfca4b5a4 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -21,8 +21,9 @@ app.directive('wzTagFilter', function () { restrict: 'E', scope: { path: '=path', - keys: '=keys', - queryFn: '&' + //keys: '=keys', + queryFn: '&', + fieldsModel: '=' }, controller( $scope, @@ -157,16 +158,16 @@ app.directive('wzTagFilter', function () { $scope.autocompleteContent.title = isKey ? 'Filter keys' : 'Values'; if (isKey) { const regex = new RegExp('^' + term[0], 'i'); - $scope.keys.forEach(function (key) { + for (var key in $scope.fieldsModel) { if (regex.test(key)) { $scope.autocompleteContent.list.push(key); } - }); + } } else { const regex = new RegExp('^' + term[1], 'i'); const model = $scope.dataModel.find(function (x) { return x.key === $scope.newTag.split(':')[0] }) if (model) { - model.list = Array.isArray(model.list[0]) ? model.list[0] : model.list; + //model.list = Array.isArray(model.list[0]) ? model.list[0] : model.list; $scope.autocompleteContent.list = [...new Set(model.list.filter(function (x) { return regex.test(x) }))]; } } @@ -195,8 +196,8 @@ app.directive('wzTagFilter', function () { const load = async () => { try { const result = await instance.fetch(); - $scope.keys.forEach(function (key) { - $scope.dataModel.push({ 'key': key, 'list': result.items.map(function (x) { return key.split('.').reduce((a, b) => a ? a[b] : '', x) }) }); + Object.keys($scope.fieldsModel).forEach(function(key) { + $scope.dataModel.push({ 'key': key, 'list': $scope.fieldsModel[key]}); }); return; } catch (error) { diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index e9305a40ad..b6481f9b21 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -50,7 +50,7 @@
+ query-fn="ctrl.query(q, search)" fields-model="ctrl.searchBarModel">
Date: Tue, 4 Dec 2018 17:30:18 +0100 Subject: [PATCH 10/16] Fix typo --- public/directives/wz-tag-filter/wz-tag-filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 7bfca4b5a4..056684231c 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -158,7 +158,7 @@ app.directive('wzTagFilter', function () { $scope.autocompleteContent.title = isKey ? 'Filter keys' : 'Values'; if (isKey) { const regex = new RegExp('^' + term[0], 'i'); - for (var key in $scope.fieldsModel) { + for (let key in $scope.fieldsModel) { if (regex.test(key)) { $scope.autocompleteContent.list.push(key); } From 19e2d1bb18ed721144f48a47a6e1b7be91c5d903 Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Tue, 4 Dec 2018 17:31:24 +0100 Subject: [PATCH 11/16] Removed keys param of directive --- public/directives/wz-tag-filter/wz-tag-filter.js | 2 -- public/templates/agents-prev/agents-prev.html | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 056684231c..e254df702c 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -21,7 +21,6 @@ app.directive('wzTagFilter', function () { restrict: 'E', scope: { path: '=path', - //keys: '=keys', queryFn: '&', fieldsModel: '=' }, @@ -167,7 +166,6 @@ app.directive('wzTagFilter', function () { const regex = new RegExp('^' + term[1], 'i'); const model = $scope.dataModel.find(function (x) { return x.key === $scope.newTag.split(':')[0] }) if (model) { - //model.list = Array.isArray(model.list[0]) ? model.list[0] : model.list; $scope.autocompleteContent.list = [...new Set(model.list.filter(function (x) { return regex.test(x) }))]; } } diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html index b6481f9b21..07ddc836d3 100644 --- a/public/templates/agents-prev/agents-prev.html +++ b/public/templates/agents-prev/agents-prev.html @@ -49,8 +49,7 @@
- +
Date: Wed, 5 Dec 2018 09:37:05 +0100 Subject: [PATCH 12/16] Added remove groups posibility --- public/controllers/agent/agents-preview.js | 1 + public/directives/wz-tag-filter/wz-tag-filter.html | 3 ++- public/directives/wz-tag-filter/wz-tag-filter.js | 14 +++++++++----- public/directives/wz-tag-filter/wz-tag-filter.less | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index 48f56dc7e6..681d427d88 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -125,6 +125,7 @@ export class AgentsPreviewController { const unique = agentsUnique.data.result; this.searchBarModel = { + 'status': ['Active', 'Disconnected', 'Never connected'], 'group': unique.groups, 'node_name': unique.nodes, 'version': unique.versions, diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html index 2d5a1aebb7..e53c6b7ea5 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.html +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -9,10 +9,11 @@ {{tag.value.name}} : {{tag.value.value}} - × + ×
OR
+ ×
AND diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index e254df702c..0f30db0aca 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -57,7 +57,7 @@ app.directive('wzTagFilter', function () { 'type': isFilter ? 'filter' : 'search' }; const idxSearch = $scope.tagList.find(function (x) { return x.type === 'search' }); - if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id) }; + if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id, false) }; if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { $scope.tagList.push(tag); $scope.groupedTagList = groupBy($scope.tagList, 'key'); @@ -135,8 +135,12 @@ app.directive('wzTagFilter', function () { $scope.addTag(); }; - $scope.removeTag = (id) => { - $scope.tagList.splice($scope.tagList.findIndex(function (x) { return x.id === id }), 1); + $scope.removeTag = (id, deleteGroup) => { + if (deleteGroup) { + $scope.tagList = $scope.tagList.filter(function (x) { return x.key !== id }); + } else { + $scope.tagList.splice($scope.tagList.findIndex(function (x) { return x.id === id }), 1); + } $scope.groupedTagList = groupBy($scope.tagList, 'key'); buildQuery($scope.groupedTagList); $scope.showAutocomplete(false); @@ -194,8 +198,8 @@ app.directive('wzTagFilter', function () { const load = async () => { try { const result = await instance.fetch(); - Object.keys($scope.fieldsModel).forEach(function(key) { - $scope.dataModel.push({ 'key': key, 'list': $scope.fieldsModel[key]}); + Object.keys($scope.fieldsModel).forEach(function (key) { + $scope.dataModel.push({ 'key': key, 'list': $scope.fieldsModel[key] }); }); return; } catch (error) { diff --git a/public/directives/wz-tag-filter/wz-tag-filter.less b/public/directives/wz-tag-filter/wz-tag-filter.less index 24736b5961..634f9ceea6 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.less +++ b/public/directives/wz-tag-filter/wz-tag-filter.less @@ -72,6 +72,12 @@ .wz-tag-remove-button{ color: #005571; } + +.wz-tag-remove-button-group{ + padding: 4px 2px; + color: gray; +} + .wz-search-filter-bar-input{ border: none; padding: 5px; From 9ac08cde708afb5f063643d5158a83650bf82b72 Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Wed, 5 Dec 2018 11:37:57 +0100 Subject: [PATCH 13/16] Some bug fixes --- .../wz-tag-filter/wz-tag-filter.html | 2 +- .../directives/wz-tag-filter/wz-tag-filter.js | 42 +++++++++++-------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.html b/public/directives/wz-tag-filter/wz-tag-filter.html index e53c6b7ea5..75e440ea64 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.html +++ b/public/directives/wz-tag-filter/wz-tag-filter.html @@ -13,7 +13,7 @@
OR
- × + ×
AND diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 0f30db0aca..e691c406c6 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -49,22 +49,30 @@ app.directive('wzTagFilter', function () { input.blur(); const term = $scope.newTag.split(':'); const obj = { name: term[0], value: term[1] }; + const onlyCharsNums = /[^A-Za-z0-9]+/; const isFilter = obj.value; - const tag = { - 'id': generateUID(), - 'key': obj.name, - 'value': obj, - 'type': isFilter ? 'filter' : 'search' - }; - const idxSearch = $scope.tagList.find(function (x) { return x.type === 'search' }); - if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id, false) }; - if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { - $scope.tagList.push(tag); - $scope.groupedTagList = groupBy($scope.tagList, 'key'); - buildQuery($scope.groupedTagList); + if ((isFilter && Object.keys($scope.fieldsModel).indexOf(obj.name) === -1) || + (isFilter && onlyCharsNums.test(obj.value)) || + (!isFilter && onlyCharsNums.test(obj.name))) { + $scope.showAutocomplete(flag); + $scope.newTag = ''; + } else { + const tag = { + 'id': generateUID(), + 'key': obj.name, + 'value': obj, + 'type': isFilter ? 'filter' : 'search' + }; + const idxSearch = $scope.tagList.find(function (x) { return x.type === 'search' }); + if (!isFilter && idxSearch) { $scope.removeTag(idxSearch.id, false) }; + if (!$scope.tagList.find(function (x) { return x.type === 'filter' && x.key === tag.key && x.value.value === tag.value.value })) { + $scope.tagList.push(tag); + $scope.groupedTagList = groupBy($scope.tagList, 'key'); + buildQuery($scope.groupedTagList); + } + $scope.showAutocomplete(flag); + $scope.newTag = ''; } - $scope.showAutocomplete(flag); - $scope.newTag = ''; } catch (error) { errorHandler.handle(error, 'Add filter'); } @@ -160,17 +168,15 @@ app.directive('wzTagFilter', function () { $scope.autocompleteContent = { 'title': '', 'isKey': isKey, 'list': [] }; $scope.autocompleteContent.title = isKey ? 'Filter keys' : 'Values'; if (isKey) { - const regex = new RegExp('^' + term[0], 'i'); for (let key in $scope.fieldsModel) { - if (regex.test(key)) { + if (key.toUpperCase().includes(term[0].toUpperCase())) { $scope.autocompleteContent.list.push(key); } } } else { - const regex = new RegExp('^' + term[1], 'i'); const model = $scope.dataModel.find(function (x) { return x.key === $scope.newTag.split(':')[0] }) if (model) { - $scope.autocompleteContent.list = [...new Set(model.list.filter(function (x) { return regex.test(x) }))]; + $scope.autocompleteContent.list = [...new Set(model.list.filter(function (x) { return x.toUpperCase().includes(term[1].toUpperCase()); }))]; } } }; From 62f7a49213cad7bfb60f59014cf0947f263724f7 Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Wed, 5 Dec 2018 13:25:59 +0100 Subject: [PATCH 14/16] Handle some casuistics --- public/directives/wz-tag-filter/wz-tag-filter.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index e691c406c6..2be98182c4 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -49,11 +49,12 @@ app.directive('wzTagFilter', function () { input.blur(); const term = $scope.newTag.split(':'); const obj = { name: term[0], value: term[1] }; - const onlyCharsNums = /[^A-Za-z0-9]+/; + const onlyCharsNums = /[^A-Za-z0-9 .-]+/; const isFilter = obj.value; if ((isFilter && Object.keys($scope.fieldsModel).indexOf(obj.name) === -1) || (isFilter && onlyCharsNums.test(obj.value)) || - (!isFilter && onlyCharsNums.test(obj.name))) { + (!isFilter && onlyCharsNums.test(obj.name)) || + (!isFilter && (!obj.name || /^\s*$/.test(obj.name)))) { $scope.showAutocomplete(flag); $scope.newTag = ''; } else { @@ -122,7 +123,7 @@ app.directive('wzTagFilter', function () { for (; i < collection.length; i++) { val = collection[i][property]; index = values.indexOf(val); - if (index > -1) + if (index > -1 && collection[i].type === 'filter') result[index].push(collection[i]); else { values.push(val); @@ -225,15 +226,17 @@ app.directive('wzTagFilter', function () { $('#wz-search-filter-bar-input').bind('keydown', function (e) { let $current = $('#wz-search-filter-bar-autocomplete-list li.selected'); - if ($current.length === 0) { + if ($current.length === 0 && (e.keyCode === 38 || e.keyCode === 40)) { $('#wz-search-filter-bar-autocomplete-list li').first().addClass('selected'); $current = $('#wz-search-filter-bar-autocomplete-list li.selected'); } else { let $next; switch (e.keyCode) { case 13: // enter - $scope.autocompleteEnter = true; - $scope.autocompleteContent.isKey ? $scope.addTagKey($current.text().trim()) : $scope.addTagValue($current.text().trim()); + if ($current.text().trim() && !/^\s*$/.test($current.text().trim())) { + $scope.autocompleteEnter = true; + $scope.autocompleteContent.isKey ? $scope.addTagKey($current.text().trim()) : $scope.addTagValue($current.text().trim()); + } break; case 38: // if the UP key is pressed $next = $current.prev(); From 3c2b3ac23021f697c63e1691b979fa5b22740f3f Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Wed, 5 Dec 2018 13:48:08 +0100 Subject: [PATCH 15/16] Allow special characters --- public/directives/wz-tag-filter/wz-tag-filter.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index 2be98182c4..ce86fd0b95 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -49,11 +49,8 @@ app.directive('wzTagFilter', function () { input.blur(); const term = $scope.newTag.split(':'); const obj = { name: term[0], value: term[1] }; - const onlyCharsNums = /[^A-Za-z0-9 .-]+/; const isFilter = obj.value; if ((isFilter && Object.keys($scope.fieldsModel).indexOf(obj.name) === -1) || - (isFilter && onlyCharsNums.test(obj.value)) || - (!isFilter && onlyCharsNums.test(obj.name)) || (!isFilter && (!obj.name || /^\s*$/.test(obj.name)))) { $scope.showAutocomplete(flag); $scope.newTag = ''; From 2c0b5f2f8e7a86946f9f477819c573b84745fafc Mon Sep 17 00:00:00 2001 From: JuanCarlos Date: Wed, 5 Dec 2018 16:25:51 +0100 Subject: [PATCH 16/16] Change error catches messages --- public/directives/wz-tag-filter/wz-tag-filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/directives/wz-tag-filter/wz-tag-filter.js b/public/directives/wz-tag-filter/wz-tag-filter.js index ce86fd0b95..4963826bd5 100644 --- a/public/directives/wz-tag-filter/wz-tag-filter.js +++ b/public/directives/wz-tag-filter/wz-tag-filter.js @@ -72,7 +72,7 @@ app.directive('wzTagFilter', function () { $scope.newTag = ''; } } catch (error) { - errorHandler.handle(error, 'Add filter'); + errorHandler.handle(error, 'Error adding filter'); } }; @@ -110,7 +110,7 @@ app.directive('wzTagFilter', function () { }); $scope.queryFn({ 'q': queryObj.query, 'search': queryObj.search }); } catch (error) { - errorHandler.handle(error, 'Query filter request'); + errorHandler.handle(error, 'Error in query request'); } }