Revert "Adapt 7.5.0 in 3.11 (#1960)"

This reverts commit 8f9c9aa95a.
This commit is contained in:
JuanCarlos 2019-12-17 10:30:55 +01:00
parent 8f9c9aa95a
commit d750302f09
No known key found for this signature in database
GPG Key ID: B1C4FB733616273A
30 changed files with 2832 additions and 806 deletions

View File

@ -27,13 +27,6 @@ All notable changes to the Wazuh app project will be documented in this file.
- Fixed error exporting as CSV the files into a group [#1833](https://github.com/wazuh/wazuh-kibana-app/issues/1833).
## Wazuh v3.10.2 - Kibana v7.5.0 - Revision 550
### Added
- Support for Kibana v7.5.0
## Wazuh v3.10.2 - Kibana v7.3.2 - Revision 546
### Added

View File

@ -32,8 +32,8 @@ Visualize and analyze Wazuh alerts stored in Elasticsearch using our Kibana app
- Wazuh HIDS 3.11.0
- Wazuh RESTful API 3.11.0
- Kibana 7.5.0
- Elasticsearch 7.5.0
- Kibana 7.4.2
- Elasticsearch 7.4.2
## Installation
@ -42,13 +42,13 @@ Install the app
- With sudo:
```
sudo -u kibana /usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.5.0.zip
sudo -u kibana /usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.4.2.zip
```
- Without sudo:
```
su -c '/usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.5.0.zip' kibana
su -c '/usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.4.2.zip' kibana
```
Restart Kibana
@ -105,13 +105,13 @@ Install the app
- With sudo:
```
sudo -u kibana /usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.5.0.zip
sudo -u kibana /usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.4.2.zip
```
- Without sudo:
```
su -c '/usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.5.0.zip' kibana
su -c '/usr/share/kibana/bin/kibana-plugin install https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.4.2.zip' kibana
```
Restart Kibana
@ -210,13 +210,10 @@ service kibana restart
| 7.3.1 | 3.10.1 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.1_7.3.1.zip> |
| 7.3.2 | 3.10.1 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.1_7.3.2.zip> |
| 6.8.3 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_6.8.3.zip> |
| 6.8.4 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_6.8.4.zip> |
| 6.8.5 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_6.8.5.zip> |
| 7.3.2 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_7.3.2.zip> |
| 7.4.0 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_7.4.0.zip> |
| 7.4.1 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_7.4.1.zip> |
| 7.4.2 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_7.4.2.zip> |
| 7.5.0 | 3.10.2 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.10.2_7.5.0.zip> |
| 6.8.3 | 3.11.0 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_6.8.3.zip> |
| 7.2.0 | 3.11.0 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.2.0.zip> |
| 7.4.2 | 3.11.0 | /usr/share/kibana/bin/kibana-plugin install <https://packages.wazuh.com/wazuhapp/wazuhapp-3.11.0_7.4.2.zip> |

View File

@ -14,11 +14,11 @@
import { initApp } from './init';
import { resolve } from 'path';
export default function (kibana) {
return new kibana.Plugin({
require: ['kibana', 'elasticsearch'],
export default kibana =>
new kibana.Plugin({
id: 'wazuh',
name: 'wazuh',
require: ['kibana', 'elasticsearch'],
uiExports: {
app: {
id: 'wazuh',
@ -37,48 +37,20 @@ export default function (kibana) {
});
}
},
config(Joi) {
return Joi.object({
enabled: Joi.boolean().default(true),
}).default();
},
init(server, options) { // eslint-disable-line no-unused-vars
init(server, options) {
// Kibana spaces locker
const xpackMainPlugin = server.plugins.xpack_main;
if (xpackMainPlugin) {
const featureId = 'wazuh';
if (xpackMainPlugin) {
xpackMainPlugin.registerFeature({
id: featureId,
id: 'wazuh',
name: 'Wazuh',
navLinkId: featureId,
icon: 'questionInCircle',
app: [featureId, 'kibana', 'elasticsearch'],
catalogue: [],
privileges: {
all: {
api: [],
savedObject: {
all: [],
read: [],
},
ui: ['show'],
},
read: {
api: [],
savedObject: {
all: [],
read: [],
},
ui: ['show'],
},
},
app: ['wazuh', 'kibana', 'elasticsearch'],
navLinkId: 'wazuh',
privileges: {}
});
}
// Add server routes and initialize the plugin here
initApp(server);
return initApp(server, options);
}
});
}

View File

@ -28,7 +28,7 @@ export function initApp(server) {
log('init:initApp', `Waiting for Kibana migration jobs`, 'debug');
server.kibanaMigrator
.runMigrations()
.awaitMigration()
.then(() => {
log(
'init:initApp',

View File

@ -4,7 +4,7 @@
"revision": "0561",
"code": "0561-0",
"kibana": {
"version": "7.5.0"
"version": "7.4.2"
},
"description": "Wazuh app",
"main": "index.js",

View File

@ -120,7 +120,7 @@ export class WelcomeScreen extends Component {
<EuiSpacer size="l" />
<EuiFlexGrid columns={2}>
{this.buildTabCard('general', 'dashboardApp')}
{this.buildTabCard('fim', 'filebeatApp')}
{this.buildTabCard('fim', 'loggingApp')}
{this.buildTabCard('configuration', 'gear')}
{this.buildTabCard('syscollector', 'notebookApp')}
</EuiFlexGrid>

View File

@ -126,7 +126,7 @@ export class WelcomeScreen extends Component {
</EuiFlexGroup>
<EuiFlexGrid columns={2}>
{this.buildTabCard('general', 'dashboardApp')}
{this.buildTabCard('fim', 'filebeatApp')}
{this.buildTabCard('fim', 'loggingApp')}
{this.props.extensions.aws &&
this.buildTabCard('aws', 'logoAWSMono')}
</EuiFlexGrid>

View File

@ -11,7 +11,7 @@
<md-button class="wz-menu-button" ng-href="#/manager" ng-click="setMenuNavItem('manager')"
ng-class="{'wz-menu-active': menuNavItem === 'manager' }" aria-label="Cluster tab"
data-test-subj="wzMenuManagement">
<react-component name="EuiIcon" class="kbnGlobalNavLink__euiIcon" props="{type:'managementApp'}" /> Management
<react-component name="EuiIcon" class="kbnGlobalNavLink__euiIcon" props="{type:'infraApp'}" /> Management
</md-button>
<md-button class="wz-menu-button" ng-href="#/agents-preview" ng-click="setMenuNavItem('agents-preview')"

View File

@ -39,7 +39,7 @@ export class VisHandlers {
* Get all applied filters
* @param {*} syscollector
*/
async getAppliedFilters(syscollector) {
getAppliedFilters(syscollector) {
const appliedFilters = {};
if (syscollector) {
@ -56,32 +56,34 @@ export class VisHandlers {
}
// Check raw response from all rendered tables
let tables = this.list.filter(item => (((item || {}).vis || {})._state || {}).type === 'table');
for (let i = 0; i < tables.length; i++) {
const columns = [];
const title = tables[i].vis._state.title || tables[i].dataLoader.previousVisState.title || 'Table';
const item = await tables[i].fetch();
for (const table of item.value.visData.tables) {
columns.push(...table.columns.map(t => t.name));
}
tables[i] = !!(((((item || {}).value || {}).visData || {}).tables || [])[0] || {}).rows
? {
rows: item.value.visData.tables[0].rows.map(x => { return Object.values(x) }),
title,
columns
const tables = this.list
.filter(item => (((item || {}).vis || {})._state || {}).type === 'table')
.map(item => {
const columns = [];
for (const table of item.dataLoader.visData.tables) {
columns.push(...table.columns.map(t => t.name));
}
: false;
}
return !!(((item || {}).vis || {}).searchSource || {}).rawResponse
? {
rawResponse: item.vis.searchSource.rawResponse,
title:
item.vis.title ||
item.dataLoader.previousVisState.title ||
'Table',
columns
}
: false;
});
if (this.list && this.list.length) {
const visualization = this.list[0].vis;
// Parse applied filters for the first visualization
const filters = visualization.API.queryFilter.getFilters();
// Parse current time range
const { from, to } = visualization.filters.timeRange;
const { from, to } = visualization.API.timeFilter.getTime();
const { query } = visualization.searchSource._fields.query;
// Parse applied filters for the first visualization
const filters = visualization.searchSource._fields.filter;
Object.assign(appliedFilters, {
filters,
@ -107,7 +109,7 @@ export class VisHandlers {
item.vis &&
item.vis.title !== 'Agents status' &&
((item.dataLoader || {}).previousVisState || {}).title !==
'Agents status' &&
'Agents status' &&
item.vis.searchSource &&
item.vis.searchSource.rawResponse &&
item.vis.searchSource.rawResponse.hits &&

File diff suppressed because it is too large Load Diff

View File

@ -11,14 +11,14 @@
*/
import $ from 'jquery';
import { uiModules } from 'ui/modules';
import { getVisualizeLoader } from 'ui/visualize/loader';
import { getVisualizeLoader } from './loader';
import { timefilter } from 'ui/timefilter';
import dateMath from '@elastic/datemath';
const app = uiModules.get('app/wazuh', []);
let lockFields = false;
app.directive('kbnVis', function () {
app.directive('kbnVis', function() {
return {
restrict: 'E',
scope: {
@ -160,18 +160,9 @@ app.directive('kbnVis', function () {
timeRange:
isAgentStatus && timeFilterSeconds < 900
? { from: 'now-15m', to: 'now', mode: 'quick' }
: timefilter.getTime(),
filters: isAgentStatus ? [] : discoverList[1] || []
: timefilter.getTime()
});
if (!isAgentStatus) {
visHandler.update({
query: discoverList[0]
});
}
setSearchSource(discoverList);
$rootScope.rendered = true;
}
}
} catch (error) {
@ -228,10 +219,10 @@ app.directive('kbnVis', function () {
const destroyAll = () => {
try {
visualization.destroy();
} catch (error) { } // eslint-disable-line
} catch (error) {} // eslint-disable-line
try {
visHandler.destroy();
} catch (error) { } // eslint-disable-line
} catch (error) {} // eslint-disable-line
};
$scope.$on('$destroy', () => {
@ -264,7 +255,7 @@ app.directive('kbnVis', function () {
$rootScope.loadingStatus = `Rendering visualizations... ${
currentCompleted > 100 ? 100 : currentCompleted
} %`;
} %`;
if (currentCompleted >= 100) {
$rootScope.rendered = true;

View File

@ -0,0 +1,548 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-ignore
import { EventEmitter } from 'events';
import { debounce, forEach, get, isEqual } from 'lodash';
import * as Rx from 'rxjs';
import { share } from 'rxjs/operators';
// @ts-ignore
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { toastNotifications } from 'ui/notify';
// @ts-ignore
import { registries } from 'plugins/interpreter/registries';
// @ts-ignore
import { Inspector } from 'ui/inspector';
// @ts-ignore
import { Adapters } from 'ui/inspector/types';
// @ts-ignore
import { PersistedState } from 'ui/persisted_state';
// @ts-ignore
import { IPrivate } from 'ui/private';
// @ts-ignore
import { RenderCompleteHelper } from 'ui/render_complete';
// @ts-ignore
import { AppState } from 'ui/state_management/app_state';
// @ts-ignore
import { timefilter } from 'ui/timefilter';
// @ts-ignore
import { RequestHandlerParams, Vis } from 'ui/vis';
// @ts-ignore untyped dependency
import { VisFiltersProvider } from 'ui/vis/vis_filters';
// @ts-ignore
import { PipelineDataLoader } from 'ui/visualize/loader/pipeline_data_loader';
import { visualizationLoader } from 'ui/visualize/loader';
import { VisualizeDataLoader } from './visualize_data_loader';
import { onlyDisabledFiltersChanged } from 'plugins/data';
import { DataAdapter, RequestAdapter } from 'ui/inspector/adapters';
import { getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities';
import {
VisResponseData,
VisSavedObject,
VisualizeLoaderParams,
VisualizeUpdateParams,
} from 'ui/visualize/loader';
// @ts-ignore
import { queryGeohashBounds } from 'ui/visualize/loader/utils';
interface EmbeddedVisualizeHandlerParams extends VisualizeLoaderParams {
Private: IPrivate;
queryFilter: any;
autoFetch?: boolean;
pipelineDataLoader?: boolean;
}
const RENDER_COMPLETE_EVENT = 'render_complete';
const DATA_SHARED_ITEM = 'data-shared-item';
const LOADING_ATTRIBUTE = 'data-loading';
const RENDERING_COUNT_ATTRIBUTE = 'data-rendering-count';
/**
* A handler to the embedded visualization. It offers several methods to interact
* with the visualization.
*/
export class EmbeddedVisualizeHandler {
/**
* This observable will emit every time new data is loaded for the
* visualization. The emitted value is the loaded data after it has
* been transformed by the visualization's response handler.
* This should not be used by any plugin.
* @ignore
*/
public readonly data$: Rx.Observable<any>;
public readonly inspectorAdapters: Adapters = {};
private vis: Vis;
private handlers: any;
private loaded: boolean = false;
private destroyed: boolean = false;
private pipelineDataLoader: boolean = false;
private listeners = new EventEmitter();
private firstRenderComplete: Promise<void>;
private renderCompleteHelper: RenderCompleteHelper;
private shouldForceNextFetch: boolean = false;
private debouncedFetchAndRender = debounce(() => {
if (this.destroyed) {
return;
}
const forceFetch = this.shouldForceNextFetch;
this.shouldForceNextFetch = false;
this.fetch(forceFetch).then(this.render);
}, 100);
private dataLoaderParams: RequestHandlerParams;
private readonly appState?: AppState;
private uiState: PersistedState;
private dataLoader: VisualizeDataLoader | PipelineDataLoader;
private dataSubject: Rx.Subject<any>;
private actions: any = {};
private events$: Rx.Observable<any>;
private autoFetch: boolean;
private abortController?: AbortController;
private autoRefreshFetchSubscription: Rx.Subscription | undefined;
constructor(
private readonly element: HTMLElement,
savedObject: VisSavedObject,
params: EmbeddedVisualizeHandlerParams
) {
const { searchSource, vis } = savedObject;
const {
appState,
uiState,
queryFilter,
timeRange,
filters,
query,
autoFetch = true,
pipelineDataLoader = false,
Private,
} = params;
this.dataLoaderParams = {
searchSource,
timeRange,
query,
queryFilter,
filters,
uiState,
aggs: vis.getAggConfig(),
forceFetch: false,
};
this.pipelineDataLoader = pipelineDataLoader;
// Listen to the first RENDER_COMPLETE_EVENT to resolve this promise
this.firstRenderComplete = new Promise(resolve => {
this.listeners.once(RENDER_COMPLETE_EVENT, resolve);
});
element.setAttribute(LOADING_ATTRIBUTE, '');
element.setAttribute(DATA_SHARED_ITEM, '');
element.setAttribute(RENDERING_COUNT_ATTRIBUTE, '0');
element.addEventListener('renderComplete', this.onRenderCompleteListener);
this.autoFetch = autoFetch;
this.appState = appState;
this.vis = vis;
if (uiState) {
vis._setUiState(uiState);
}
this.uiState = this.vis.getUiState();
this.handlers = {
vis: this.vis,
uiState: this.uiState,
onDestroy: (fn: () => never) => (this.handlers.destroyFn = fn),
};
this.vis.on('update', this.handleVisUpdate);
this.vis.on('reload', this.reload);
this.uiState.on('change', this.onUiStateChange);
if (autoFetch) {
this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.reload);
}
// This is a hack to give maps visualizations access to data in the
// globalState, since they can no longer access it via searchSource.
// TODO: Remove this as a part of elastic/kibana#30593
this.vis.API.getGeohashBounds = () => {
return queryGeohashBounds(this.vis, {
filters: this.dataLoaderParams.filters,
query: this.dataLoaderParams.query,
});
};
//WAZUH disable pipelineLoader
this.dataLoader = new VisualizeDataLoader(vis, Private);
const visFilters: any = Private(VisFiltersProvider);
this.renderCompleteHelper = new RenderCompleteHelper(element);
this.inspectorAdapters = this.getActiveInspectorAdapters();
this.vis.openInspector = this.openInspector;
this.vis.hasInspector = this.hasInspector;
// init default actions
forEach(this.vis.type.events, (event, eventName) => {
if (event.disabled || !eventName) {
return;
} else {
this.actions[eventName] = event.defaultAction;
}
});
this.handlers.eventsSubject = new Rx.Subject();
this.vis.eventsSubject = this.handlers.eventsSubject;
this.events$ = this.handlers.eventsSubject.asObservable().pipe(share());
this.events$.subscribe(event => {
if (this.actions[event.name]) {
event.data.aggConfigs = getTableAggs(this.vis);
const newFilters = this.actions[event.name](event.data) || [];
visFilters.pushFilters(newFilters);
}
});
this.dataSubject = new Rx.Subject();
this.data$ = this.dataSubject.asObservable().pipe(share());
this.render();
}
/**
* Update properties of the embedded visualization. This method does not allow
* updating all initial parameters, but only a subset of the ones allowed
* in {@link VisualizeUpdateParams}.
*
* @param params The parameters that should be updated.
*/
public update(params: VisualizeUpdateParams = {}) {
// Apply data- attributes to the element if specified
const dataAttrs = params.dataAttrs;
if (dataAttrs) {
Object.keys(dataAttrs).forEach(key => {
if (dataAttrs[key] === null) {
this.element.removeAttribute(`data-${key}`);
return;
}
this.element.setAttribute(`data-${key}`, dataAttrs[key]);
});
}
let fetchRequired = false;
if (params.hasOwnProperty('timeRange')) {
fetchRequired = true;
this.dataLoaderParams.timeRange = params.timeRange;
}
if (params.hasOwnProperty('filters')) {
fetchRequired = true;
this.dataLoaderParams.filters = params.filters;
}
if (params.hasOwnProperty('query')) {
fetchRequired = true;
this.dataLoaderParams.query = params.query;
}
if (fetchRequired) {
this.fetchAndRender();
}
}
/**
* Destroy the underlying Angular scope of the visualization. This should be
* called whenever you remove the visualization.
*/
public destroy(): void {
this.destroyed = true;
this.cancel();
this.debouncedFetchAndRender.cancel();
if (this.autoFetch) {
if (this.autoRefreshFetchSubscription) this.autoRefreshFetchSubscription.unsubscribe();
}
this.vis.removeListener('reload', this.reload);
this.vis.removeListener('update', this.handleVisUpdate);
this.element.removeEventListener('renderComplete', this.onRenderCompleteListener);
this.uiState.off('change', this.onUiStateChange);
visualizationLoader.destroy(this.element);
this.renderCompleteHelper.destroy();
if (this.handlers.destroyFn) {
this.handlers.destroyFn();
}
}
/**
* Return the actual DOM element (wrapped in jQuery) of the rendered visualization.
* This is especially useful if you used `append: true` in the parameters where
* the visualization will be appended to the specified container.
*/
public getElement(): HTMLElement {
return this.element;
}
/**
* renders visualization with provided data
* @param response: visualization data
*/
public render = (response: VisResponseData | null = null): void => {
const executeRenderer = this.rendererProvider(response);
if (!executeRenderer) {
return;
}
// TODO: we have this weird situation when we need to render first,
// and then we call fetch and render... we need to get rid of that.
executeRenderer().then(() => {
if (!this.loaded) {
this.loaded = true;
if (this.autoFetch) {
this.fetchAndRender();
}
}
});
};
/**
* Opens the inspector for the embedded visualization. This will return an
* handler to the inspector to close and interact with it.
* @return An inspector session to interact with the opened inspector.
*/
public openInspector = () => {
return Inspector.open(this.inspectorAdapters, {
title: this.vis.title,
});
};
public hasInspector = () => {
return Inspector.isAvailable(this.inspectorAdapters);
};
/**
* Returns a promise, that will resolve (without a value) once the first rendering of
* the visualization has finished. If you want to listen to consecutive rendering
* events, look into the `addRenderCompleteListener` method.
*
* @returns Promise, that resolves as soon as the visualization is done rendering
* for the first time.
*/
public whenFirstRenderComplete(): Promise<void> {
return this.firstRenderComplete;
}
/**
* Adds a listener to be called whenever the visualization finished rendering.
* This can be called multiple times, when the visualization rerenders, e.g. due
* to new data.
*
* @param {function} listener The listener to be notified about complete renders.
*/
public addRenderCompleteListener(listener: () => void) {
this.listeners.addListener(RENDER_COMPLETE_EVENT, listener);
}
/**
* Removes a previously registered render complete listener from this handler.
* This listener will no longer be called when the visualization finished rendering.
*
* @param {function} listener The listener to remove from this handler.
*/
public removeRenderCompleteListener(listener: () => void) {
this.listeners.removeListener(RENDER_COMPLETE_EVENT, listener);
}
/**
* Force the fetch of new data and renders the chart again.
*/
public reload = () => {
this.fetchAndRender(true);
};
private incrementRenderingCount = () => {
const renderingCount = Number(this.element.getAttribute(RENDERING_COUNT_ATTRIBUTE) || 0);
this.element.setAttribute(RENDERING_COUNT_ATTRIBUTE, `${renderingCount + 1}`);
};
private onRenderCompleteListener = () => {
this.listeners.emit(RENDER_COMPLETE_EVENT);
this.element.removeAttribute(LOADING_ATTRIBUTE);
this.incrementRenderingCount();
};
private onUiStateChange = () => {
this.fetchAndRender();
};
/**
* Returns an object of all inspectors for this vis object.
* This must only be called after this.type has properly be initialized,
* since we need to read out data from the the vis type to check which
* inspectors are available.
*/
private getActiveInspectorAdapters = (): Adapters => {
const adapters: Adapters = {};
const { inspectorAdapters: typeAdapters } = this.vis.type;
// Add the requests inspector adapters if the vis type explicitly requested it via
// inspectorAdapters.requests: true in its definition or if it's using the courier
// request handler, since that will automatically log its requests.
if ((typeAdapters && typeAdapters.requests) || this.vis.type.requestHandler === 'courier') {
adapters.requests = new RequestAdapter();
}
// Add the data inspector adapter if the vis type requested it or if the
// vis is using courier, since we know that courier supports logging
// its data.
if ((typeAdapters && typeAdapters.data) || this.vis.type.requestHandler === 'courier') {
adapters.data = new DataAdapter();
}
// Add all inspectors, that are explicitly registered with this vis type
if (typeAdapters && typeAdapters.custom) {
Object.entries(typeAdapters.custom).forEach(([key, Adapter]) => {
adapters[key] = new (Adapter as any)();
});
}
return adapters;
};
/**
* Fetches new data and renders the chart. This will happen debounced for a couple
* of milliseconds, to bundle fast successive calls into one fetch and render,
* e.g. while resizing the window, this will be triggered constantly on the resize
* event.
*
* @param forceFetch=false Whether the request handler should be signaled to forceFetch
* (i.e. ignore caching in case it supports it). If at least one call to this
* passed `true` the debounced fetch and render will be a force fetch.
*/
private fetchAndRender = (forceFetch = false): void => {
this.shouldForceNextFetch = forceFetch || this.shouldForceNextFetch;
this.element.setAttribute(LOADING_ATTRIBUTE, '');
this.debouncedFetchAndRender();
};
private handleVisUpdate = () => {
if (this.appState) {
this.appState.vis = this.vis.getState();
this.appState.save();
}
this.fetchAndRender();
};
private cancel = () => {
if (this.abortController) this.abortController.abort();
};
private fetch = (forceFetch: boolean = false) => {
this.cancel();
this.abortController = new AbortController();
this.dataLoaderParams.abortSignal = this.abortController.signal;
this.dataLoaderParams.aggs = this.vis.getAggConfig();
this.dataLoaderParams.forceFetch = forceFetch;
this.dataLoaderParams.inspectorAdapters = this.inspectorAdapters;
this.vis.filters = { timeRange: this.dataLoaderParams.timeRange };
this.vis.requestError = undefined;
this.vis.showRequestError = false;
return (
this.dataLoader
// Don't pass in this.dataLoaderParams directly because it may be modified async in another
// call to fetch before the previous one has completed
.fetch({ ...this.dataLoaderParams })
.then(data => {
// Pipeline responses never throw errors, so we need to check for
// `type: 'error'`, and then throw so it can be caught below.
// TODO: We should revisit this after we have fully migrated
// to the new expression pipeline infrastructure.
if (data && data.type === 'error') {
throw data.error;
}
if (data && data.value) {
this.dataSubject.next(data.value);
}
return data;
})
.catch(this.handleDataLoaderError)
);
};
/**
* When dataLoader returns an error, we need to make sure it surfaces in the UI.
*
* TODO: Eventually we should add some custom error messages for issues that are
* frequently encountered by users.
*/
private handleDataLoaderError = (error: any): void => {
// If the data loader was aborted then no need to surface this error in the UI
if (error && error.name === 'AbortError') return;
// TODO: come up with a general way to cancel execution of pipeline expressions.
if (this.dataLoaderParams.searchSource && this.dataLoaderParams.searchSource.cancelQueued) {
this.dataLoaderParams.searchSource.cancelQueued();
}
this.vis.requestError = error;
this.vis.showRequestError =
error.type && ['NO_OP_SEARCH_STRATEGY', 'UNSUPPORTED_QUERY'].includes(error.type);
toastNotifications.addDanger({
title: i18n.translate('common.ui.visualize.dataLoaderError', {
defaultMessage: 'Error in visualization',
}),
text: error.message,
});
};
private rendererProvider = (response: VisResponseData | null) => {
let renderer: any = null;
let args: any[] = [];
if (this.pipelineDataLoader) {
renderer = registries.renderers.get(get(response || {}, 'as', 'visualization'));
args = [this.element, get(response, 'value', { visType: this.vis.type.name }), this.handlers];
} else {
renderer = visualizationLoader;
args = [
this.element,
this.vis,
get(response, 'value.visData', null),
get(response, 'value.visConfig', this.vis.params),
this.uiState,
{
listenOnChange: false,
},
];
}
if (!renderer) {
return null;
}
return () => renderer.render(...args);
};
}

View File

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './visualize_loader';

View File

@ -0,0 +1,135 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @ts-ignore
import { isEqual } from 'lodash';
// @ts-ignore
import { getVisParams } from 'ui/visualize/loader/pipeline_helpers/build_pipeline';
// @ts-ignore
import { VisRequestHandlersRegistryProvider as RequestHandlersProvider } from 'ui/registry/vis_request_handlers';
// @ts-ignore
import { VisResponseHandlersRegistryProvider as ResponseHandlerProvider } from 'ui/registry/vis_response_handlers';
// @ts-ignore
import { IPrivate } from 'ui/private';
// @ts-ignore
import {
RequestHandler,
RequestHandlerDescription,
RequestHandlerParams,
ResponseHandler,
ResponseHandlerDescription,
Vis,
// @ts-ignore
} from 'ui/vis';
import { VisResponseData } from 'ui/visualize/loader';
function getHandler<T extends RequestHandler | ResponseHandler>(
from: Array<{ name: string; handler: T }>,
type: string | T
): T {
if (typeof type === 'function') {
return type;
}
const handlerDesc = from.find(handler => handler.name === type);
if (!handlerDesc) {
throw new Error(`Could not find handler "${type}".`);
}
return handlerDesc.handler;
}
export class VisualizeDataLoader {
private requestHandler: RequestHandler;
private responseHandler: ResponseHandler;
private visData: any;
private previousVisState: any;
private previousRequestHandlerResponse: any;
constructor(private readonly vis: Vis, Private: IPrivate) {
const { requestHandler, responseHandler } = vis.type;
const requestHandlers: RequestHandlerDescription[] = Private(RequestHandlersProvider);
const responseHandlers: ResponseHandlerDescription[] = Private(ResponseHandlerProvider);
this.requestHandler = getHandler(requestHandlers, requestHandler);
this.responseHandler = getHandler(responseHandlers, responseHandler);
}
public async fetch(params: RequestHandlerParams): Promise<VisResponseData | void> {
// add necessary params to vis object (dimensions, bucket, metric, etc)
const visParams = await getVisParams(this.vis, {
searchSource: params.searchSource,
timeRange: params.timeRange,
});
const filters = params.filters || [];
const savedFilters = params.searchSource.getField('filter') || [];
const query = params.query || params.searchSource.getField('query');
// searchSource is only there for courier request handler
const requestHandlerResponse = await this.requestHandler({
partialRows: this.vis.params.partialRows || this.vis.type.requiresPartialRows,
metricsAtAllLevels: this.vis.isHierarchical(),
visParams,
...params,
query,
filters: filters.concat(savedFilters).filter(f => !f.meta.disabled),
});
// No need to call the response handler when there have been no data nor has there been changes
// in the vis-state (response handler does not depend on uiState)
const canSkipResponseHandler =
this.previousRequestHandlerResponse &&
this.previousRequestHandlerResponse === requestHandlerResponse &&
this.previousVisState &&
isEqual(this.previousVisState, this.vis.getState());
this.previousVisState = this.vis.getState();
this.previousRequestHandlerResponse = requestHandlerResponse;
if (!canSkipResponseHandler) {
this.visData = await Promise.resolve(
this.responseHandler(requestHandlerResponse, visParams.dimensions)
);
}
const valueAxes = (visParams || {}).valueAxes || false;
const hasSeries = ((this.visData || {}).series || []).length;
if (valueAxes && hasSeries) {
if (visParams.type !== 'area') {
visParams.valueAxes.forEach((axis: { scale: { max: number; }; }, idx: string | number) => {
const maxValue = Math.max.apply(Math, this.visData.series[idx].values.map((x: { y: any; }) => { return x.y; }));
const lengthMaxValue = maxValue.toString().length;
const addTo = parseInt('1' + '0'.repeat(lengthMaxValue - 1));
axis.scale.max = maxValue + (addTo / 2);
});
}
}
return {
as: 'visualization',
value: {
visType: this.vis.type.name,
visData: this.visData,
visConfig: visParams,
params: {},
},
};
}
}

View File

@ -0,0 +1,171 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* IMPORTANT: If you make changes to this API, please make sure to check that
* the docs (docs/development/visualize/development-create-visualization.asciidoc)
* are up to date.
*/
// @ts-ignore
import chrome from 'ui/chrome';
// @ts-ignore
import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
// @ts-ignore
import { IPrivate } from 'ui/private';
import { EmbeddedVisualizeHandler } from './embedded_visualize_handler';
import { VisSavedObject, VisualizeLoaderParams } from 'ui/visualize/loader';
export class VisualizeLoader {
constructor(
private readonly savedVisualizations: any,
private readonly pipelineDataLoader: boolean,
private readonly Private: IPrivate
) {}
/**
* Renders a saved visualization specified by its id into a DOM element.
*
* @param element The DOM element to render the visualization into.
* You can alternatively pass a jQuery element instead.
* @param id The id of the saved visualization. This is the id of the
* saved object that is stored in the .kibana index.
* @param params A list of parameters that will influence rendering.
*
* @return A promise that resolves to the
* handler for this visualization as soon as the saved object could be found.
*/
public async embedVisualizationWithId(
element: HTMLElement,
savedVisualizationId: string,
params: VisualizeLoaderParams
) {
return new Promise((resolve, reject) => {
this.savedVisualizations.get(savedVisualizationId).then((savedObj: VisSavedObject) => {
const handler = this.renderVis(element, savedObj, params);
resolve(handler);
}, reject);
});
}
/**
* Renders a saved visualization specified by its savedObject into a DOM element.
* In most of the cases you will need this method, since it allows you to specify
* filters, handlers, queries, etc. on the savedObject before rendering.
*
* We do not encourage you to use this method, since it will most likely be changed
* or removed in a future version of Kibana. Rather embed a visualization by its id
* via the {@link #embedVisualizationWithId} method.
*
* @deprecated You should rather embed by id, since this method will be removed in the future.
* @param element The DOM element to render the visualization into.
* You can alternatively pass a jQuery element instead.
* @param savedObj The savedObject as it could be retrieved by the
* `savedVisualizations` service.
* @param params A list of parameters that will influence rendering.
*
* @return The handler to the visualization.
*/
public embedVisualizationWithSavedObject(
el: HTMLElement,
savedObj: VisSavedObject,
params: VisualizeLoaderParams
) {
return this.renderVis(el, savedObj, params);
}
/**
* Returns a promise, that resolves to a list of all saved visualizations.
*
* @return Resolves with a list of all saved visualizations as
* returned by the `savedVisualizations` service in Kibana.
*/
public getVisualizationList(): Promise<any[]> {
return this.savedVisualizations.find().then((result: any) => result.hits);
}
private renderVis(
container: HTMLElement,
savedObj: VisSavedObject,
params: VisualizeLoaderParams
) {
const { vis, description, searchSource } = savedObj;
vis.description = description;
vis.searchSource = searchSource;
if (!params.append) {
container.innerHTML = '';
}
const element = document.createElement('div');
element.className = 'visualize';
element.setAttribute('data-test-subj', 'visualizationLoader');
container.appendChild(element);
// We need the container to have display: flex so visualization will render correctly
container.style.display = 'flex';
// If params specified cssClass, we will set this to the element.
if (params.cssClass) {
params.cssClass.split(' ').forEach(cssClass => {
element.classList.add(cssClass);
});
}
// Apply data- attributes to the element if specified
const dataAttrs = params.dataAttrs;
if (dataAttrs) {
Object.keys(dataAttrs).forEach(key => {
element.setAttribute(`data-${key}`, dataAttrs[key]);
});
}
const handlerParams = {
...params,
// lets add query filter angular service to the params
queryFilter: this.Private(FilterBarQueryFilterProvider),
// lets add Private to the params, we'll need to pass it to visualize later
Private: this.Private,
pipelineDataLoader: this.pipelineDataLoader,
};
return new EmbeddedVisualizeHandler(element, savedObj, handlerParams);
}
}
function VisualizeLoaderProvider(
savedVisualizations: any,
interpreterConfig: any,
Private: IPrivate
) {
return new VisualizeLoader(savedVisualizations, interpreterConfig.enableInVisualize, Private);
}
/**
* Returns a promise, that resolves with the visualize loader, once it's ready.
* @return A promise, that resolves to the visualize loader.
*/
function getVisualizeLoader(): Promise<VisualizeLoader> {
return chrome.dangerouslyGetActiveInjector().then($injector => {
const Private: IPrivate = $injector.get('Private');
return Private(VisualizeLoaderProvider);
});
}
export { getVisualizeLoader, VisualizeLoaderProvider };

View File

@ -38,16 +38,16 @@ export class SavedObjectLoader {
this.loaderProperties = {
name: `${this.lowercaseType}s`,
noun: StringUtils.upperFirst(this.type),
nouns: `${this.lowercaseType}s`,
nouns: `${this.lowercaseType}s`
};
this.savedObjectsClient = savedObjectClient;
}
// Fake async function, only to resolve a promise
async processFunc() {
return;
}
// Fake async function, only to resolve a promise
async processFunc() {
return;
}
/**
* Retrieve a saved object by id. Returns a promise that completes when the object finishes
@ -148,13 +148,14 @@ export class SavedObjectLoader {
page: 1,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
fields,
fields
})
.then((resp) => {
.then(resp => {
return {
total: resp.total,
hits: resp.savedObjects
.map((savedObject) => this.mapSavedObjectApiHits(savedObject))
hits: resp.savedObjects.map(savedObject =>
this.mapSavedObjectApiHits(savedObject)
)
};
});
}

View File

@ -1,30 +1,23 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Author: Elasticsearch B.V.
* Updated by Wazuh, Inc.
*
* http://www.apache.org/licenses/LICENSE-2.0
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* 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 'plugins/kibana/visualize/saved_visualizations/_saved_vis';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { uiModules } from 'ui/modules';
import { SavedObjectLoader } from './saved-object-loader';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_object_registry';
import { start as visualizations } from 'plugins/kibana/visualize/../../../visualizations/public/np_ready/public/legacy';
import { createVisualizeEditUrl } from 'plugins/kibana/visualize/visualize_constants';
import { findListItems } from 'plugins/kibana/visualize/saved_visualizations/find_list_items';
const app = uiModules.get('app/visualize');
@ -32,20 +25,30 @@ const app = uiModules.get('app/visualize');
// edited by the object editor.
savedObjectManagementRegistry.register({
service: 'wzsavedVisualizations',
title: 'visualizations',
title: 'visualizations'
});
app.service('wzsavedVisualizations', function (SavedVis, Private, kbnUrl, chrome) {
const visTypes = visualizations.types;
app.service('wzsavedVisualizations', function(
kbnIndex,
SavedVis,
Private,
kbnUrl,
$http,
chrome
) {
const visTypes = Private(VisTypesRegistryProvider);
const savedObjectClient = Private(SavedObjectsClientProvider);
const saveVisualizationLoader = new SavedObjectLoader(
SavedVis,
kbnIndex,
kbnUrl,
$http,
chrome,
savedObjectClient
);
saveVisualizationLoader.mapHitSource = function (source, id) {
saveVisualizationLoader.mapHitSource = function(source, id) {
source.id = id;
source.url = this.urlFor(id);
@ -58,36 +61,18 @@ app.service('wzsavedVisualizations', function (SavedVis, Private, kbnUrl, chrome
} // eslint-disable-line no-empty
}
if (!typeName || !visTypes.get(typeName)) {
if (!typeName || !visTypes.byName[typeName]) {
source.error = 'Unknown visualization type';
return source;
}
source.type = visTypes.get(typeName);
source.savedObjectType = 'visualization';
source.type = visTypes.byName[typeName];
source.icon = source.type.icon;
source.image = source.type.image;
source.typeTitle = source.type.title;
source.editUrl = `#${createVisualizeEditUrl(id)}`;
return source;
};
saveVisualizationLoader.urlFor = function (id) {
saveVisualizationLoader.urlFor = function(id) {
return kbnUrl.eval('#/visualize/edit/{{id}}', { id: id });
};
// This behaves similarly to find, except it returns visualizations that are
// defined as appExtensions and which may not conform to type: visualization
saveVisualizationLoader.findListItems = function (search = '', size = 100) {
return findListItems({
search,
size,
mapSavedObjectApiHits: this.mapSavedObjectApiHits.bind(this),
savedObjectsClient: this.savedObjectsClient,
visTypes: visualizations.types.getAliases(),
});
};
return saveVisualizationLoader;
});

View File

@ -0,0 +1,242 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui';
import {
buildEmptyFilter,
disableFilter,
enableFilter,
Filter,
//pinFilter,
toggleFilterDisabled,
toggleFilterPinned,
isFilterPinned,
toggleFilterNegated,
unpinFilter,
} from '@kbn/es-query';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames';
import React, { Component } from 'react';
import { UiSettingsClientContract } from 'src/core/public';
import { IndexPattern } from 'ui/index_patterns';
import { FilterEditor } from 'plugins/data/filter/filter_bar/filter_editor';
import { FilterItem } from './filter_item';
import { FilterOptions } from 'plugins/data/filter/filter_bar/filter_options';
interface Props {
filters: Filter[];
onFiltersUpdated: (filters: Filter[]) => void;
className: string;
indexPatterns: IndexPattern[];
intl: InjectedIntl;
uiSettings: UiSettingsClientContract;
}
interface State {
isAddFilterPopoverOpen: boolean;
}
class FilterBarUI extends Component<Props, State> {
public state = {
isAddFilterPopoverOpen: false,
};
public render() {
if (!this.props.uiSettings) {
return null;
}
const classes = classNames('globalFilterBar', this.props.className);
return (
<EuiFlexGroup
className="globalFilterGroup"
gutterSize="none"
alignItems="flexStart"
responsive={false}
>
<EuiFlexItem className="globalFilterGroup__branch" grow={false}>
<FilterOptions
onEnableAll={this.onEnableAll}
onDisableAll={this.onDisableAll}
onPinAll={this.onPinAll}
onUnpinAll={this.onUnpinAll}
onToggleAllNegated={this.onToggleAllNegated}
onToggleAllDisabled={this.onToggleAllDisabled}
onRemoveAll={this.onRemoveAll}
/>
</EuiFlexItem>
<EuiFlexItem className="globalFilterGroup__filterFlexItem">
<EuiFlexGroup
className={classes}
wrap={true}
responsive={false}
gutterSize="xs"
alignItems="center"
>
{this.renderItems()}
{this.renderAddFilter()}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);
}
private renderItems() {
return this.props.filters.map((filter, i) => (
<EuiFlexItem key={i} grow={false} className="globalFilterBar__flexItem">
<FilterItem
id={`${i}`}
filter={filter}
onUpdate={newFilter => this.onUpdate(i, newFilter)}
onRemove={() => this.onRemove(i)}
indexPatterns={this.props.indexPatterns}
uiSettings={this.props.uiSettings}
/>
</EuiFlexItem>
));
}
private renderAddFilter() {
const isPinned = this.props.uiSettings.get('filters:pinnedByDefault');
const [indexPattern] = this.props.indexPatterns;
const index = indexPattern && indexPattern.id;
const newFilter = buildEmptyFilter(isPinned, index);
const button = (
<EuiButtonEmpty size="xs" onClick={this.onOpenAddFilterPopover} data-test-subj="addFilter">
+{' '}
<FormattedMessage
id="data.filter.filterBar.addFilterButtonLabel"
defaultMessage="Add filter"
/>
</EuiButtonEmpty>
);
return (
<EuiFlexItem grow={false}>
<EuiPopover
id="addFilterPopover"
button={button}
isOpen={this.state.isAddFilterPopoverOpen}
closePopover={this.onCloseAddFilterPopover}
anchorPosition="downLeft"
withTitle
panelPaddingSize="none"
ownFocus={true}
>
<EuiFlexItem grow={false}>
<div style={{ width: 400 }}>
<FilterEditor
filter={newFilter}
indexPatterns={this.props.indexPatterns}
onSubmit={this.onAdd}
onCancel={this.onCloseAddFilterPopover}
key={JSON.stringify(newFilter)}
uiSettings={this.props.uiSettings}
/>
</div>
</EuiFlexItem>
</EuiPopover>
</EuiFlexItem>
);
}
private onAdd = (filter: Filter) => {
this.onCloseAddFilterPopover();
const filters = [...this.props.filters, filter];
this.props.onFiltersUpdated(filters);
};
private onRemove = (i: number) => {
const filters = [...this.props.filters];
filters.splice(i, 1);
this.props.onFiltersUpdated(filters);
};
private onUpdate = (i: number, filter: Filter) => {
const filters = [...this.props.filters];
filters[i] = filter;
this.props.onFiltersUpdated(filters);
};
private onEnableAll = () => {
const filters = this.props.filters.map(enableFilter);
this.props.onFiltersUpdated(filters);
};
private onDisableAll = () => {
const filters = this.props.filters.map(disableFilter);
this.props.onFiltersUpdated(filters);
};
private onPinAll = () => {
const filters = this.props.filters.map(filter => {
const shouldExclude =
filter &&
filter.meta &&
typeof filter.meta.removable !== 'undefined' &&
!filter.meta.removable;
return isFilterPinned(filter) || shouldExclude
? filter
: toggleFilterPinned(filter);
});
this.props.onFiltersUpdated(filters);
};
private onUnpinAll = () => {
const filters = this.props.filters.map(unpinFilter);
this.props.onFiltersUpdated(filters);
};
private onToggleAllNegated = () => {
const filters = this.props.filters.map(toggleFilterNegated);
this.props.onFiltersUpdated(filters);
};
private onToggleAllDisabled = () => {
const filters = this.props.filters.map(toggleFilterDisabled);
this.props.onFiltersUpdated(filters);
};
private isRemovable = item => {
const property = ((item || {}).meta || {}).removable;
return typeof property !== 'undefined' && !property;
};
private onRemoveAll = () => {
const filters = this.props.filters.filter(item => this.isRemovable(item));
this.props.onFiltersUpdated(filters);
};
private onOpenAddFilterPopover = () => {
this.setState({
isAddFilterPopoverOpen: true,
});
};
private onCloseAddFilterPopover = () => {
this.setState({
isAddFilterPopoverOpen: false,
});
};
}
export const FilterBar = injectI18n(FilterBarUI);

View File

@ -0,0 +1,231 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EuiContextMenu, EuiPopover } from '@elastic/eui';
import {
Filter,
isFilterPinned,
toggleFilterDisabled,
toggleFilterNegated,
toggleFilterPinned,
} from '@kbn/es-query';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames';
import React, { Component } from 'react';
import { UiSettingsClientContract } from 'src/core/public';
import { IndexPattern } from 'ui/index_patterns';
import { FilterEditor } from 'plugins/data/filter/filter_bar/filter_editor';
import { FilterView } from './filter_view';
interface Props {
id: string;
filter: Filter;
indexPatterns: IndexPattern[];
className?: string;
onUpdate: (filter: Filter) => void;
onRemove: () => void;
intl: InjectedIntl;
uiSettings: UiSettingsClientContract;
}
interface State {
isPopoverOpen: boolean;
}
class FilterItemUI extends Component<Props, State> {
public state = {
isPopoverOpen: false,
};
public render() {
const { filter, id } = this.props;
const { negate, disabled } = filter.meta;
const classes = classNames(
'globalFilterItem',
{
'globalFilterItem-isDisabled': disabled,
'globalFilterItem-isPinned': isFilterPinned(filter),
'globalFilterItem-isExcluded': negate,
},
this.props.className
);
const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : '';
const dataTestSubjValue = filter.meta.value ? `filter-value-${filter.meta.value}` : '';
const dataTestSubjDisabled = `filter-${
this.props.filter.meta.disabled ? 'disabled' : 'enabled'
}`;
const badge = (
<FilterView
filter={filter}
className={classes}
iconOnClick={() => this.props.onRemove()}
onClick={this.togglePopover}
data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue}`}
/>
);
const panelTree = [
{
id: 0,
items: [
{
name: isFilterPinned(filter)
? this.props.intl.formatMessage({
id: 'data.filter.filterBar.unpinFilterButtonLabel',
defaultMessage: 'Unpin',
})
: this.props.intl.formatMessage({
id: 'data.filter.filterBar.pinFilterButtonLabel',
defaultMessage: 'Pin across all apps',
}),
icon: 'pin',
onClick: () => {
this.closePopover();
this.onTogglePinned();
},
'data-test-subj': 'pinFilter',
},
{
name: this.props.intl.formatMessage({
id: 'data.filter.filterBar.editFilterButtonLabel',
defaultMessage: 'Edit filter',
}),
icon: 'pencil',
panel: 1,
'data-test-subj': 'editFilter',
},
{
name: negate
? this.props.intl.formatMessage({
id: 'data.filter.filterBar.includeFilterButtonLabel',
defaultMessage: 'Include results',
})
: this.props.intl.formatMessage({
id: 'data.filter.filterBar.excludeFilterButtonLabel',
defaultMessage: 'Exclude results',
}),
icon: negate ? 'plusInCircle' : 'minusInCircle',
onClick: () => {
this.closePopover();
this.onToggleNegated();
},
'data-test-subj': 'negateFilter',
},
{
name: disabled
? this.props.intl.formatMessage({
id: 'data.filter.filterBar.enableFilterButtonLabel',
defaultMessage: 'Re-enable',
})
: this.props.intl.formatMessage({
id: 'data.filter.filterBar.disableFilterButtonLabel',
defaultMessage: 'Temporarily disable',
}),
icon: `${disabled ? 'eye' : 'eyeClosed'}`,
onClick: () => {
this.closePopover();
this.onToggleDisabled();
},
'data-test-subj': 'disableFilter',
},
{
name: this.props.intl.formatMessage({
id: 'data.filter.filterBar.deleteFilterButtonLabel',
defaultMessage: 'Delete',
}),
icon: 'trash',
onClick: () => {
this.closePopover();
this.props.onRemove();
},
'data-test-subj': 'deleteFilter',
},
],
},
{
id: 1,
width: 400,
content: (
<div>
<FilterEditor
filter={filter}
indexPatterns={this.props.indexPatterns}
onSubmit={this.onSubmit}
onCancel={this.closePopover}
uiSettings={this.props.uiSettings}
/>
</div>
),
},
];
return (
<EuiPopover
id={`popoverFor_filter${id}`}
className={`globalFilterItem__popover`}
anchorClassName={`globalFilterItem__popoverAnchor`}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
button={badge}
anchorPosition="downLeft"
withTitle={true}
panelPaddingSize="none"
>
<EuiContextMenu initialPanelId={0} panels={panelTree} />
</EuiPopover>
);
}
private closePopover = () => {
this.setState({
isPopoverOpen: false,
});
};
private togglePopover = () => {
this.setState({
isPopoverOpen: !this.state.isPopoverOpen,
});
};
private onSubmit = (filter: Filter) => {
this.closePopover();
this.props.onUpdate(filter);
};
private onTogglePinned = () => {
const filter = toggleFilterPinned(this.props.filter);
this.props.onUpdate(filter);
};
private onToggleNegated = () => {
const filter = toggleFilterNegated(this.props.filter);
this.props.onUpdate(filter);
};
private onToggleDisabled = () => {
const filter = toggleFilterDisabled(this.props.filter);
this.props.onUpdate(filter);
};
}
export const FilterItem = injectI18n(FilterItemUI);

View File

@ -0,0 +1,116 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EuiBadge } from '@elastic/eui';
import { Filter, isFilterPinned } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import React, { SFC } from 'react';
import {
existsOperator,
isOneOfOperator
} from 'plugins/data/filter/filter_bar/filter_editor/lib/filter_operators';
interface Props {
filter: Filter;
[propName: string]: any;
}
export const FilterView: SFC<Props> = ({ filter, iconOnClick, onClick, ...rest }: Props) => {
let title = `Filter: ${getFilterDisplayText(filter)}. ${i18n.translate(
'data.filter.filterBar.moreFilterActionsMessage',
{
defaultMessage: 'Select for more filter actions.',
}
)}`;
if (isFilterPinned(filter)) {
title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', {
defaultMessage: 'Pinned',
})} ${title}`;
}
if (filter.meta.disabled) {
title = `${i18n.translate('data.filter.filterBar.disabledFilterPrefix', {
defaultMessage: 'Disabled',
})} ${title}`;
}
const isImplicit =
typeof filter.meta.removable !== 'undefined' && !!!filter.meta.removable;
return !isImplicit ? (
<EuiBadge
title={title}
iconType="cross"
iconSide="right"
closeButtonProps={{
// Removing tab focus on close button because the same option can be optained through the context menu
// Also, we may want to add a `DEL` keyboard press functionality
tabIndex: -1,
}}
iconOnClick={iconOnClick}
iconOnClickAriaLabel={i18n.translate('data.filter.filterBar.filterItemBadgeIconAriaLabel', {
defaultMessage: 'Delete',
})}
onClick={onClick}
onClickAriaLabel={i18n.translate('data.filter.filterBar.filterItemBadgeAriaLabel', {
defaultMessage: 'Filter actions',
})}
{...rest}
>
<span>{getFilterDisplayText(filter)}</span>
</EuiBadge>
) : (
<EuiBadge
className={rest.className}
>
<span>{getFilterDisplayText(filter)}</span>
</EuiBadge>
);
};
export function getFilterDisplayText(filter: Filter) {
const prefix = filter.meta.negate
? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', {
defaultMessage: 'NOT ',
})}`
: '';
if (filter.meta.alias !== null) {
return `${prefix}${filter.meta.alias}`;
}
switch (filter.meta.type) {
case 'exists':
return `${prefix}${filter.meta.key} ${existsOperator.message}`;
case 'geo_bounding_box':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
case 'geo_polygon':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
case 'phrase':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
case 'phrases':
return `${prefix}${filter.meta.key} ${isOneOfOperator.message} ${filter.meta.value}`;
case 'query_string':
return `${prefix}${filter.meta.value}`;
case 'range':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
default:
return `${prefix}${JSON.stringify(filter.query)}`;
}
}

View File

@ -0,0 +1,468 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Filter } from '@kbn/es-query';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames';
import React, { Component } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import { Storage } from 'ui/storage';
import { get, isEqual } from 'lodash';
import { toastNotifications } from 'ui/notify';
import { CoreStart, UiSettingsClientContract, SavedObjectsClientContract } from 'src/core/public';
import { IndexPattern, Query, QueryBar } from 'plugins/data';
import { SavedQuery, SavedQueryAttributes } from 'plugins/data/search/search_bar/';
import { SavedQueryMeta, SaveQueryForm } from 'plugins/data/search/search_bar/components/saved_query_management/save_query_form';
import { SavedQueryManagementComponent } from 'plugins/data/search/search_bar/components/saved_query_management/saved_query_management_component';
import { SavedQueryService } from 'plugins/data/search/search_bar/lib/saved_query_service';
import { createSavedQueryService } from 'plugins/data/search/search_bar/lib/saved_query_service';
import { FilterBar } from './filter_bar';
interface DateRange {
from: string;
to: string;
}
/**
* NgReact lib requires that changes to the props need to be made in the directive config as well
* See [search_bar\directive\index.js] file
*/
export interface SearchBarProps {
appName: string;
intl: InjectedIntl;
toasts: CoreStart['notifications']['toasts'];
uiSettings: UiSettingsClientContract;
savedObjectsClient: SavedObjectsClientContract;
indexPatterns?: IndexPattern[];
// Query bar
showQueryBar?: boolean;
showQueryInput?: boolean;
screenTitle?: string;
store?: Storage;
query?: Query;
savedQuery?: SavedQuery;
onQuerySubmit?: (payload: { dateRange: DateRange; query?: Query }) => void;
// Filter bar
showFilterBar?: boolean;
filters?: Filter[];
onFiltersUpdated?: (filters: Filter[]) => void;
// Date picker
showDatePicker?: boolean;
dateRangeFrom?: string;
dateRangeTo?: string;
// Autorefresh
isRefreshPaused?: boolean;
refreshInterval?: number;
showAutoRefreshOnly?: boolean;
showSaveQuery?: boolean;
onRefreshChange?: (options: { isPaused: boolean; refreshInterval: number }) => void;
onSaved?: (savedQuery: SavedQuery) => void;
onSavedQueryUpdated?: (savedQuery: SavedQuery) => void;
onClearSavedQuery?: () => void;
customSubmitButton?: React.ReactNode;
}
interface State {
savedQueryService: SavedQueryService;
isFiltersVisible: boolean;
showSaveQueryModal: boolean;
showSaveNewQueryModal: boolean;
showSavedQueryPopover: boolean;
currentProps?: SearchBarProps;
query?: Query;
dateRangeFrom: string;
dateRangeTo: string;
}
class SearchBarUI extends Component<SearchBarProps, State> {
public static defaultProps = {
showQueryBar: true,
showFilterBar: true,
showDatePicker: true,
showAutoRefreshOnly: false,
};
public filterBarRef: Element | null = null;
public filterBarWrapperRef: Element | null = null;
public static getDerivedStateFromProps(nextProps: SearchBarProps, prevState: State) {
if (isEqual(prevState.currentProps, nextProps)) {
return null;
}
let nextQuery = null;
if (nextProps.query && nextProps.query.query !== get(prevState, 'currentProps.query.query')) {
nextQuery = {
query: nextProps.query.query,
language: nextProps.query.language,
};
} else if (
nextProps.query &&
prevState.query &&
nextProps.query.language !== prevState.query.language
) {
nextQuery = {
query: '',
language: nextProps.query.language,
};
}
let nextDateRange = null;
if (
nextProps.dateRangeFrom !== get(prevState, 'currentProps.dateRangeFrom') ||
nextProps.dateRangeTo !== get(prevState, 'currentProps.dateRangeTo')
) {
nextDateRange = {
dateRangeFrom: nextProps.dateRangeFrom,
dateRangeTo: nextProps.dateRangeTo,
};
}
const nextState: any = {
currentProps: nextProps,
};
if (nextQuery) {
nextState.query = nextQuery;
}
if (nextDateRange) {
nextState.dateRangeFrom = nextDateRange.dateRangeFrom;
nextState.dateRangeTo = nextDateRange.dateRangeTo;
}
return nextState;
}
/*
Keep the "draft" value in local state until the user actually submits the query. There are a couple advantages:
1. Each app doesn't have to maintain its own "draft" value if it wants to put off updating the query in app state
until the user manually submits their changes. Most apps have watches on the query value in app state so we don't
want to trigger those on every keypress. Also, some apps (e.g. dashboard) already juggle multiple query values,
each with slightly different semantics and I'd rather not add yet another variable to the mix.
2. Changes to the local component state won't trigger an Angular digest cycle. Triggering digest cycles on every
keypress has been a major source of performance issues for us in previous implementations of the query bar.
See https://github.com/elastic/kibana/issues/14086
*/
public state = {
isFiltersVisible: true,
showSaveQueryModal: false,
showSaveNewQueryModal: false,
showSavedQueryPopover: false,
currentProps: this.props,
query: this.props.query ? { ...this.props.query } : undefined,
dateRangeFrom: get(this.props, 'dateRangeFrom', 'now-15m'),
dateRangeTo: get(this.props, 'dateRangeTo', 'now'),
savedQueryService: createSavedQueryService(this.props.savedObjectsClient),
};
public isDirty = () => {
if (!this.props.showDatePicker && this.state.query && this.props.query) {
return this.state.query.query !== this.props.query.query;
}
return (
(this.state.query && this.props.query && this.state.query.query !== this.props.query.query) ||
this.state.dateRangeFrom !== this.props.dateRangeFrom ||
this.state.dateRangeTo !== this.props.dateRangeTo
);
};
private getFilterUpdateFunction() {
if (this.props.showFilterBar && this.props.onFiltersUpdated) {
return this.props.onFiltersUpdated;
}
return (filters: Filter[]) => {};
}
private shouldRenderQueryBar() {
const showDatePicker = this.props.showDatePicker || this.props.showAutoRefreshOnly;
const showQueryInput =
this.props.showQueryInput && this.props.indexPatterns && this.state.query;
return this.props.showQueryBar && (showDatePicker || showQueryInput);
}
private shouldRenderFilterBar() {
return this.props.showFilterBar && this.props.filters && this.props.indexPatterns;
}
public setFilterBarHeight = () => {
requestAnimationFrame(() => {
const height =
this.filterBarRef && this.state.isFiltersVisible ? this.filterBarRef.clientHeight : 0;
if (this.filterBarWrapperRef) {
this.filterBarWrapperRef.setAttribute('style', `height: ${height}px`);
}
});
};
// member-ordering rules conflict with use-before-declaration rules
/* eslint-disable */
public ro = new ResizeObserver(this.setFilterBarHeight);
/* eslint-enable */
public onSave = async (savedQueryMeta: SavedQueryMeta, saveAsNew = false) => {
if (!this.state.query) return;
const savedQueryAttributes: SavedQueryAttributes = {
title: savedQueryMeta.title,
description: savedQueryMeta.description,
query: this.state.query,
};
if (savedQueryMeta.shouldIncludeFilters) {
savedQueryAttributes.filters = this.props.filters;
}
if (
savedQueryMeta.shouldIncludeTimefilter &&
this.state.dateRangeTo !== undefined &&
this.state.dateRangeFrom !== undefined &&
this.props.refreshInterval !== undefined &&
this.props.isRefreshPaused !== undefined
) {
savedQueryAttributes.timefilter = {
from: this.state.dateRangeFrom,
to: this.state.dateRangeTo,
refreshInterval: {
value: this.props.refreshInterval,
pause: this.props.isRefreshPaused,
},
};
}
try {
let response;
if (this.props.savedQuery && !saveAsNew) {
response = await this.state.savedQueryService.saveQuery(savedQueryAttributes, {
overwrite: true,
});
} else {
response = await this.state.savedQueryService.saveQuery(savedQueryAttributes);
}
toastNotifications.addSuccess(`Your query "${response.attributes.title}" was saved`);
this.setState({
showSaveQueryModal: false,
showSaveNewQueryModal: false,
});
if (this.props.onSaved) {
this.props.onSaved(response);
}
if (this.props.onQuerySubmit) {
this.props.onQuerySubmit({
query: this.state.query,
dateRange: {
from: this.state.dateRangeFrom,
to: this.state.dateRangeTo,
},
});
}
} catch (error) {
toastNotifications.addDanger(`An error occured while saving your query: ${error.message}`);
throw error;
}
};
public onInitiateSave = () => {
this.setState({
showSaveQueryModal: true,
});
};
public onInitiateSaveNew = () => {
this.setState({
showSaveNewQueryModal: true,
});
};
public onQueryBarChange = (queryAndDateRange: { dateRange: DateRange; query?: Query }) => {
this.setState({
query: queryAndDateRange.query,
dateRangeFrom: queryAndDateRange.dateRange.from,
dateRangeTo: queryAndDateRange.dateRange.to,
});
};
public onQueryBarSubmit = (queryAndDateRange: { dateRange?: DateRange; query?: Query }) => {
this.setState(
{
query: queryAndDateRange.query,
dateRangeFrom:
(queryAndDateRange.dateRange && queryAndDateRange.dateRange.from) ||
this.state.dateRangeFrom,
dateRangeTo:
(queryAndDateRange.dateRange && queryAndDateRange.dateRange.to) || this.state.dateRangeTo,
},
() => {
if (this.props.onQuerySubmit) {
this.props.onQuerySubmit({
query: this.state.query,
dateRange: {
from: this.state.dateRangeFrom,
to: this.state.dateRangeTo,
},
});
}
}
);
};
public onLoadSavedQuery = (savedQuery: SavedQuery) => {
const dateRangeFrom = get(savedQuery, 'attributes.timefilter.from', this.state.dateRangeFrom);
const dateRangeTo = get(savedQuery, 'attributes.timefilter.to', this.state.dateRangeTo);
this.setState({
query: savedQuery.attributes.query,
dateRangeFrom,
dateRangeTo,
});
if (this.props.onSavedQueryUpdated) {
this.props.onSavedQueryUpdated(savedQuery);
}
};
public componentDidMount() {
if (this.filterBarRef) {
this.setFilterBarHeight();
this.ro.observe(this.filterBarRef);
}
}
public componentDidUpdate() {
if (this.filterBarRef) {
this.setFilterBarHeight();
this.ro.unobserve(this.filterBarRef);
}
}
public render() {
// This is needed, as kbn-top-nav might render before npSetup.core.uiSettings is set.
// This won't be needed when it's loaded exclusively with React.
if (!this.props.uiSettings) {
return null;
}
const savedQueryManagement = this.state.query && this.props.onClearSavedQuery && (
<SavedQueryManagementComponent
showSaveQuery={this.props.showSaveQuery}
loadedSavedQuery={this.props.savedQuery}
onSave={this.onInitiateSave}
onSaveAsNew={this.onInitiateSaveNew}
onLoad={this.onLoadSavedQuery}
savedQueryService={this.state.savedQueryService}
onClearSavedQuery={this.props.onClearSavedQuery}
></SavedQueryManagementComponent>
);
let queryBar;
if (this.shouldRenderQueryBar()) {
queryBar = (
<QueryBar
toasts={this.props.toasts}
uiSettings={this.props.uiSettings}
savedObjectsClient={this.props.savedObjectsClient}
query={this.state.query}
screenTitle={this.props.screenTitle}
onSubmit={this.onQueryBarSubmit}
appName={this.props.appName}
indexPatterns={this.props.indexPatterns}
store={this.props.store}
prepend={this.props.showFilterBar ? savedQueryManagement : undefined}
showDatePicker={this.props.showDatePicker}
dateRangeFrom={this.state.dateRangeFrom}
dateRangeTo={this.state.dateRangeTo}
isRefreshPaused={this.props.isRefreshPaused}
refreshInterval={this.props.refreshInterval}
showAutoRefreshOnly={this.props.showAutoRefreshOnly}
showQueryInput={this.props.showQueryInput}
onRefreshChange={this.props.onRefreshChange}
onChange={this.onQueryBarChange}
isDirty={this.isDirty()}
customSubmitButton={
this.props.customSubmitButton ? this.props.customSubmitButton : undefined
}
/>
);
}
let filterBar;
if (this.shouldRenderFilterBar()) {
const filterGroupClasses = classNames('globalFilterGroup__wrapper', {
'globalFilterGroup__wrapper-isVisible': this.state.isFiltersVisible,
});
filterBar = (
<div
id="GlobalFilterGroup"
ref={node => {
this.filterBarWrapperRef = node;
}}
className={filterGroupClasses}
>
<div
ref={node => {
this.filterBarRef = node;
}}
>
<FilterBar
className="globalFilterGroup__filterBar"
uiSettings={this.props.uiSettings}
filters={this.props.filters!}
onFiltersUpdated={this.getFilterUpdateFunction()}
indexPatterns={this.props.indexPatterns!}
/>
</div>
</div>
);
}
return (
<div className="globalQueryBar">
{queryBar}
{filterBar}
{this.state.showSaveQueryModal ? (
<SaveQueryForm
savedQuery={this.props.savedQuery ? this.props.savedQuery.attributes : undefined}
savedQueryService={this.state.savedQueryService}
onSave={this.onSave}
onClose={() => this.setState({ showSaveQueryModal: false })}
showFilterOption={this.props.showFilterBar}
showTimeFilterOption={this.props.showDatePicker}
/>
) : null}
{this.state.showSaveNewQueryModal ? (
<SaveQueryForm
savedQueryService={this.state.savedQueryService}
onSave={savedQueryMeta => this.onSave(savedQueryMeta, true)}
onClose={() => this.setState({ showSaveNewQueryModal: false })}
showFilterOption={this.props.showFilterBar}
showTimeFilterOption={this.props.showDatePicker}
/>
) : null}
</div>
);
}
}
export const SearchBar = injectI18n(SearchBarUI);

View File

@ -0,0 +1,121 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { I18nProvider } from '@kbn/i18n/react';
import { UiSettingsClientContract, SavedObjectsClientContract, CoreStart } from 'src/core/public';
import { TopNavMenuData } from 'plugins/kibana_react/top_nav_menu/';
import { TopNavMenuItem } from 'plugins/kibana_react/top_nav_menu/';
import { SearchBar, SearchBarProps } from './search_bar';
type Props = Partial<SearchBarProps> & {
name: string;
uiSettings: UiSettingsClientContract;
savedObjectsClient: SavedObjectsClientContract;
toasts: CoreStart['notifications']['toasts'];
config?: TopNavMenuData[];
showSearchBar?: boolean;
};
/*
* Top Nav Menu is a convenience wrapper component for:
* - Top navigation menu - configured by an array of `TopNavMenuData` objects
* - Search Bar - which includes Filter Bar \ Query Input \ Timepicker.
*
* See SearchBar documentation to learn more about its properties.
*
**/
export function TopNavMenu(props: Props) {
function renderItems() {
if (!props.config) return;
return props.config.map((menuItem: TopNavMenuData, i: number) => {
return (
<EuiFlexItem grow={false} key={`nav-menu-${i}`}>
<TopNavMenuItem {...menuItem} />
</EuiFlexItem>
);
});
}
function renderSearchBar() {
// Validate presense of all required fields
if (!props.showSearchBar || !props.savedObjectsClient) return;
return (
<SearchBar
savedObjectsClient={props.savedObjectsClient}
query={props.query}
filters={props.filters}
toasts={props.toasts}
uiSettings={props.uiSettings}
showQueryBar={props.showQueryBar}
showQueryInput={props.showQueryInput}
showFilterBar={props.showFilterBar}
showDatePicker={props.showDatePicker}
appName={props.appName!}
screenTitle={props.screenTitle!}
onQuerySubmit={props.onQuerySubmit}
onFiltersUpdated={props.onFiltersUpdated}
dateRangeFrom={props.dateRangeFrom}
dateRangeTo={props.dateRangeTo}
isRefreshPaused={props.isRefreshPaused}
showAutoRefreshOnly={props.showAutoRefreshOnly}
onRefreshChange={props.onRefreshChange}
refreshInterval={props.refreshInterval}
indexPatterns={props.indexPatterns}
store={props.store}
savedQuery={props.savedQuery}
showSaveQuery={props.showSaveQuery}
onClearSavedQuery={props.onClearSavedQuery}
onSaved={props.onSaved}
onSavedQueryUpdated={props.onSavedQueryUpdated}
/>
);
}
function renderLayout() {
return (
<span className="kbnTopNavMenu__wrapper">
<EuiFlexGroup
data-test-subj="top-nav"
justifyContent="flexStart"
gutterSize="none"
className="kbnTopNavMenu"
responsive={false}
>
{renderItems()}
</EuiFlexGroup>
{renderSearchBar()}
</span>
);
}
return <I18nProvider>{renderLayout()}</I18nProvider>;
}
TopNavMenu.defaultProps = {
showSearchBar: false,
showQueryBar: true,
showQueryInput: true,
showDatePicker: true,
showFilterBar: true,
screenTitle: '',
};

View File

@ -20,7 +20,9 @@
import 'ngreact';
import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
import { start as navigation } from 'plugins/navigation/legacy';
import { TopNavMenu } from './search-bar/top_nav_menu';
import { Storage } from 'ui/storage';
import chrome from 'ui/chrome';
const module = uiModules.get('kibana');
@ -28,7 +30,7 @@ module.directive('wzTopNav', () => {
return {
restrict: 'E',
template: '',
compile: (elem) => {
compile: elem => {
const child = document.createElement('wz-top-nav-helper');
// Copy attributes to the child directive
@ -40,32 +42,44 @@ module.directive('wzTopNav', () => {
// of the config array's disableButton function return value changes.
child.setAttribute('disabled-buttons', 'disabledButtons');
// Pass in storage
const localStorage = new Storage(window.localStorage);
child.setAttribute('store', 'store');
child.setAttribute('ui-settings', 'uiSettings');
child.setAttribute('saved-objects-client', 'savedObjectsClient');
// Append helper directive
elem.append(child);
const linkFn = ($scope, _, $attr) => {
$scope.store = localStorage;
$scope.uiSettings = chrome.getUiSettingsClient();
$scope.savedObjectsClient = chrome.getSavedObjectsClient();
// Watch config changes
$scope.$watch(() => {
const config = $scope.$eval($attr.config) || [];
return config.map((item) => {
// Copy key into id, as it's a reserved react propery.
// This is done for Angular directive backward compatibility.
// In React only id is recognized.
if (item.key && !item.id) {
item.id = item.key;
}
$scope.$watch(
() => {
const config = $scope.$eval($attr.config) || [];
return config.map(item => {
// Copy key into id, as it's a reserved react propery.
// This is done for Angular directive backward compatibility.
// In React only id is recognized.
if (item.key && !item.id) {
item.id = item.key;
}
// Watch the disableButton functions
if (typeof item.disableButton === 'function') {
return item.disableButton();
}
return item.disableButton;
});
}, (newVal) => {
$scope.disabledButtons = newVal;
},
true);
// Watch the disableButton functions
if (typeof item.disableButton === 'function') {
return item.disableButton();
}
return item.disableButton;
});
},
newVal => {
$scope.disabledButtons = newVal;
},
true
);
};
return linkFn;
@ -73,45 +87,46 @@ module.directive('wzTopNav', () => {
};
});
module.directive('wzTopNavHelper', (reactDirective) => {
const { TopNavMenu } = navigation.ui;
return reactDirective(
wrapInI18nContext(TopNavMenu),
[
['config', { watchDepth: 'value' }],
['disabledButtons', { watchDepth: 'reference' }],
module.directive('wzTopNavHelper', reactDirective => {
return reactDirective(wrapInI18nContext(TopNavMenu), [
['name', { watchDepth: 'reference' }],
['config', { watchDepth: 'value' }],
['disabledButtons', { watchDepth: 'reference' }],
['query', { watchDepth: 'reference' }],
['savedQuery', { watchDepth: 'reference' }],
['intl', { watchDepth: 'reference' }],
['query', { watchDepth: 'reference' }],
['savedQuery', { watchDepth: 'reference' }],
['store', { watchDepth: 'reference' }],
['uiSettings', { watchDepth: 'reference' }],
['savedObjectsClient', { watchDepth: 'reference' }],
['intl', { watchDepth: 'reference' }],
['store', { watchDepth: 'reference' }],
['onQuerySubmit', { watchDepth: 'reference' }],
['onFiltersUpdated', { watchDepth: 'reference' }],
['onRefreshChange', { watchDepth: 'reference' }],
['onClearSavedQuery', { watchDepth: 'reference' }],
['onSaved', { watchDepth: 'reference' }],
['onSavedQueryUpdated', { watchDepth: 'reference' }],
['onQuerySubmit', { watchDepth: 'reference' }],
['onFiltersUpdated', { watchDepth: 'reference' }],
['onRefreshChange', { watchDepth: 'reference' }],
['onClearSavedQuery', { watchDepth: 'reference' }],
['onSaved', { watchDepth: 'reference' }],
['onSavedQueryUpdated', { watchDepth: 'reference' }],
['indexPatterns', { watchDepth: 'collection' }],
['filters', { watchDepth: 'collection' }],
['indexPatterns', { watchDepth: 'collection' }],
['filters', { watchDepth: 'collection' }],
// All modifiers default to true.
// Set to false to hide subcomponents.
'showSearchBar',
'showFilterBar',
'showQueryBar',
'showQueryInput',
'showDatePicker',
'showSaveQuery',
// All modifiers default to true.
// Set to false to hide subcomponents.
'showSearchBar',
'showFilterBar',
'showQueryBar',
'showQueryInput',
'showDatePicker',
'showSaveQuery',
'appName',
'screenTitle',
'dateRangeFrom',
'dateRangeTo',
'isRefreshPaused',
'refreshInterval',
'disableAutoFocus',
'showAutoRefreshOnly',
],
);
'appName',
'screenTitle',
'dateRangeFrom',
'dateRangeTo',
'isRefreshPaused',
'refreshInterval',
'disableAutoFocus',
'showAutoRefreshOnly'
]);
});

View File

@ -1255,7 +1255,3 @@ md-chips.md-default-theme .md-chips, md-chips .md-chips{
.table-vis-container{
overflow: auto !important;
}
.hide-close-button{
display: none;
}

View File

@ -181,7 +181,7 @@ export class CommonData {
}
}
if (agent) filters.push(filterHandler.agentQuery(agent));
this.$rootScope.$emit('wzEventFilters', { filters, localChange, tab });
this.$rootScope.$emit('wzEventFilters', { filters, localChange });
if (!this.$rootScope.$$listenerCount['wzEventFilters']) {
this.$timeout(100).then(() =>
this.af(filterHandler, tab, localChange, (agent = false))

View File

@ -31,10 +31,6 @@ export class ReportingService {
this.errorHandler = errorHandler;
this.wazuhConfig = wazuhConfig;
}
removeTableVis(visList) {
const attributes = JSON.parse(visList.attributes.visState);
return attributes.type !== 'table';
}
removeAgentStatusVis(idArray) {
const monitoringEnabled = this.wazuhConfig.getConfig()[
@ -61,16 +57,13 @@ export class ReportingService {
this.vis2png.clear();
const rawVisualizations = this.rawVisualizations.getList()
.filter(this.removeTableVis);
let idArray = [];
if (tab === 'general') {
idArray = this.removeAgentStatusVis(
rawVisualizations.map(item => item.id)
this.rawVisualizations.getList().map(item => item.id)
);
} else {
idArray = rawVisualizations.map(item => item.id);
idArray = this.rawVisualizations.getList().map(item => item.id);
}
for (const item of idArray) {
@ -78,7 +71,7 @@ export class ReportingService {
this.vis2png.assignHTMLItem(item, tmpHTMLElement);
}
const appliedFilters = await this.visHandlers.getAppliedFilters(
const appliedFilters = this.visHandlers.getAppliedFilters(
syscollectorFilters
);
@ -129,8 +122,8 @@ export class ReportingService {
const docType =
type === 'agentConfig'
? `wazuh-agent-${obj.id}`
: `wazuh-group-${obj.name}`;
? `wazuh-agent-${obj.id}`
: `wazuh-group-${obj.name}`;
const name = `${docType}-configuration-${(Date.now() / 1000) | 0}.pdf`;
const browserTimezone = moment.tz.guess(true);
@ -139,9 +132,7 @@ export class ReportingService {
array: [],
name,
filters: [
type === 'agentConfig'
? { agent: obj.id }
: { group: obj.name }
type === 'agentConfig' ? { agent: obj.id } : { group: obj.name }
],
time: '',
searchBar: '',

View File

@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
import { healthCheck } from './health-check';
import { recentlyAccessed } from 'plugins/kibana/home/components/recently_accessed';
import { recentlyAccessed } from 'ui/persisted_log';
export function getSavedSearch(
redirectWhenMissing,
$location,

View File

@ -1,234 +1,222 @@
<discover-app-w class="app-container">
<h1 class="euiScreenReaderOnly">{{screenTitle}}</h1>
<wz-top-nav
app-name="'discover'"
config="topNavMenu"
show-search-bar="tabView !== 'cluster-monitoring'"
show-date-picker="enableTimeRangeSelector"
show-save-query="showSaveQuery"
query="state.query"
saved-query="savedQuery"
screen-title="screenTitle"
on-query-submit="updateQueryAndFetch"
index-patterns="[indexPattern]"
filters="filters"
on-filters-updated="onFiltersUpdated"
date-range-from="time.from"
date-range-to="time.to"
is-refresh-paused="refreshInterval.pause"
refresh-interval="refreshInterval.value"
on-refresh-change="onRefreshChange"
on-saved="onQuerySaved"
on-saved-query-updated="onSavedQueryUpdated"
on-clear-saved-query="onClearSavedQuery"
>
</wz-top-nav>
<!-- Local nav. -->
<wz-top-nav
app-name="'discover'"
config="topNavMenu"
show-search-bar="tabView !== 'cluster-monitoring'"
show-date-picker="enableTimeRangeSelector"
show-save-query="showSaveQuery"
query="state.query"
saved-query="savedQuery"
screen-title="screenTitle"
on-query-submit="updateQueryAndFetch"
index-patterns="[indexPattern]"
filters="filters"
on-filters-updated="onFiltersUpdated"
date-range-from="time.from"
date-range-to="time.to"
is-refresh-paused="refreshInterval.pause"
refresh-interval="refreshInterval.value"
on-refresh-change="onRefreshChange"
on-saved="onQuerySaved"
on-saved-query-updated="onSavedQueryUpdated"
on-clear-saved-query="onClearSavedQuery"
>
</wz-top-nav>
<main ng-show="tabView !== 'cluster-monitoring'" class="container-fluid">
<div ng-show="tabView === 'discover'" class="row">
<div ng-if="tabView === 'discover'" class="col-md-2 sidebar-container collapsible-sidebar" id="discover-sidebar">
<div class="dscFieldChooser">
<disc-field-chooser
class="dscFieldChooser"
columns="state.columns"
hits="rows"
field-counts="fieldCounts"
index-pattern="searchSource.getField('index')"
index-pattern-list="opts.indexPatternList"
state="state"
on-add-field="addColumn"
on-add-filter="filterQuery"
on-remove-field="removeColumn"
>
</disc-field-chooser>
<main ng-show="tabView !== 'cluster-monitoring'" class="container-fluid">
<div ng-show="tabView === 'discover'" class="row">
<div ng-if="tabView === 'discover'" class="col-md-2 sidebar-container collapsible-sidebar" id="discover-sidebar">
<disc-field-chooser
class="dscFieldChooser"
columns="state.columns"
hits="rows"
field-counts="fieldCounts"
index-pattern="searchSource.getField('index')"
index-pattern-list="opts.indexPatternList"
state="state"
on-add-field="addColumn"
on-add-filter="filterQuery"
on-remove-field="removeColumn"
>
</disc-field-chooser>
</div>
<div class="dscWrapper col-md-10">
<discover-unsupported-index-pattern
ng-if="isUnsupportedIndexPattern"
unsupported-type="unsupportedIndexPatternType"
></discover-unsupported-index-pattern>
<discover-no-results
ng-show="resultState === 'none'"
shard-failures="failures"
time-field-name="opts.timefield"
query-language="state.query.language"
get-doc-link="getDocLink"
></discover-no-results>
<discover-uninitialized
ng-show="resultState === 'uninitialized'"
on-refresh="fetch"
></discover-uninitialized>
<!-- loading -->
<div ng-show="resultState === 'loading'">
<discover-fetch-error
ng-show="fetchError"
fetch-error="fetchError"
></discover-fetch-error>
<div ng-hide="fetchError" class="dscOverlay">
<h2
i18n-id="kbn.discover.searchingTitle"
i18n-default-message="Searching"
class="euiTitle euiTitle--small"
></h2>
<div class="euiSpacer euiSpacer--m"></div>
<div
class="euiLoadingSpinner euiLoadingSpinner--large"
data-test-subj="loadingSpinner"
></div>
</div>
</div>
<div class="dscWrapper col-md-10">
<discover-unsupported-index-pattern
ng-if="isUnsupportedIndexPattern"
unsupported-type="unsupportedIndexPatternType"
></discover-unsupported-index-pattern>
<discover-no-results
ng-show="resultState === 'none'"
shard-failures="failures"
time-field-name="opts.timefield"
query-language="state.query.language"
get-doc-link="getDocLink"
></discover-no-results>
<discover-uninitialized
ng-show="resultState === 'uninitialized'"
on-refresh="fetch"
></discover-uninitialized>
<!-- loading -->
<div ng-show="resultState === 'loading'">
<discover-fetch-error
ng-show="fetchError"
fetch-error="fetchError"
></discover-fetch-error>
<div ng-hide="fetchError" class="dscOverlay">
<h2
i18n-id="kbn.discover.searchingTitle"
i18n-default-message="Searching"
class="euiTitle euiTitle--small"
></h2>
<div class="euiSpacer euiSpacer--m"></div>
<div
class="euiLoadingSpinner euiLoadingSpinner--large"
data-test-subj="loadingSpinner"
></div>
</div>
</div>
<div class="dscWrapper__content" ng-show="resultState === 'ready'">
<!-- result -->
<div class="dscResults">
<button
class="kuiButton kuiButton--basic kuiButton--iconText dscSkipButton"
ng-click="showAllRows(); scrollToBottom()"
>
<span class="kuiButton__inner">
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-chevron-down"></span>
<span
i18n-id="kbn.discover.skipToBottomButtonLabel"
i18n-default-message="Skip to bottom"
></span>
</span>
</button>
<div class="dscResultCount">
<strong data-test-subj="discoverQueryHits">{{(hits || 0) | number:0}}</strong>
<div class="dscWrapper__content" ng-show="resultState === 'ready'">
<!-- result -->
<div class="dscResults">
<button
class="kuiButton kuiButton--basic kuiButton--iconText dscSkipButton"
ng-click="showAllRows(); scrollToBottom()"
>
<span class="kuiButton__inner">
<span aria-hidden="true" class="kuiButton__icon kuiIcon fa-chevron-down"></span>
<span
class="dscResultHits"
i18n-id="kbn.discover.hitsPluralTitle"
i18n-default-message="{hits, plural, one {hit} other {hits}}"
i18n-values="{ hits }"
i18n-id="kbn.discover.skipToBottomButtonLabel"
i18n-default-message="Skip to bottom"
></span>
<button
ng-if="opts.savedSearch.id"
class="kuiLink"
type="button"
id="reload_saved_search"
ng-click="resetQuery()"
>
{{::'kbn.discover.reloadSavedSearchButton' | i18n: {defaultMessage: 'Reset search'} }}
</button>
</div>
</span>
</button>
<section
aria-label="{{::'kbn.discover.histogramOfFoundDocumentsAriaLabel' | i18n: {defaultMessage: 'Histogram of found documents'} }}"
class="dscTimechart"
ng-if="opts.timefield"
<div class="dscResultCount">
<strong data-test-subj="discoverQueryHits">{{(hits || 0) | number:0}}</strong>
<span
class="dscResultHits"
i18n-id="kbn.discover.hitsPluralTitle"
i18n-default-message="{hits, plural, one {hit} other {hits}}"
i18n-values="{ hits }"
></span>
<button
ng-if="opts.savedSearch.id"
class="kuiLink"
type="button"
id="reload_saved_search"
ng-click="resetQuery()"
>
<header class="dscTimechart__header">
<div class="small">
<label
for="dscResultsIntervalSelector"
tooltip="{{::'kbn.discover.howToChangeTheTimeTooltip' | i18n: {defaultMessage: 'To change the time, click the calendar icon in the navigation bar'} }}"
>
{{toMoment(timeRange.from)}} - {{toMoment(timeRange.to)}}
</label>
&mdash;
<span class="form-inline">
<select
id="dscResultsIntervalSelector"
class="dscResults__interval form-control"
ng-model="state.interval"
ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled"
ng-blur="toggleInterval()"
data-test-subj="discoverIntervalSelect"
>
</select>
<span ng-if="bucketInterval.scaled">
<icon-tip
content="getBucketIntervalToolTipText()"
position="'top'"
></icon-tip>
<span
i18n-id="kbn.discover.scaledToDescription"
i18n-default-message="Scaled to {bucketIntervalDescription}"
i18n-values="{
bucketIntervalDescription: bucketInterval.description
}"
></span>
</span>
</span>
</div>
</header>
<discover-histogram
class="dscHistogram"
ng-show="vis && rows.length !== 0"
chart-data="histogramData"
timefilter-update-handler="timefilterUpdateHandler"
watch-depth="reference"
data-test-subj="discoverChart"
></discover-histogram>
</section>
<section
class="dscTable"
fixed-scroll
aria-labelledby="documentsAriaLabel"
>
<h2 class="euiScreenReaderOnly"
id="documentsAriaLabel"
i18n-id="kbn.discover.documentsAriaLabel"
i18n-default-message="Documents"
></h2>
<doc-table
hits="rows"
index-pattern="indexPattern"
sorting="state.sort"
columns="state.columns"
infinite-scroll="true"
filter="filterQuery"
filters="state.filters"
data-shared-item
data-title="{{opts.savedSearch.lastSavedTitle}}"
data-description="{{opts.savedSearch.description}}"
minimum-visible-rows="minimumVisibleRows"
render-complete
on-add-column="addColumn"
on-change-sort-order="setSortOrder"
on-move-column="moveColumn"
on-remove-column="removeColumn"
></doc-table>
<a tabindex="0" id="discoverBottomMarker"></a>
<div
ng-if="rows.length == opts.sampleSize"
class="dscTable__footer"
>
<span
i18n-id="kbn.discover.howToSeeOtherMatchingDocumentsDescription"
i18n-default-message="These are the first {sampleSize} documents matching
your search, refine your search to see others. "
i18n-values="{
sampleSize: opts.sampleSize,
}"
></span>
<a
kbn-accessible-click
ng-click="scrollToTop()"
i18n-id="kbn.discover.backToTopLinkText"
i18n-default-message="Back to top."
></a>
</div>
</section>
{{::'kbn.discover.reloadSavedSearchButton' | i18n: {defaultMessage: 'Reset search'} }}
</button>
</div>
<section
aria-label="{{::'kbn.discover.histogramOfFoundDocumentsAriaLabel' | i18n: {defaultMessage: 'Histogram of found documents'} }}"
class="dscTimechart"
ng-if="opts.timefield"
>
<header class="dscTimechart__header">
<div class="small">
<label
for="dscResultsIntervalSelector"
tooltip="{{::'kbn.discover.howToChangeTheTimeTooltip' | i18n: {defaultMessage: 'To change the time, click the calendar icon in the navigation bar'} }}"
>
{{toMoment(timeRange.from)}} - {{toMoment(timeRange.to)}}
</label>
&mdash;
<span class="form-inline">
<select
id="dscResultsIntervalSelector"
class="dscResults__interval form-control"
ng-model="state.interval"
ng-options="interval.val as interval.display for interval in intervalOptions | filter: intervalEnabled"
ng-blur="toggleInterval()"
data-test-subj="discoverIntervalSelect"
>
</select>
<span ng-if="bucketInterval.scaled">
<icon-tip
content="getBucketIntervalToolTipText()"
position="'top'"
></icon-tip>
<span
i18n-id="kbn.discover.scaledToDescription"
i18n-default-message="Scaled to {bucketIntervalDescription}"
i18n-values="{
bucketIntervalDescription: bucketInterval.description
}"
></span>
</span>
</span>
</div>
</header>
<div id="discoverHistogram"
ng-show="vis && rows.length !== 0"
style="display: flex; height: 200px"
>
</div>
</section>
<section
class="dscTable"
fixed-scroll
aria-label="{{::'kbn.discover.documentsAriaLabel' | i18n: {defaultMessage: 'Documents'} }}"
>
<doc-table
hits="rows"
index-pattern="indexPattern"
sorting="state.sort"
columns="state.columns"
infinite-scroll="true"
filter="filterQuery"
filters="state.filters"
data-shared-item
data-title="{{opts.savedSearch.lastSavedTitle}}"
data-description="{{opts.savedSearch.description}}"
minimum-visible-rows="minimumVisibleRows"
render-complete
on-add-column="addColumn"
on-change-sort-order="setSortOrder"
on-move-column="moveColumn"
on-remove-column="removeColumn"
></doc-table>
<a tabindex="0" id="discoverBottomMarker"></a>
<div
ng-if="rows.length == opts.sampleSize"
class="dscTable__footer"
>
<span
i18n-id="kbn.discover.howToSeeOtherMatchingDocumentsDescription"
i18n-default-message="These are the first {sampleSize} documents matching
your search, refine your search to see others. "
i18n-values="{
sampleSize: opts.sampleSize,
}"
></span>
<a
kbn-accessible-click
ng-click="scrollToTop()"
i18n-id="kbn.discover.backToTopLinkText"
i18n-default-message="Back to top."
></a>
</div>
</section>
</div>
</div>
</div>
</main>
</discover-app-w>
</div>
</main>
</discover-app-w>

View File

@ -20,7 +20,7 @@ export const changeWazuhNavLogo = () => {
clearInterval(interval);
}
const url = chrome.addBasePath('/plugins/wazuh/img/logo.svg');
$('.euiBreadcrumbs').html(
$('.euiBreadcrumb').html(
`<img src="${url}" class="navBarLogo" alt="">`
);
}, 100);

View File

@ -185,7 +185,11 @@ export class WazuhReportingCtrl {
log('reporting:renderTables', `isVis: ${isVis}`, 'debug');
for (const table of tables) {
let rowsparsed = [];
rowsparsed = table.rows;
if (isVis) {
rowsparsed = rawParser(table.rawResponse, table.columns);
} else {
rowsparsed = table.rows;
}
if (Array.isArray(rowsparsed) && rowsparsed.length) {
const rows =
rowsparsed.length > 100 ? rowsparsed.slice(0, 99) : rowsparsed;
@ -200,8 +204,8 @@ export class WazuhReportingCtrl {
parseInt(a[a.length - 1]) < parseInt(b[b.length - 1])
? 1
: parseInt(a[a.length - 1]) > parseInt(b[b.length - 1])
? -1
: 0;
? -1
: 0;
TimSort.sort(rows, sortFunction);
@ -355,9 +359,9 @@ export class WazuhReportingCtrl {
const seconds = date.getSeconds();
const str = `${year}-${month < 10 ? '0' + month : month}-${
day < 10 ? '0' + day : day
}T${hours < 10 ? '0' + hours : hours}:${
}T${hours < 10 ? '0' + hours : hours}:${
minutes < 10 ? '0' + minutes : minutes
}:${seconds < 10 ? '0' + seconds : seconds}`;
}:${seconds < 10 ? '0' + seconds : seconds}`;
log('reporting:formatDate', `str: ${str}`, 'debug');
return str;
}
@ -458,15 +462,19 @@ export class WazuhReportingCtrl {
const len = filters.length;
for (let i = 0; i < len; i++) {
const { negate, key, value, params, type } = filters[i].meta;
str += `${negate ? 'NOT ' : ''}`;
str += `${key}: `;
str += `${type === 'range'
? `${params.gte}-${params.lt}`
: !!value
? value
: (params || {}).query}`;
str += `${i === len - 1 ? '' : ' AND ' }`;
const filter = filters[i];
str +=
i === len - 1
? (filter.meta.negate ? 'NOT ' : '') +
filter.meta.key +
': ' +
filter.meta.value
: (filter.meta.negate ? 'NOT ' : '') +
filter.meta.key +
': ' +
filter.meta.value +
' AND ';
}
if (searchBar) {
@ -587,7 +595,7 @@ export class WazuhReportingCtrl {
log(
'reporting:checkTitle',
`Item ID ${item.id}, from ${
isAgents ? 'agents' : 'overview'
isAgents ? 'agents' : 'overview'
} and tab ${tab}`,
'info'
);
@ -1549,7 +1557,7 @@ export class WazuhReportingCtrl {
if (hardware.data.ram && hardware.data.ram.total)
ulcustom.push(
Number(hardware.data.ram.total / 1024 / 1024).toFixed(2) +
'GB RAM'
'GB RAM'
);
ulcustom &&
ulcustom.length &&
@ -1734,8 +1742,8 @@ export class WazuhReportingCtrl {
plainData[key] =
Array.isArray(data[key]) && typeof data[key][0] !== 'object'
? data[key].map(x => {
return typeof x === 'object' ? JSON.stringify(x) : x + '\n';
})
return typeof x === 'object' ? JSON.stringify(x) : x + '\n';
})
: data[key];
} else if (
Array.isArray(data[key]) &&
@ -1755,7 +1763,7 @@ export class WazuhReportingCtrl {
title: (section.options || {}).hideHeader
? ''
: (section.tabs || [])[tab] ||
(section.isGroupConfig ? ((section.labels || [])[0] || [])[tab] : ''),
(section.isGroupConfig ? ((section.labels || [])[0] || [])[tab] : ''),
columns: ['', ''],
type: 'config',
rows: this.getConfigRows(plainData, (section.labels || [])[0])
@ -1773,10 +1781,10 @@ export class WazuhReportingCtrl {
typeof x[key] !== 'object'
? x[key]
: Array.isArray(x[key])
? x[key].map(x => {
? x[key].map(x => {
return x + '\n';
})
: JSON.stringify(x[key])
: JSON.stringify(x[key])
);
}
while (row.length < columns.length) {
@ -1968,10 +1976,10 @@ export class WazuhReportingCtrl {
typeof x[key] !== 'object'
? x[key]
: Array.isArray(x[key])
? x[key].map(x => {
? x[key].map(x => {
return x + '\n';
})
: JSON.stringify(x[key])
: JSON.stringify(x[key])
);
});
return row;
@ -2157,7 +2165,7 @@ export class WazuhReportingCtrl {
data &&
data.data &&
Object.keys(data.data[Object.keys(data.data)[0]]).length >
0
0
) {
if (!titleOfSection) {
this.dd.content.push({
@ -2209,10 +2217,10 @@ export class WazuhReportingCtrl {
typeof x[key] !== 'object'
? x[key]
: Array.isArray(x[key])
? x[key].map(x => {
? x[key].map(x => {
return x + '\n';
})
: JSON.stringify(x[key])
: JSON.stringify(x[key])
);
});
return row;
@ -2394,22 +2402,22 @@ export class WazuhReportingCtrl {
agentOs === 'windows'
? ['Name', 'Architecture', 'Version', 'Vendor']
: [
'Name',
'Architecture',
'Version',
'Vendor',
'Description'
],
'Name',
'Architecture',
'Version',
'Vendor',
'Description'
],
rows: packages.data.items.map(x => {
return agentOs === 'windows'
? [x['name'], x['architecture'], x['version'], x['vendor']]
: [
x['name'],
x['architecture'],
x['version'],
x['vendor'],
x['description']
];
x['name'],
x['architecture'],
x['version'],
x['vendor'],
x['description']
];
})
});
}
@ -2439,11 +2447,11 @@ export class WazuhReportingCtrl {
return agentOs === 'windows'
? [x['name'], x['cmd'], x['priority'], x['nlwp']]
: [
x['name'],
x['euser'],
x['nice'],
ProcessEquivalence[x.state]
];
x['name'],
x['euser'],
x['nice'],
ProcessEquivalence[x.state]
];
})
});
}
@ -2473,18 +2481,18 @@ export class WazuhReportingCtrl {
rows: ports.data.items.map(x => {
return agentOs === 'windows'
? [
x['local']['ip'],
x['local']['port'],
x['process'],
x['state'],
x['protocol']
]
x['local']['ip'],
x['local']['port'],
x['process'],
x['state'],
x['protocol']
]
: [
x['local']['ip'],
x['local']['port'],
x['state'],
x['protocol']
];
x['local']['ip'],
x['local']['port'],
x['state'],
x['protocol']
];
})
});
}