diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js
index a50b511b7e..681d427d88 100644
--- a/public/controllers/agent/agents-preview.js
+++ b/public/controllers/agent/agents-preview.js
@@ -72,12 +72,8 @@ 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 });
}
showAgent(agent) {
@@ -128,6 +124,19 @@ export class AgentsPreviewController {
const [agentsUnique, agentsTop] = data;
const unique = agentsUnique.data.result;
+ this.searchBarModel = {
+ 'status': ['Active', 'Disconnected', 'Never connected'],
+ '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/index.js b/public/directives/index.js
index 91d9f92c5c..9127222ba9 100644
--- a/public/directives/index.js
+++ b/public/directives/index.js
@@ -18,4 +18,6 @@ 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-config-viewer/wz-config-viewer';
\ No newline at end of file
+import './wz-tag-filter/wz-tag-filter';
+import './wz-tag-filter/wz-tag-filter.less';
+import './wz-config-viewer/wz-config-viewer';
diff --git a/public/directives/wz-table/lib/data.js b/public/directives/wz-table/lib/data.js
index 30624ecf79..4a2b16e92c 100644
--- a/public/directives/wz-table/lib/data.js
+++ b/public/directives/wz-table/lib/data.js
@@ -64,10 +64,48 @@ 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,
+ term,
+ instance,
+ wzTableFilter,
+ $scope,
+ fetch,
+ errorHandler
+) {
+ try {
+ $scope.error = false;
+ $scope.wazuh_table_loading = true;
+ 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;
+ $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..1428895cce 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, parameters.search);
+}
+
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 ff01e79943..2c62d7b4b3 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: {
@@ -135,6 +135,17 @@ app.directive('wzTable', function() {
errorHandler
);
+ const query = async (query, search) =>
+ queryData(
+ query,
+ search,
+ instance,
+ wzTableFilter,
+ $scope,
+ fetch,
+ errorHandler
+ );
+
const realTimeFunction = async () => {
try {
$scope.error = false;
@@ -184,7 +195,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);
};
@@ -204,6 +215,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
new file mode 100644
index 0000000000..75e440ea64
--- /dev/null
+++ b/public/directives/wz-tag-filter/wz-tag-filter.html
@@ -0,0 +1,33 @@
+
\ 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..4963826bd5
--- /dev/null
+++ b/public/directives/wz-tag-filter/wz-tag-filter.js
@@ -0,0 +1,257 @@
+/*
+ * 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',
+ queryFn: '&',
+ fieldsModel: '='
+ },
+ controller(
+ $scope,
+ $timeout,
+ apiReq,
+ $document,
+ errorHandler
+ ) {
+ const instance = new DataFactory(
+ apiReq,
+ $scope.path,
+ {}
+ );
+
+ $scope.tagList = [];
+ $scope.groupedTagList = [];
+ $scope.newTag = '';
+ $scope.isAutocomplete = false;
+ $scope.dataModel = [];
+
+ $scope.addTag = (flag = false) => {
+ 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;
+ if ((isFilter && Object.keys($scope.fieldsModel).indexOf(obj.name) === -1) ||
+ (!isFilter && (!obj.name || /^\s*$/.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 = '';
+ }
+ } catch (error) {
+ errorHandler.handle(error, 'Error adding filter');
+ }
+ };
+
+ 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;
+ }
+ else {
+ if (!first) {
+ queryObj.query += ';';
+ }
+ 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;
+ }
+ });
+ $scope.queryFn({ 'q': queryObj.query, 'search': queryObj.search });
+ } catch (error) {
+ errorHandler.handle(error, 'Error in query request');
+ }
+ }
+
+ const groupBy = (collection, property) => {
+ let i = 0, val, index,
+ values = [], result = [];
+ for (; i < collection.length; i++) {
+ val = collection[i][property];
+ index = values.indexOf(val);
+ if (index > -1 && collection[i].type === 'filter')
+ result[index].push(collection[i]);
+ else {
+ values.push(val);
+ result.push([collection[i]]);
+ }
+ }
+ return result;
+ }
+
+ $scope.addTagKey = (key) => {
+ $scope.newTag = key + ':';
+ $scope.showAutocomplete(true);
+ };
+
+ $scope.addTagValue = (value) => {
+ $scope.newTag = $scope.newTag.substring(0, $scope.newTag.indexOf(':') + 1);
+ $scope.newTag += value;
+ $scope.addTag();
+ };
+
+ $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);
+ };
+
+ $scope.showAutocomplete = (flag) => {
+ if (flag) {
+ $scope.getAutocompleteContent();
+ }
+ $scope.isAutocomplete = flag;
+ indexAutocomplete(flag);
+ };
+
+ $scope.getAutocompleteContent = () => {
+ const term = $scope.newTag.split(':');
+ const isKey = !term[1] && $scope.newTag.indexOf(':') === -1;
+ $scope.autocompleteContent = { 'title': '', 'isKey': isKey, 'list': [] };
+ $scope.autocompleteContent.title = isKey ? 'Filter keys' : 'Values';
+ if (isKey) {
+ for (let key in $scope.fieldsModel) {
+ if (key.toUpperCase().includes(term[0].toUpperCase())) {
+ $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 = [...new Set(model.list.filter(function (x) { return x.toUpperCase().includes(term[1].toUpperCase()); }))];
+ }
+ }
+ };
+
+ $scope.addSearchKey = (e) => {
+ if ($scope.autocompleteEnter) {
+ $scope.autocompleteEnter = false;
+ }
+ $scope.getAutocompleteContent();
+ };
+
+ const indexAutocomplete = (flag = true) => {
+ $timeout(function () {
+ 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();
+ }
+ $('#wz-search-filter-bar-autocomplete-list li').removeClass('selected');
+ }, 100);
+ }
+
+ 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] });
+ });
+ return;
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ };
+
+ const generateUID = () => {
+ // I generate the UID from two parts here
+ // to ensure the random number provide enough bits.
+ 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;
+ }
+
+ $('#wz-search-filter-bar-input').bind('keydown', function (e) {
+ let $current = $('#wz-search-filter-bar-autocomplete-list li.selected');
+ 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
+ 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();
+ break;
+ case 40: // if the DOWN key is pressed
+ $next = $current.next();
+ break;
+ }
+ if ($next && $next.length > 0 && (e.keyCode === 38 || e.keyCode === 40)) {
+ const input = $document[0].getElementById('wz-search-filter-bar-input');
+ input.focus();
+ $('#wz-search-filter-bar-autocomplete-list li').removeClass('selected');
+ $next.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
new file mode 100644
index 0000000000..634f9ceea6
--- /dev/null
+++ b/public/directives/wz-tag-filter/wz-tag-filter.less
@@ -0,0 +1,119 @@
+/*
+ * 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;
+ overflow: auto;
+}
+
+#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;
+ display: flex;
+}
+
+.wz-tags > ul li .grouped{
+ background: #d9d9d9;
+ border-radius: 3px;
+ padding: 3px;
+ height: 36px;
+ margin-top: -3px;
+ display: flex;
+}
+
+.wz-tag-item {
+ color: white;
+ border-radius: 3px;
+ display: inline-block;
+ padding: 3px 5px;
+ background: #0079a5;
+ 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;
+}
+
+.wz-tag-remove-button-group{
+ padding: 4px 2px;
+ color: gray;
+}
+
+.wz-search-filter-bar-input{
+ border: none;
+ padding: 5px;
+ width: auto;
+ margin-left: 5px;
+ min-width: 225px;
+ flex: 1;
+}
+
+.wz-search-filter-bar-autocomplete{
+ min-width: 200px;
+ background: #000000d9;
+ position: absolute;
+ border: 1px solid black;
+ border-radius: 5px;
+ padding: 10px 0;
+ margin-top: 30px;
+}
+
+.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, .wz-search-filter-bar-autocomplete ul li.selected{
+ background: black;
+ color:#0079a5;
+ cursor: pointer;
+}
diff --git a/public/templates/agents-prev/agents-prev.html b/public/templates/agents-prev/agents-prev.html
index 8a1d03fc79..07ddc836d3 100644
--- a/public/templates/agents-prev/agents-prev.html
+++ b/public/templates/agents-prev/agents-prev.html
@@ -34,85 +34,26 @@
Last registered agent
-
{{ctrl.lastAgent.name}}
-
{{ctrl.lastAgent.name}} (manager)
+
{{ctrl.lastAgent.name}}
+
{{ctrl.lastAgent.name}}
+ (manager)
Higher activity
-
{{ctrl.mostActiveAgent.name}}
+
{{ctrl.mostActiveAgent.name}}
{{ctrl.mostActiveAgent.name}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
@@ -122,4 +63,4 @@
-
+
\ No newline at end of file