mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
## [SIEM] Overview Page "1.5" A redesigned SIEM Overview page that includes `Recent timelines`, a `Security news` feed, visualizations, and rolled-up event counts   ### Overview enhancements - Added the global Search bar and Date picker to the Overview page - New `Recent timelines` widget affords quick access to favorite and recently modified timelines - New `Security news` widget - New Kibana advanced settings (toggle switch) for enabling or disabling the news widget and configuring the news URL  - New `Events count by dataset` widget - Updated the `Host Events` and `Network Events` widgets to integrate with the Search bar and date picker input - Enhanced the `Host Events` and `Network Events` widgets to use an accordion paradigm that summarizes stats by source (e.g. `Auditbeat`, `Endgame`) - Enhanced the `Host Events` and `Network Events` widgets to visualize relative percentages of events collected as progress bars - New `Alerts count by category` widget - New `Signals count by MITRE ATT&CK™ category` widget - New `View events`, `View alerts`, and `View signals` navigation buttons for their respective visualizations ### FTUE enhancements - FTUE "no data" view design refresh  - When the FTUE "no data" page is displayed, hide all global navigation links (i.e. `Hosts`, `Network`, `Detection engine`), such that only `Overview` appears in the global nav - App Help popover design refresh  - Removed the `Beta` badge and `Security Information & Event Management with the Elastic Stack` from the Overview header - Tested in Chrome `79.0.3945.117`, Firefox `72.0.1`, and Safari `13.0.4` ## Known issues - The `siem:newsFeedUrl` advanced setting is defaulted to `https://feeds.elastic.co/kibana` - The `Signals count by MITRE ATT&CK™ category` visualization does not display all categories - The `Signals count by MITRE ATT&CK™ category` visualization may require a different index pattern - `EuiButtonGroup` throwing a `Can't perform a React state update on an unmounted component` warning when switching from the Overview tab https://github.com/elastic/siem-team/issues/484 Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
1cf848041d
commit
fd2e7b1fb8
80 changed files with 3633 additions and 1096 deletions
|
@ -79,7 +79,10 @@ readonly links: {
|
|||
readonly introduction: string;
|
||||
};
|
||||
readonly kibana: string;
|
||||
readonly siem: string;
|
||||
readonly siem: {
|
||||
readonly guide: string;
|
||||
readonly gettingStarted: string;
|
||||
};
|
||||
readonly query: {
|
||||
readonly luceneQuerySyntax: string;
|
||||
readonly queryDsl: string;
|
||||
|
|
|
@ -17,5 +17,5 @@ export interface DocLinksStart
|
|||
| --- | --- | --- |
|
||||
| [DOC\_LINK\_VERSION](./kibana-plugin-public.doclinksstart.doc_link_version.md) | <code>string</code> | |
|
||||
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-public.doclinksstart.elastic_website_url.md) | <code>string</code> | |
|
||||
| [links](./kibana-plugin-public.doclinksstart.links.md) | <code>{</code><br/><code> readonly filebeat: {</code><br/><code> readonly base: string;</code><br/><code> readonly installation: string;</code><br/><code> readonly configuration: string;</code><br/><code> readonly elasticsearchOutput: string;</code><br/><code> readonly startup: string;</code><br/><code> readonly exportedFields: string;</code><br/><code> };</code><br/><code> readonly auditbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly metricbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly heartbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly logstash: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly functionbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly winlogbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly aggs: {</code><br/><code> readonly date_histogram: string;</code><br/><code> readonly date_range: string;</code><br/><code> readonly filter: string;</code><br/><code> readonly filters: string;</code><br/><code> readonly geohash_grid: string;</code><br/><code> readonly histogram: string;</code><br/><code> readonly ip_range: string;</code><br/><code> readonly range: string;</code><br/><code> readonly significant_terms: string;</code><br/><code> readonly terms: string;</code><br/><code> readonly avg: string;</code><br/><code> readonly avg_bucket: string;</code><br/><code> readonly max_bucket: string;</code><br/><code> readonly min_bucket: string;</code><br/><code> readonly sum_bucket: string;</code><br/><code> readonly cardinality: string;</code><br/><code> readonly count: string;</code><br/><code> readonly cumulative_sum: string;</code><br/><code> readonly derivative: string;</code><br/><code> readonly geo_bounds: string;</code><br/><code> readonly geo_centroid: string;</code><br/><code> readonly max: string;</code><br/><code> readonly median: string;</code><br/><code> readonly min: string;</code><br/><code> readonly moving_avg: string;</code><br/><code> readonly percentile_ranks: string;</code><br/><code> readonly serial_diff: string;</code><br/><code> readonly std_dev: string;</code><br/><code> readonly sum: string;</code><br/><code> readonly top_hits: string;</code><br/><code> };</code><br/><code> readonly scriptedFields: {</code><br/><code> readonly scriptFields: string;</code><br/><code> readonly scriptAggs: string;</code><br/><code> readonly painless: string;</code><br/><code> readonly painlessApi: string;</code><br/><code> readonly painlessSyntax: string;</code><br/><code> readonly luceneExpressions: string;</code><br/><code> };</code><br/><code> readonly indexPatterns: {</code><br/><code> readonly loadingData: string;</code><br/><code> readonly introduction: string;</code><br/><code> };</code><br/><code> readonly kibana: string;</code><br/><code> readonly siem: string;</code><br/><code> readonly query: {</code><br/><code> readonly luceneQuerySyntax: string;</code><br/><code> readonly queryDsl: string;</code><br/><code> readonly kueryQuerySyntax: string;</code><br/><code> };</code><br/><code> readonly date: {</code><br/><code> readonly dateMath: string;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
| [links](./kibana-plugin-public.doclinksstart.links.md) | <code>{</code><br/><code> readonly filebeat: {</code><br/><code> readonly base: string;</code><br/><code> readonly installation: string;</code><br/><code> readonly configuration: string;</code><br/><code> readonly elasticsearchOutput: string;</code><br/><code> readonly startup: string;</code><br/><code> readonly exportedFields: string;</code><br/><code> };</code><br/><code> readonly auditbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly metricbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly heartbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly logstash: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly functionbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly winlogbeat: {</code><br/><code> readonly base: string;</code><br/><code> };</code><br/><code> readonly aggs: {</code><br/><code> readonly date_histogram: string;</code><br/><code> readonly date_range: string;</code><br/><code> readonly filter: string;</code><br/><code> readonly filters: string;</code><br/><code> readonly geohash_grid: string;</code><br/><code> readonly histogram: string;</code><br/><code> readonly ip_range: string;</code><br/><code> readonly range: string;</code><br/><code> readonly significant_terms: string;</code><br/><code> readonly terms: string;</code><br/><code> readonly avg: string;</code><br/><code> readonly avg_bucket: string;</code><br/><code> readonly max_bucket: string;</code><br/><code> readonly min_bucket: string;</code><br/><code> readonly sum_bucket: string;</code><br/><code> readonly cardinality: string;</code><br/><code> readonly count: string;</code><br/><code> readonly cumulative_sum: string;</code><br/><code> readonly derivative: string;</code><br/><code> readonly geo_bounds: string;</code><br/><code> readonly geo_centroid: string;</code><br/><code> readonly max: string;</code><br/><code> readonly median: string;</code><br/><code> readonly min: string;</code><br/><code> readonly moving_avg: string;</code><br/><code> readonly percentile_ranks: string;</code><br/><code> readonly serial_diff: string;</code><br/><code> readonly std_dev: string;</code><br/><code> readonly sum: string;</code><br/><code> readonly top_hits: string;</code><br/><code> };</code><br/><code> readonly scriptedFields: {</code><br/><code> readonly scriptFields: string;</code><br/><code> readonly scriptAggs: string;</code><br/><code> readonly painless: string;</code><br/><code> readonly painlessApi: string;</code><br/><code> readonly painlessSyntax: string;</code><br/><code> readonly luceneExpressions: string;</code><br/><code> };</code><br/><code> readonly indexPatterns: {</code><br/><code> readonly loadingData: string;</code><br/><code> readonly introduction: string;</code><br/><code> };</code><br/><code> readonly kibana: string;</code><br/><code> readonly siem: {</code><br/><code> readonly guide: string;</code><br/><code> readonly gettingStarted: string;</code><br/><code> };</code><br/><code> readonly query: {</code><br/><code> readonly luceneQuerySyntax: string;</code><br/><code> readonly queryDsl: string;</code><br/><code> readonly kueryQuerySyntax: string;</code><br/><code> };</code><br/><code> readonly date: {</code><br/><code> readonly dateMath: string;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
|
||||
|
|
|
@ -1,151 +1,151 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md)
|
||||
|
||||
## kibana-plugin-public package
|
||||
|
||||
The Kibana Core APIs for client-side plugins.
|
||||
|
||||
A plugin's `public/index` file must contain a named import, `plugin`<!-- -->, that implements [PluginInitializer](./kibana-plugin-public.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-public.plugin.md)<!-- -->.
|
||||
|
||||
The plugin integrates with the core system via lifecycle events: `setup`<!-- -->, `start`<!-- -->, and `stop`<!-- -->. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-public.coresetup.md) or [CoreStart](./kibana-plugin-public.corestart.md)<!-- -->) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked.
|
||||
|
||||
## Classes
|
||||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. |
|
||||
| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md)<!-- -->.<!-- -->It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. |
|
||||
| [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. |
|
||||
|
||||
## Enumerations
|
||||
|
||||
| Enumeration | Description |
|
||||
| --- | --- |
|
||||
| [AppLeaveActionType](./kibana-plugin-public.appleaveactiontype.md) | Possible type of actions on application leave. |
|
||||
| [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) | Status of the application's navLink. |
|
||||
| [AppStatus](./kibana-plugin-public.appstatus.md) | Accessibility status of an application. |
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. |
|
||||
| [AppBase](./kibana-plugin-public.appbase.md) | |
|
||||
| [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.<!-- -->See |
|
||||
| [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.<!-- -->See |
|
||||
| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | |
|
||||
| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | |
|
||||
| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md)<!-- -->. |
|
||||
| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | |
|
||||
| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
|
||||
| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | |
|
||||
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
|
||||
| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. |
|
||||
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
|
||||
| [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | |
|
||||
| [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. |
|
||||
| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | |
|
||||
| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. |
|
||||
| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. |
|
||||
| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | |
|
||||
| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. |
|
||||
| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. |
|
||||
| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the <code>Plugin</code> setup lifecycle |
|
||||
| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the <code>Plugin</code> start lifecycle |
|
||||
| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | |
|
||||
| [EnvironmentMode](./kibana-plugin-public.environmentmode.md) | |
|
||||
| [ErrorToastOptions](./kibana-plugin-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-public.itoasts.md) APIs. |
|
||||
| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the <code>message</code> and <code>stack</code> of a fatal Error |
|
||||
| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
|
||||
| [HttpErrorRequest](./kibana-plugin-public.httperrorrequest.md) | |
|
||||
| [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | |
|
||||
| [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md)<!-- -->. |
|
||||
| [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | |
|
||||
| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. |
|
||||
| [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | |
|
||||
| [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md)<!-- -->. |
|
||||
| [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)<!-- -->s. |
|
||||
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
|
||||
| [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
|
||||
| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication |
|
||||
| [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. |
|
||||
| [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. |
|
||||
| [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | |
|
||||
| [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md)<!-- -->. |
|
||||
| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | |
|
||||
| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. |
|
||||
| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) |
|
||||
| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the <code>ui/new_platform</code> module. |
|
||||
| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the <code>ui/new_platform</code> module. |
|
||||
| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | |
|
||||
| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | |
|
||||
| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | |
|
||||
| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | |
|
||||
| [OverlayRef](./kibana-plugin-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-public.overlaystart.md) methods for closing a mounted overlay. |
|
||||
| [OverlayStart](./kibana-plugin-public.overlaystart.md) | |
|
||||
| [PackageInfo](./kibana-plugin-public.packageinfo.md) | |
|
||||
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a <code>PluginInitializer</code> |
|
||||
| [SavedObject](./kibana-plugin-public.savedobject.md) | |
|
||||
| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the <code>attributes</code> property. |
|
||||
| [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. |
|
||||
| [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | |
|
||||
| [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | |
|
||||
| [SavedObjectsBulkCreateObject](./kibana-plugin-public.savedobjectsbulkcreateobject.md) | |
|
||||
| [SavedObjectsBulkCreateOptions](./kibana-plugin-public.savedobjectsbulkcreateoptions.md) | |
|
||||
| [SavedObjectsBulkUpdateObject](./kibana-plugin-public.savedobjectsbulkupdateobject.md) | |
|
||||
| [SavedObjectsBulkUpdateOptions](./kibana-plugin-public.savedobjectsbulkupdateoptions.md) | |
|
||||
| [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | |
|
||||
| [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | |
|
||||
| [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects <code>find()</code> method.<!-- -->\*Note\*: this type is different between the Public and Server Saved Objects clients. |
|
||||
| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. |
|
||||
| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. |
|
||||
| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. |
|
||||
| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. |
|
||||
| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. |
|
||||
| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. |
|
||||
| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. |
|
||||
| [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
|
||||
| [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | |
|
||||
| [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | |
|
||||
| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
| --- | --- |
|
||||
| [AppLeaveAction](./kibana-plugin-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md)<!-- -->See [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) |
|
||||
| [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return <code>confirm</code> to to prompt a message to the user before leaving the page, or <code>default</code> to keep the default behavior (doing nothing).<!-- -->See [AppMountParameters](./kibana-plugin-public.appmountparameters.md) for detailed usage examples. |
|
||||
| [AppMount](./kibana-plugin-public.appmount.md) | A mount function called when the user navigates to this app's route. |
|
||||
| [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. |
|
||||
| [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. |
|
||||
| [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-public.appupdater.md)<!-- -->. |
|
||||
| [AppUpdater](./kibana-plugin-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) |
|
||||
| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | |
|
||||
| [ChromeHelpExtensionMenuCustomLink](./kibana-plugin-public.chromehelpextensionmenucustomlink.md) | |
|
||||
| [ChromeHelpExtensionMenuDiscussLink](./kibana-plugin-public.chromehelpextensionmenudiscusslink.md) | |
|
||||
| [ChromeHelpExtensionMenuDocumentationLink](./kibana-plugin-public.chromehelpextensionmenudocumentationlink.md) | |
|
||||
| [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | |
|
||||
| [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | |
|
||||
| [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | |
|
||||
| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. |
|
||||
| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) |
|
||||
| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md)<!-- -->, excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md)<!-- -->. |
|
||||
| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) |
|
||||
| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
|
||||
| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md)<!-- -->. |
|
||||
| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. |
|
||||
| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
|
||||
| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | |
|
||||
| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | |
|
||||
| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
|
||||
| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) |
|
||||
| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) |
|
||||
| [Toast](./kibana-plugin-public.toast.md) | |
|
||||
| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. |
|
||||
| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md)<!-- -->. |
|
||||
| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
|
||||
| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
|
||||
| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) |
|
||||
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md)
|
||||
|
||||
## kibana-plugin-public package
|
||||
|
||||
The Kibana Core APIs for client-side plugins.
|
||||
|
||||
A plugin's `public/index` file must contain a named import, `plugin`<!-- -->, that implements [PluginInitializer](./kibana-plugin-public.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-public.plugin.md)<!-- -->.
|
||||
|
||||
The plugin integrates with the core system via lifecycle events: `setup`<!-- -->, `start`<!-- -->, and `stop`<!-- -->. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-public.coresetup.md) or [CoreStart](./kibana-plugin-public.corestart.md)<!-- -->) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked.
|
||||
|
||||
## Classes
|
||||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. |
|
||||
| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md)<!-- -->.<!-- -->It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. |
|
||||
| [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. |
|
||||
|
||||
## Enumerations
|
||||
|
||||
| Enumeration | Description |
|
||||
| --- | --- |
|
||||
| [AppLeaveActionType](./kibana-plugin-public.appleaveactiontype.md) | Possible type of actions on application leave. |
|
||||
| [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) | Status of the application's navLink. |
|
||||
| [AppStatus](./kibana-plugin-public.appstatus.md) | Accessibility status of an application. |
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. |
|
||||
| [AppBase](./kibana-plugin-public.appbase.md) | |
|
||||
| [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.<!-- -->See |
|
||||
| [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.<!-- -->See |
|
||||
| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | |
|
||||
| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | |
|
||||
| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md)<!-- -->. |
|
||||
| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | |
|
||||
| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
|
||||
| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | |
|
||||
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
|
||||
| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. |
|
||||
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
|
||||
| [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | |
|
||||
| [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. |
|
||||
| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | |
|
||||
| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. |
|
||||
| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. |
|
||||
| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | |
|
||||
| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. |
|
||||
| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. |
|
||||
| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the <code>Plugin</code> setup lifecycle |
|
||||
| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the <code>Plugin</code> start lifecycle |
|
||||
| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | |
|
||||
| [EnvironmentMode](./kibana-plugin-public.environmentmode.md) | |
|
||||
| [ErrorToastOptions](./kibana-plugin-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-public.itoasts.md) APIs. |
|
||||
| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the <code>message</code> and <code>stack</code> of a fatal Error |
|
||||
| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
|
||||
| [HttpErrorRequest](./kibana-plugin-public.httperrorrequest.md) | |
|
||||
| [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | |
|
||||
| [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md)<!-- -->. |
|
||||
| [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | |
|
||||
| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. |
|
||||
| [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | |
|
||||
| [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md)<!-- -->. |
|
||||
| [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)<!-- -->s. |
|
||||
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
|
||||
| [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
|
||||
| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication |
|
||||
| [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. |
|
||||
| [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. |
|
||||
| [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | |
|
||||
| [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md)<!-- -->. |
|
||||
| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | |
|
||||
| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. |
|
||||
| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) |
|
||||
| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the <code>ui/new_platform</code> module. |
|
||||
| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the <code>ui/new_platform</code> module. |
|
||||
| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | |
|
||||
| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | |
|
||||
| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | |
|
||||
| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | |
|
||||
| [OverlayRef](./kibana-plugin-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-public.overlaystart.md) methods for closing a mounted overlay. |
|
||||
| [OverlayStart](./kibana-plugin-public.overlaystart.md) | |
|
||||
| [PackageInfo](./kibana-plugin-public.packageinfo.md) | |
|
||||
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a <code>PluginInitializer</code> |
|
||||
| [SavedObject](./kibana-plugin-public.savedobject.md) | |
|
||||
| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the <code>attributes</code> property. |
|
||||
| [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. |
|
||||
| [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | |
|
||||
| [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | |
|
||||
| [SavedObjectsBulkCreateObject](./kibana-plugin-public.savedobjectsbulkcreateobject.md) | |
|
||||
| [SavedObjectsBulkCreateOptions](./kibana-plugin-public.savedobjectsbulkcreateoptions.md) | |
|
||||
| [SavedObjectsBulkUpdateObject](./kibana-plugin-public.savedobjectsbulkupdateobject.md) | |
|
||||
| [SavedObjectsBulkUpdateOptions](./kibana-plugin-public.savedobjectsbulkupdateoptions.md) | |
|
||||
| [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | |
|
||||
| [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | |
|
||||
| [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects <code>find()</code> method.<!-- -->\*Note\*: this type is different between the Public and Server Saved Objects clients. |
|
||||
| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. |
|
||||
| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. |
|
||||
| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. |
|
||||
| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. |
|
||||
| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. |
|
||||
| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. |
|
||||
| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. |
|
||||
| [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
|
||||
| [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | |
|
||||
| [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | |
|
||||
| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
| --- | --- |
|
||||
| [AppLeaveAction](./kibana-plugin-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md)<!-- -->See [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) |
|
||||
| [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return <code>confirm</code> to to prompt a message to the user before leaving the page, or <code>default</code> to keep the default behavior (doing nothing).<!-- -->See [AppMountParameters](./kibana-plugin-public.appmountparameters.md) for detailed usage examples. |
|
||||
| [AppMount](./kibana-plugin-public.appmount.md) | A mount function called when the user navigates to this app's route. |
|
||||
| [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. |
|
||||
| [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. |
|
||||
| [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-public.appupdater.md)<!-- -->. |
|
||||
| [AppUpdater](./kibana-plugin-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) |
|
||||
| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | |
|
||||
| [ChromeHelpExtensionMenuCustomLink](./kibana-plugin-public.chromehelpextensionmenucustomlink.md) | |
|
||||
| [ChromeHelpExtensionMenuDiscussLink](./kibana-plugin-public.chromehelpextensionmenudiscusslink.md) | |
|
||||
| [ChromeHelpExtensionMenuDocumentationLink](./kibana-plugin-public.chromehelpextensionmenudocumentationlink.md) | |
|
||||
| [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | |
|
||||
| [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | |
|
||||
| [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | |
|
||||
| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. |
|
||||
| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) |
|
||||
| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md)<!-- -->, excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md)<!-- -->. |
|
||||
| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) |
|
||||
| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
|
||||
| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md)<!-- -->. |
|
||||
| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. |
|
||||
| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
|
||||
| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | |
|
||||
| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | |
|
||||
| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
|
||||
| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) |
|
||||
| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) |
|
||||
| [Toast](./kibana-plugin-public.toast.md) | |
|
||||
| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. |
|
||||
| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md)<!-- -->. |
|
||||
| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
|
||||
| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
|
||||
| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) |
|
||||
|
||||
|
|
|
@ -218,6 +218,8 @@ might increase the search time. This setting is off by default. Users must opt-i
|
|||
[horizontal]
|
||||
`siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app.
|
||||
`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events.
|
||||
`siem:enableNewsFeed`:: Enables the News feed
|
||||
`siem:newsFeedUrl`:: News feed content will be retrieved from this URL
|
||||
`siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds.
|
||||
`siem:timeDefaults`:: The default period of time in the SIEM time filter.
|
||||
|
||||
|
|
|
@ -106,7 +106,10 @@ export class DocLinksService {
|
|||
introduction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-patterns.html`,
|
||||
},
|
||||
kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`,
|
||||
siem: `${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/index.html`,
|
||||
siem: {
|
||||
guide: `${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/index.html`,
|
||||
gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/install-siem.html`,
|
||||
},
|
||||
query: {
|
||||
luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`,
|
||||
queryDsl: `${ELASTICSEARCH_DOCS}query-dsl.html`,
|
||||
|
@ -199,7 +202,10 @@ export interface DocLinksStart {
|
|||
readonly introduction: string;
|
||||
};
|
||||
readonly kibana: string;
|
||||
readonly siem: string;
|
||||
readonly siem: {
|
||||
readonly guide: string;
|
||||
readonly gettingStarted: string;
|
||||
};
|
||||
readonly query: {
|
||||
readonly luceneQuerySyntax: string;
|
||||
readonly queryDsl: string;
|
||||
|
|
|
@ -486,7 +486,10 @@ export interface DocLinksStart {
|
|||
readonly introduction: string;
|
||||
};
|
||||
readonly kibana: string;
|
||||
readonly siem: string;
|
||||
readonly siem: {
|
||||
readonly guide: string;
|
||||
readonly gettingStarted: string;
|
||||
};
|
||||
readonly query: {
|
||||
readonly luceneQuerySyntax: string;
|
||||
readonly queryDsl: string;
|
||||
|
|
|
@ -189,7 +189,10 @@ exports[`LanguageSwitcher should toggle off if language is lucene 1`] = `
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
@ -482,7 +485,10 @@ exports[`LanguageSwitcher should toggle on if language is kuery 1`] = `
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
|
|
@ -295,7 +295,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
@ -918,7 +921,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
@ -1523,7 +1529,10 @@ exports[`QueryStringInput Should pass the query language to the language switche
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
@ -2143,7 +2152,10 @@ exports[`QueryStringInput Should pass the query language to the language switche
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
@ -2748,7 +2760,10 @@ exports[`QueryStringInput Should render the given query 1`] = `
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
@ -3368,7 +3383,10 @@ exports[`QueryStringInput Should render the given query 1`] = `
|
|||
"scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source",
|
||||
"scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html",
|
||||
},
|
||||
"siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
"siem": Object {
|
||||
"gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html",
|
||||
"guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html",
|
||||
},
|
||||
"winlogbeat": Object {
|
||||
"base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch",
|
||||
},
|
||||
|
|
|
@ -34,6 +34,15 @@ export const DEFAULT_INTERVAL_TYPE = 'manual';
|
|||
export const DEFAULT_INTERVAL_VALUE = 300000; // ms
|
||||
export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges';
|
||||
|
||||
/** This Kibana Advanced Setting enables the `Security news` feed widget */
|
||||
export const ENABLE_NEWS_FEED_SETTING = 'siem:enableNewsFeed';
|
||||
|
||||
/** This Kibana Advanced Setting specifies the URL of the News feed widget */
|
||||
export const NEWS_FEED_URL_SETTING = 'siem:newsFeedUrl';
|
||||
|
||||
/** The default value for News feed widget */
|
||||
export const NEWS_FEED_URL_SETTING_DEFAULT = 'https://feeds.elastic.co/kibana'; // TODO: replace this with the real feed URL
|
||||
|
||||
/**
|
||||
* Id for the signals alerting type
|
||||
*/
|
||||
|
|
|
@ -133,3 +133,7 @@ export const NETWORK_STATS = [
|
|||
STAT_FLOW,
|
||||
STAT_TLS,
|
||||
];
|
||||
|
||||
export const OVERVIEW_HOST_STATS = '[data-test-subj="overview-hosts-stats"]';
|
||||
|
||||
export const OVERVIEW_NETWORK_STATS = '[data-test-subj="overview-network-stats"]';
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
import { OVERVIEW_PAGE } from '../../lib/urls';
|
||||
import { clearFetch, stubApi } from '../../lib/fixtures/helpers';
|
||||
import { HOST_STATS, NETWORK_STATS, STAT_AUDITD } from '../../lib/overview/selectors';
|
||||
import {
|
||||
HOST_STATS,
|
||||
NETWORK_STATS,
|
||||
OVERVIEW_HOST_STATS,
|
||||
OVERVIEW_NETWORK_STATS,
|
||||
STAT_AUDITD,
|
||||
} from '../../lib/overview/selectors';
|
||||
import { loginAndWaitForPage } from '../../lib/util/helpers';
|
||||
|
||||
describe('Overview Page', () => {
|
||||
|
@ -17,6 +23,14 @@ describe('Overview Page', () => {
|
|||
});
|
||||
|
||||
it('Host and Network stats render with correct values', () => {
|
||||
cy.get(OVERVIEW_HOST_STATS)
|
||||
.find('button')
|
||||
.invoke('click');
|
||||
|
||||
cy.get(OVERVIEW_NETWORK_STATS)
|
||||
.find('button')
|
||||
.invoke('click');
|
||||
|
||||
cy.get(STAT_AUDITD.domId);
|
||||
|
||||
HOST_STATS.forEach(stat => {
|
||||
|
|
|
@ -25,6 +25,9 @@ import {
|
|||
DEFAULT_FROM,
|
||||
DEFAULT_TO,
|
||||
DEFAULT_SIGNALS_INDEX,
|
||||
ENABLE_NEWS_FEED_SETTING,
|
||||
NEWS_FEED_URL_SETTING,
|
||||
NEWS_FEED_URL_SETTING_DEFAULT,
|
||||
SIGNALS_INDEX_KEY,
|
||||
DEFAULT_SIGNALS_INDEX_KEY,
|
||||
} from './common/constants';
|
||||
|
@ -133,6 +136,29 @@ export const siem = (kibana: any) => {
|
|||
category: ['siem'],
|
||||
requiresPageReload: true,
|
||||
},
|
||||
[ENABLE_NEWS_FEED_SETTING]: {
|
||||
name: i18n.translate('xpack.siem.uiSettings.enableNewsFeedLabel', {
|
||||
defaultMessage: 'News feed',
|
||||
}),
|
||||
value: true,
|
||||
description: i18n.translate('xpack.siem.uiSettings.enableNewsFeedDescription', {
|
||||
defaultMessage: '<p>Enables the News feed</p>',
|
||||
}),
|
||||
type: 'boolean',
|
||||
category: ['siem'],
|
||||
requiresPageReload: true,
|
||||
},
|
||||
[NEWS_FEED_URL_SETTING]: {
|
||||
name: i18n.translate('xpack.siem.uiSettings.newsFeedUrl', {
|
||||
defaultMessage: 'News feed URL',
|
||||
}),
|
||||
value: NEWS_FEED_URL_SETTING_DEFAULT,
|
||||
description: i18n.translate('xpack.siem.uiSettings.newsFeedUrlDescription', {
|
||||
defaultMessage: '<p>News feed content will be retrieved from this URL</p>',
|
||||
}),
|
||||
category: ['siem'],
|
||||
requiresPageReload: true,
|
||||
},
|
||||
},
|
||||
mappings: savedObjectMappings,
|
||||
},
|
||||
|
|
|
@ -14,9 +14,13 @@ import { MatrixHistogramOption } from '../matrix_histogram/types';
|
|||
import { MatrixHistogramContainer } from '../../containers/matrix_histogram';
|
||||
import { MatrixHistogramGqlQuery } from '../../containers/matrix_histogram/index.gql_query';
|
||||
const ID = 'alertsOverTimeQuery';
|
||||
const alertsStackByOptions: MatrixHistogramOption[] = [
|
||||
export const alertsStackByOptions: MatrixHistogramOption[] = [
|
||||
{
|
||||
text: i18n.ALERTS_STACK_BY_MODULE,
|
||||
text: i18n.CATEGORY,
|
||||
value: 'event.category',
|
||||
},
|
||||
{
|
||||
text: i18n.MODULE,
|
||||
value: 'event.module',
|
||||
},
|
||||
];
|
||||
|
@ -51,7 +55,7 @@ export const AlertsView = ({
|
|||
<MatrixHistogramContainer
|
||||
dataKey={dataKey}
|
||||
deleteQuery={deleteQuery}
|
||||
defaultStackByOption={alertsStackByOptions[0]}
|
||||
defaultStackByOption={alertsStackByOptions[1]}
|
||||
endDate={endDate}
|
||||
errorMessage={i18n.ERROR_FETCHING_ALERTS_DATA}
|
||||
filterQuery={filterQuery}
|
||||
|
|
|
@ -41,3 +41,11 @@ export const ERROR_FETCHING_ALERTS_DATA = i18n.translate(
|
|||
defaultMessage: 'Failed to query alerts data',
|
||||
}
|
||||
);
|
||||
|
||||
export const CATEGORY = i18n.translate('xpack.siem.alertsView.categoryLabel', {
|
||||
defaultMessage: 'category',
|
||||
});
|
||||
|
||||
export const MODULE = i18n.translate('xpack.siem.alertsView.moduleLabel', {
|
||||
defaultMessage: 'module',
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ exports[`renders correctly 1`] = `
|
|||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
iconType="securityAnalyticsApp"
|
||||
title={
|
||||
<h2>
|
||||
My Super Title
|
||||
|
|
|
@ -43,6 +43,7 @@ export const EmptyPage = React.memo<EmptyPageProps>(
|
|||
...rest
|
||||
}) => (
|
||||
<EmptyPrompt
|
||||
iconType="securityAnalyticsApp"
|
||||
title={<h2>{title}</h2>}
|
||||
body={message && <p>{message}</p>}
|
||||
actions={
|
||||
|
|
|
@ -10,6 +10,13 @@ export const SHOWING = i18n.translate('xpack.siem.eventsViewer.showingLabel', {
|
|||
defaultMessage: 'Showing',
|
||||
});
|
||||
|
||||
export const ERROR_FETCHING_EVENTS_DATA = i18n.translate(
|
||||
'xpack.siem.eventsViewer.errorFetchingEventsData',
|
||||
{
|
||||
defaultMessage: 'Failed to query events data',
|
||||
}
|
||||
);
|
||||
|
||||
export const EVENTS = i18n.translate('xpack.siem.eventsViewer.eventsLabel', {
|
||||
defaultMessage: 'Events',
|
||||
});
|
||||
|
|
|
@ -10,18 +10,61 @@ import { FormattedRelative } from '@kbn/i18n/react';
|
|||
|
||||
import { useDateFormat, useTimeZone } from '../../hooks';
|
||||
import { getOrEmptyTagFromValue } from '../empty_value';
|
||||
import { useUiSetting$ } from '../../lib/kibana';
|
||||
import { LocalizedDateTooltip } from '../localized_date_tooltip';
|
||||
import { getMaybeDate } from './maybe_date';
|
||||
|
||||
export const PreferenceFormattedDate = React.memo<{ value: Date }>(({ value }) => {
|
||||
const dateFormat = useDateFormat();
|
||||
const timeZone = useTimeZone();
|
||||
|
||||
return <>{moment.tz(value, timeZone).format(dateFormat)}</>;
|
||||
});
|
||||
export const PreferenceFormattedDate = React.memo<{ dateFormat?: string; value: Date }>(
|
||||
({ value, dateFormat = useDateFormat() }) => (
|
||||
<>{moment.tz(value, useTimeZone()).format(dateFormat)}</>
|
||||
)
|
||||
);
|
||||
|
||||
PreferenceFormattedDate.displayName = 'PreferenceFormattedDate';
|
||||
|
||||
/**
|
||||
* This function may be passed to `Array.find()` to locate the `P1DT`
|
||||
* configuration (sub) setting, a string array that contains two entries
|
||||
* like the following example: `['P1DT', 'YYYY-MM-DD']`.
|
||||
*/
|
||||
export const isP1DTFormatterSetting = (formatNameFormatterPair?: string[]) =>
|
||||
Array.isArray(formatNameFormatterPair) &&
|
||||
formatNameFormatterPair[0] === 'P1DT' &&
|
||||
formatNameFormatterPair.length === 2;
|
||||
|
||||
/**
|
||||
* Renders a date in `P1DT` format, e.g. `YYYY-MM-DD`, as specified by
|
||||
* the `P1DT1` entry in the `dateFormat:scaled` Kibana Advanced setting.
|
||||
*
|
||||
* If the `P1DT` format is not specified in the `dateFormat:scaled` setting,
|
||||
* the fallback format `YYYY-MM-DD` will be applied
|
||||
*/
|
||||
export const PreferenceFormattedP1DTDate = React.memo<{ value: Date }>(({ value }) => {
|
||||
/**
|
||||
* A fallback "format name / formatter" 2-tuple for the `P1DT` formatter, which is
|
||||
* one of many such pairs expected to be contained in the `dateFormat:scaled`
|
||||
* Kibana advanced setting.
|
||||
*/
|
||||
const FALLBACK_DATE_FORMAT_SCALED_P1DT = ['P1DT', 'YYYY-MM-DD'];
|
||||
|
||||
// Read the 'dateFormat:scaled' Kibana Advanced setting, which contains 2-tuple sub-settings:
|
||||
const [scaledDateFormatPreference] = useUiSetting$<string[][]>('dateFormat:scaled');
|
||||
|
||||
// attempt to find the nested `['P1DT', 'formatString']` setting
|
||||
const maybeP1DTFormatter = Array.isArray(scaledDateFormatPreference)
|
||||
? scaledDateFormatPreference.find(isP1DTFormatterSetting)
|
||||
: null;
|
||||
|
||||
const p1dtFormat =
|
||||
Array.isArray(maybeP1DTFormatter) && maybeP1DTFormatter.length === 2
|
||||
? maybeP1DTFormatter[1]
|
||||
: FALLBACK_DATE_FORMAT_SCALED_P1DT[1];
|
||||
|
||||
return <PreferenceFormattedDate dateFormat={p1dtFormat} value={value} />;
|
||||
});
|
||||
|
||||
PreferenceFormattedP1DTDate.displayName = 'PreferenceFormattedP1DTDate';
|
||||
|
||||
/**
|
||||
* Renders the specified date value in a format determined by the user's preferences,
|
||||
* with a tooltip that renders:
|
||||
|
|
|
@ -30,48 +30,11 @@ exports[`HeaderGlobal it renders 1`] = `
|
|||
<FlexItem
|
||||
component="nav"
|
||||
>
|
||||
<SiemNavigation
|
||||
display="condensed"
|
||||
navTabs={
|
||||
Object {
|
||||
"detection-engine": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/detection-engine",
|
||||
"id": "detection-engine",
|
||||
"name": "Detection engine",
|
||||
"urlKey": "detection-engine",
|
||||
},
|
||||
"hosts": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/hosts",
|
||||
"id": "hosts",
|
||||
"name": "Hosts",
|
||||
"urlKey": "host",
|
||||
},
|
||||
"network": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/network",
|
||||
"id": "network",
|
||||
"name": "Network",
|
||||
"urlKey": "network",
|
||||
},
|
||||
"overview": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/overview",
|
||||
"id": "overview",
|
||||
"name": "Overview",
|
||||
"urlKey": "overview",
|
||||
},
|
||||
"timelines": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/timelines",
|
||||
"id": "timelines",
|
||||
"name": "Timelines",
|
||||
"urlKey": "timeline",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<WithSource
|
||||
sourceId="default"
|
||||
>
|
||||
<Component />
|
||||
</WithSource>
|
||||
</FlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlexItem>
|
||||
|
|
|
@ -16,6 +16,7 @@ import { getOverviewUrl } from '../link_to';
|
|||
import { MlPopover } from '../ml_popover/ml_popover';
|
||||
import { SiemNavigation } from '../navigation';
|
||||
import * as i18n from './translations';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
|
||||
const Wrapper = styled.header`
|
||||
${({ theme }) => css`
|
||||
|
@ -47,14 +48,25 @@ export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine
|
|||
</FlexItem>
|
||||
|
||||
<FlexItem component="nav">
|
||||
<SiemNavigation
|
||||
display="condensed"
|
||||
navTabs={
|
||||
hideDetectionEngine
|
||||
? pickBy((value, key) => key !== SiemPageName.detectionEngine, navTabs)
|
||||
: navTabs
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<SiemNavigation
|
||||
display="condensed"
|
||||
navTabs={
|
||||
hideDetectionEngine
|
||||
? pickBy((_, key) => key !== SiemPageName.detectionEngine, navTabs)
|
||||
: navTabs
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<SiemNavigation
|
||||
display="condensed"
|
||||
navTabs={pickBy((_, key) => key === SiemPageName.overview, navTabs)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</WithSource>
|
||||
</FlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlexItem>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { documentationLinks } from 'ui/documentation_links';
|
||||
|
||||
export const HelpMenu = React.memo(() => {
|
||||
useEffect(() => {
|
||||
|
@ -15,6 +16,14 @@ export const HelpMenu = React.memo(() => {
|
|||
defaultMessage: 'SIEM',
|
||||
}),
|
||||
links: [
|
||||
{
|
||||
content: i18n.translate('xpack.siem.chrome.helpMenu.documentation', {
|
||||
defaultMessage: 'SIEM documentation',
|
||||
}),
|
||||
href: documentationLinks.siem.guide,
|
||||
iconType: 'documents',
|
||||
linkType: 'custom',
|
||||
},
|
||||
{
|
||||
linkType: 'discuss',
|
||||
href: 'https://discuss.elastic.co/c/siem',
|
||||
|
|
|
@ -10,6 +10,6 @@ export {
|
|||
RedirectToDetectionEnginePage,
|
||||
} from './redirect_to_detection_engine';
|
||||
export { getOverviewUrl, RedirectToOverviewPage } from './redirect_to_overview';
|
||||
export { getHostsUrl, getHostDetailsUrl } from './redirect_to_hosts';
|
||||
export { getHostDetailsUrl, getHostsUrl } from './redirect_to_hosts';
|
||||
export { getNetworkUrl, getIPDetailsUrl, RedirectToNetworkPage } from './redirect_to_network';
|
||||
export { getTimelinesUrl, RedirectToTimelinesPage } from './redirect_to_timelines';
|
||||
|
|
|
@ -38,11 +38,11 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
|
|||
/>
|
||||
<Route
|
||||
component={RedirectToHostsPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:tabName(${HostsTableType.hosts}|${HostsTableType.authentications}|${HostsTableType.uncommonProcesses}|${HostsTableType.anomalies}|${HostsTableType.events})`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:tabName(${HostsTableType.hosts}|${HostsTableType.authentications}|${HostsTableType.uncommonProcesses}|${HostsTableType.anomalies}|${HostsTableType.events}|${HostsTableType.alerts})`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToHostDetailsPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:detailName/:tabName(${HostsTableType.authentications}|${HostsTableType.uncommonProcesses}|${HostsTableType.anomalies}|${HostsTableType.events})`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:detailName/:tabName(${HostsTableType.authentications}|${HostsTableType.uncommonProcesses}|${HostsTableType.anomalies}|${HostsTableType.events}|${HostsTableType.alerts})`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToHostDetailsPage}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { ScaleType } from '@elastic/charts';
|
|||
|
||||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { EuiLoadingContent, EuiSelect } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiSelect } from '@elastic/eui';
|
||||
import { noop } from 'lodash/fp';
|
||||
import * as i18n from './translations';
|
||||
import { BarChart } from '../charts/barchart';
|
||||
|
@ -39,6 +39,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
endDate,
|
||||
errorMessage,
|
||||
filterQuery,
|
||||
headerChildren,
|
||||
hideHistogramIfEmpty = false,
|
||||
id,
|
||||
isAlertsHistogram,
|
||||
|
@ -66,12 +67,12 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
}) => {
|
||||
const barchartConfigs = getBarchartConfigs({
|
||||
from: startDate,
|
||||
legendPosition,
|
||||
to: endDate,
|
||||
onBrushEnd: updateDateRange,
|
||||
scaleType,
|
||||
yTickFormatter,
|
||||
showLegend,
|
||||
legendPosition,
|
||||
});
|
||||
const [showInspect, setShowInspect] = useState(false);
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
|
@ -90,6 +91,8 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
const [selectedStackByOption, setSelectedStackByOption] = useState<MatrixHistogramOption>(
|
||||
defaultStackByOption
|
||||
);
|
||||
|
||||
const [titleWithStackByField, setTitle] = useState<string>('');
|
||||
const [subtitleWithCounts, setSubtitle] = useState<string>('');
|
||||
const [hideHistogram, setHideHistogram] = useState<boolean>(hideHistogramIfEmpty);
|
||||
const [barChartData, setBarChartData] = useState<ChartSeriesData[] | null>(null);
|
||||
|
@ -130,6 +133,8 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (title != null) setTitle(typeof title === 'function' ? title(selectedStackByOption) : title);
|
||||
|
||||
if (subtitle != null)
|
||||
setSubtitle(typeof subtitle === 'function' ? subtitle(totalCount) : subtitle);
|
||||
|
||||
|
@ -169,17 +174,22 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
>
|
||||
<HeaderSection
|
||||
id={id}
|
||||
title={title}
|
||||
title={titleWithStackByField}
|
||||
subtitle={!loading && (totalCount >= 0 ? subtitleWithCounts : null)}
|
||||
>
|
||||
{stackByOptions && (
|
||||
<EuiSelect
|
||||
onChange={setSelectedChartOptionCallback}
|
||||
options={stackByOptions}
|
||||
prepend={i18n.STACK_BY}
|
||||
value={selectedStackByOption?.value}
|
||||
/>
|
||||
)}
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
{stackByOptions && (
|
||||
<EuiSelect
|
||||
onChange={setSelectedChartOptionCallback}
|
||||
options={stackByOptions}
|
||||
prepend={i18n.STACK_BY}
|
||||
value={selectedStackByOption?.value}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{headerChildren}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</HeaderSection>
|
||||
{loading ? (
|
||||
<EuiLoadingContent data-test-subj="initialLoadingPanelMatrixOverTime" lines={10} />
|
||||
|
|
|
@ -28,12 +28,16 @@ export interface MatrixHistogramOption {
|
|||
}
|
||||
|
||||
export type GetSubTitle = (count: number) => string;
|
||||
export type GetTitle = (matrixHistogramOption: MatrixHistogramOption) => string;
|
||||
|
||||
export interface MatrixHistogramBasicProps {
|
||||
defaultIndex: string[];
|
||||
defaultStackByOption: MatrixHistogramOption;
|
||||
endDate: number;
|
||||
headerChildren?: React.ReactNode;
|
||||
hideHistogramIfEmpty?: boolean;
|
||||
id: string;
|
||||
legendPosition?: Position;
|
||||
mapping?: MatrixHistogramMappingTypes;
|
||||
setQuery: SetQuery;
|
||||
sourceId: string;
|
||||
|
@ -56,7 +60,7 @@ export interface MatrixHistogramQueryProps {
|
|||
stackByField: string;
|
||||
skip: boolean;
|
||||
startDate: number;
|
||||
title: string;
|
||||
title: string | GetTitle;
|
||||
isAlertsHistogram?: boolean;
|
||||
isAnomaliesHistogram?: boolean;
|
||||
isAuthenticationsHistogram?: boolean;
|
||||
|
|
|
@ -11,20 +11,20 @@ import { MatrixHistogramDataTypes, MatrixHistogramMappingTypes } from './types';
|
|||
|
||||
export const getBarchartConfigs = ({
|
||||
from,
|
||||
legendPosition,
|
||||
to,
|
||||
scaleType,
|
||||
onBrushEnd,
|
||||
yTickFormatter,
|
||||
showLegend,
|
||||
legendPosition,
|
||||
}: {
|
||||
from: number;
|
||||
legendPosition?: Position;
|
||||
to: number;
|
||||
scaleType: ScaleType;
|
||||
onBrushEnd: UpdateDateRange;
|
||||
yTickFormatter?: (value: number) => string;
|
||||
showLegend?: boolean;
|
||||
legendPosition?: Position;
|
||||
}) => ({
|
||||
series: {
|
||||
xScaleType: scaleType || ScaleType.Time,
|
||||
|
@ -40,7 +40,7 @@ export const getBarchartConfigs = ({
|
|||
tickSize: 8,
|
||||
},
|
||||
settings: {
|
||||
legendPosition: legendPosition || Position.Bottom,
|
||||
legendPosition: legendPosition ?? Position.Bottom,
|
||||
onBrushEnd,
|
||||
showLegend: showLegend || true,
|
||||
theme: {
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { NewsItem, RawNewsApiItem, RawNewsApiResponse } from './types';
|
||||
import { throwIfNotOk } from '../../hooks/api/api';
|
||||
|
||||
/**
|
||||
* Combines the URL specified with the `newsFeedUrlSetting` with the Kibana
|
||||
* version returned from `getKibanaVersion` to form a complete path to the
|
||||
* news (specific to the current version of Kibana)
|
||||
*/
|
||||
export const getNewsFeedUrl = ({
|
||||
newsFeedUrlSetting,
|
||||
getKibanaVersion,
|
||||
}: {
|
||||
newsFeedUrlSetting: string;
|
||||
getKibanaVersion: () => string;
|
||||
}) => [newsFeedUrlSetting, `v${getKibanaVersion()}.json`].join('/');
|
||||
|
||||
export const NEWS_FEED_FALLBACK_LANGUAGE = 'en';
|
||||
|
||||
/**
|
||||
* Returns the current locale of the browser as specified in the `document`,
|
||||
* or the value of `fallback` if the locale could not be retrieved
|
||||
*/
|
||||
export const getLocale = (fallback: string): string =>
|
||||
document.documentElement.lang?.toLowerCase() ?? fallback; // use the `lang` attribute of the `html` tag
|
||||
|
||||
const NO_NEWS_ITEMS: NewsItem[] = [];
|
||||
|
||||
/**
|
||||
* Transforms a `RawNewsApiResponse` from the news feed API to a collection of
|
||||
* `NewsItem`s
|
||||
*/
|
||||
export const getNewsItemsFromApiResponse = (response?: RawNewsApiResponse): NewsItem[] => {
|
||||
const locale = getLocale(NEWS_FEED_FALLBACK_LANGUAGE);
|
||||
|
||||
if (response == null || response.items == null) {
|
||||
return NO_NEWS_ITEMS;
|
||||
}
|
||||
|
||||
return response.items
|
||||
.filter((x: RawNewsApiItem | null) => x != null)
|
||||
.map<NewsItem>(x => ({
|
||||
description:
|
||||
get(locale, x.description) ?? get(NEWS_FEED_FALLBACK_LANGUAGE, x.description) ?? '',
|
||||
expireOn: new Date(x.expire_on ?? ''),
|
||||
hash: x.hash ?? uuid.v4(),
|
||||
imageUrl: x.image_url ?? null,
|
||||
linkUrl: get(locale, x.link_url) ?? get(NEWS_FEED_FALLBACK_LANGUAGE, x.link_url) ?? '',
|
||||
publishOn: new Date(x.publish_on ?? ''),
|
||||
title: get(locale, x.title) ?? get(NEWS_FEED_FALLBACK_LANGUAGE, x.title) ?? '',
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches `RawNewsApiResponse` from the specified `newsFeedUrl`, via a
|
||||
* cross-origin (CORS) request. This function throws an error if the request
|
||||
* fails
|
||||
*/
|
||||
export const fetchNews = async ({
|
||||
newsFeedUrl,
|
||||
}: {
|
||||
newsFeedUrl: string;
|
||||
}): Promise<RawNewsApiResponse> => {
|
||||
const response = await fetch(newsFeedUrl, {
|
||||
credentials: 'omit',
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
});
|
||||
|
||||
await throwIfNotOk(response);
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns false if `now` is before the `NewsItem` `publishOn` date, or
|
||||
* after the `expireOn` date
|
||||
*/
|
||||
export const showNewsItem = ({ publishOn, expireOn }: NewsItem): boolean =>
|
||||
!moment().isBefore(publishOn) && !moment().isAfter(expireOn);
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { fetchNews, getNewsFeedUrl, getNewsItemsFromApiResponse } from './helpers';
|
||||
import { useUiSetting$ } from '../../lib/kibana';
|
||||
import { NewsFeed } from './news_feed';
|
||||
import { NewsItem } from './types';
|
||||
|
||||
export const StatefulNewsFeed = React.memo<{
|
||||
enableNewsFeedSetting: string;
|
||||
newsFeedSetting: string;
|
||||
}>(({ enableNewsFeedSetting, newsFeedSetting }) => {
|
||||
const [enableNewsFeed] = useUiSetting$<boolean>(enableNewsFeedSetting);
|
||||
const [newsFeedUrlSetting] = useUiSetting$<string>(newsFeedSetting);
|
||||
const [news, setNews] = useState<NewsItem[] | null>(null);
|
||||
|
||||
const newsFeedUrl = getNewsFeedUrl({
|
||||
newsFeedUrlSetting,
|
||||
getKibanaVersion: chrome.getKibanaVersion,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const apiResponse = await fetchNews({ newsFeedUrl });
|
||||
|
||||
if (!canceled) {
|
||||
setNews(getNewsItemsFromApiResponse(apiResponse));
|
||||
}
|
||||
} catch {
|
||||
if (!canceled) {
|
||||
setNews([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (enableNewsFeed) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [enableNewsFeed, newsFeedUrl]);
|
||||
|
||||
return <>{enableNewsFeed ? <NewsFeed news={news} /> : null}</>;
|
||||
});
|
||||
|
||||
StatefulNewsFeed.displayName = 'StatefulNewsFeed';
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { NoNews } from './no_news';
|
||||
import { NEWS_FEED_TITLE } from '../../pages/overview/translations';
|
||||
import { Post } from './post';
|
||||
import { SidebarHeader } from '../sidebar_header';
|
||||
import { NewsItem } from './types';
|
||||
|
||||
interface Props {
|
||||
news: NewsItem[] | null | undefined;
|
||||
}
|
||||
|
||||
export const NewsFeed = React.memo<Props>(({ news }) => {
|
||||
if (news == null) {
|
||||
return <EuiLoadingSpinner size="m" />;
|
||||
}
|
||||
|
||||
if (news.length === 0) {
|
||||
return <NoNews />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarHeader title={NEWS_FEED_TITLE} />
|
||||
{news.map((n: NewsItem) => (
|
||||
<React.Fragment key={n.hash}>
|
||||
<Post newsItem={n} />
|
||||
<EuiSpacer size="l" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
NewsFeed.displayName = 'NewsFeed';
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
/** prevents links to the new pages from accessing `window.opener` */
|
||||
const REL_NOOPENER = 'noopener';
|
||||
|
||||
/** prevents search engine manipulation by noting the linked document is not trusted or endorsed by us */
|
||||
const REL_NOFOLLOW = 'nofollow';
|
||||
|
||||
/** prevents the browser from sending the current address as referrer via the Referer HTTP header */
|
||||
const REL_NOREFERRER = 'noreferrer';
|
||||
|
||||
/** A hyperlink to a (presumed to be external) news site */
|
||||
export const NewsLink = ({ href, children }: { href: string; children: React.ReactNode }) => (
|
||||
<EuiLink href={href} rel={`${REL_NOOPENER} ${REL_NOFOLLOW} ${REL_NOREFERRER}`} target="_blank">
|
||||
{children}
|
||||
</EuiLink>
|
||||
);
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export const NoNews = React.memo(() => (
|
||||
<>
|
||||
<EuiText color="subdued" size="s">
|
||||
{i18n.NO_NEWS_MESSAGE}
|
||||
<EuiLink href={'/app/kibana#/management/kibana/settings'}>
|
||||
{i18n.ADVANCED_SETTINGS_LINK_TITLE}
|
||||
</EuiLink>
|
||||
{'.'}
|
||||
</EuiText>
|
||||
</>
|
||||
));
|
||||
|
||||
NoNews.displayName = 'NoNews';
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { PreferenceFormattedP1DTDate } from '../../formatted_date';
|
||||
import { showNewsItem } from '../helpers';
|
||||
import { NewsLink } from '../news_link';
|
||||
import { NewsItem } from '../types';
|
||||
|
||||
const NewsItemPreviewImage = styled.img`
|
||||
height: 56px;
|
||||
margin-left: 16px;
|
||||
min-width: 56px;
|
||||
padding: 4px;
|
||||
width: 56px;
|
||||
`;
|
||||
|
||||
export const Post = React.memo<{ newsItem: NewsItem }>(({ newsItem }) => {
|
||||
const { linkUrl, title, publishOn, description, imageUrl } = newsItem;
|
||||
|
||||
if (!showNewsItem(newsItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<NewsLink href={linkUrl}>
|
||||
<EuiText size="s">{title}</EuiText>
|
||||
</NewsLink>
|
||||
|
||||
<EuiText color="subdued" size="xs">
|
||||
<PreferenceFormattedP1DTDate value={publishOn} />
|
||||
<EuiSpacer size="s" />
|
||||
<div>{description}</div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
{imageUrl && (
|
||||
<NewsLink href={linkUrl}>
|
||||
<NewsItemPreviewImage alt={title} className="euiPanel" src={imageUrl} />
|
||||
</NewsLink>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
Post.displayName = 'Post';
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const NO_NEWS_MESSAGE = i18n.translate('xpack.siem.newsFeed.noNewsMessage', {
|
||||
defaultMessage:
|
||||
'Your current News feed URL returned no recent news. You may update the URL or disable security news via',
|
||||
});
|
||||
|
||||
export const ADVANCED_SETTINGS_LINK_TITLE = i18n.translate(
|
||||
'xpack.siem.newsFeed.advancedSettingsLinkTitle',
|
||||
{
|
||||
defaultMessage: 'SIEM advanced settings',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* For rendering, `RawNewsApiItem`s are transformed to this
|
||||
* representation of a news item
|
||||
*/
|
||||
export interface NewsItem {
|
||||
description: string;
|
||||
expireOn: Date;
|
||||
hash: string;
|
||||
imageUrl: string | null;
|
||||
linkUrl: string;
|
||||
publishOn: Date;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw (wire format) representation of a News API item
|
||||
*/
|
||||
export interface RawNewsApiItem {
|
||||
badge?: { [lang: string]: string | null } | null;
|
||||
description?: { [lang: string]: string | null } | null;
|
||||
expire_on?: Date | null;
|
||||
hash?: string | null;
|
||||
image_url?: string | null;
|
||||
languages?: string[] | null;
|
||||
link_text?: { [lang: string]: string | null } | null;
|
||||
link_url?: { [lang: string]: string | null } | null;
|
||||
publish_on?: Date | null;
|
||||
title?: { [lang: string]: string } | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the shape of a raw response from the News API
|
||||
*/
|
||||
export interface RawNewsApiResponse {
|
||||
items?: RawNewsApiItem[];
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Position } from '@elastic/charts';
|
||||
import { omit } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -12,7 +13,9 @@ import { SetQuery } from '../../pages/hosts/navigation/types';
|
|||
|
||||
interface OwnProps {
|
||||
deleteQuery?: ({ id }: { id: string }) => void;
|
||||
headerChildren?: React.ReactNode;
|
||||
id: string;
|
||||
legendPosition?: Position;
|
||||
loading: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
setQuery: SetQuery;
|
||||
|
|
|
@ -5,23 +5,28 @@
|
|||
*/
|
||||
|
||||
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants';
|
||||
import { ESQuery } from '../../../../../common/typed_json';
|
||||
import {
|
||||
ID as OverviewHostQueryId,
|
||||
OverviewHostQuery,
|
||||
} from '../../../../containers/overview/overview_host';
|
||||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { OverviewHostStats } from '../overview_host_stats';
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { useUiSetting$ } from '../../../../lib/kibana';
|
||||
import { getHostsUrl } from '../../../link_to';
|
||||
import { getOverviewHostStats, OverviewHostStats } from '../overview_host_stats';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { InspectButtonContainer } from '../../../inspect';
|
||||
|
||||
export interface OwnProps {
|
||||
startDate: number;
|
||||
endDate: number;
|
||||
filterQuery?: ESQuery | string;
|
||||
setQuery: ({
|
||||
id,
|
||||
inspect,
|
||||
|
@ -37,44 +42,76 @@ export interface OwnProps {
|
|||
|
||||
const OverviewHostStatsManage = manageQuery(OverviewHostStats);
|
||||
type OverviewHostProps = OwnProps;
|
||||
export const OverviewHost = React.memo<OverviewHostProps>(
|
||||
({ endDate, filterQuery, startDate, setQuery }) => {
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
|
||||
const OverviewHostComponent: React.FC<OverviewHostProps> = ({ endDate, startDate, setQuery }) => (
|
||||
<EuiFlexItem>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewHostQueryId}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsSubtitle"
|
||||
defaultMessage="Showing: Last 24 hours"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage id="xpack.siem.overview.hostsTitle" defaultMessage="Host events" />
|
||||
}
|
||||
>
|
||||
<EuiButton href={getHostsUrl()}>
|
||||
<FormattedMessage id="xpack.siem.overview.hostsAction" defaultMessage="View hosts" />
|
||||
</EuiButton>
|
||||
</HeaderSection>
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<OverviewHostQuery
|
||||
endDate={endDate}
|
||||
filterQuery={filterQuery}
|
||||
sourceId="default"
|
||||
startDate={startDate}
|
||||
>
|
||||
{({ overviewHost, loading, id, inspect, refetch }) => {
|
||||
const hostEventsCount = getOverviewHostStats(overviewHost).reduce(
|
||||
(total, stat) => total + stat.count,
|
||||
0
|
||||
);
|
||||
const formattedHostEventsCount = numeral(hostEventsCount).format(
|
||||
defaultNumberFormat
|
||||
);
|
||||
|
||||
<OverviewHostQuery endDate={endDate} sourceId="default" startDate={startDate}>
|
||||
{({ overviewHost, loading, id, inspect, refetch }) => (
|
||||
<OverviewHostStatsManage
|
||||
loading={loading}
|
||||
data={overviewHost}
|
||||
setQuery={setQuery}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
refetch={refetch}
|
||||
/>
|
||||
)}
|
||||
</OverviewHostQuery>
|
||||
</EuiPanel>
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
return (
|
||||
<>
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewHostQueryId}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
defaultMessage="Showing: {formattedHostEventsCount} {hostEventsCount, plural, one {event} other {events}}"
|
||||
id="xpack.siem.overview.overviewHost.hostsSubtitle"
|
||||
values={{
|
||||
formattedHostEventsCount,
|
||||
hostEventsCount,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsTitle"
|
||||
defaultMessage="Host events"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButton href={getHostsUrl()}>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsAction"
|
||||
defaultMessage="View hosts"
|
||||
/>
|
||||
</EuiButton>
|
||||
</HeaderSection>
|
||||
|
||||
<OverviewHostStatsManage
|
||||
loading={loading}
|
||||
data={overviewHost}
|
||||
setQuery={setQuery}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</OverviewHostQuery>
|
||||
</EuiPanel>
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const OverviewHost = React.memo(OverviewHostComponent);
|
||||
OverviewHost.displayName = 'OverviewHost';
|
||||
|
|
|
@ -1,233 +1,637 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Overview Host Stat Data rendering it renders the default OverviewHostStats 1`] = `
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
<styled.div
|
||||
data-test-subj="overview-hosts-stats"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat Audit"
|
||||
id="xpack.siem.overview.auditBeatAuditTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-auditbeatAuditd"
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
gutterSize="none"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat"
|
||||
id="xpack.siem.overview.hostStatGroupAuditbeat"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={246351}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={157258653}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="host-stat-accordion-groupauditbeat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="73,847"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat File Integrity Module"
|
||||
id="xpack.siem.overview.auditBeatFimTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-auditbeatFIM"
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatAuditd"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Audit"
|
||||
id="xpack.siem.overview.auditBeatAuditTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-auditbeatAuditd"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={73847}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={246351}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatFIM"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="File Integrity Module"
|
||||
id="xpack.siem.overview.auditBeatFimTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-auditbeatFIM"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={107307}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={246351}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatLogin"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Login"
|
||||
id="xpack.siem.overview.auditBeatLoginTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-auditbeatLogin"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={60015}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={246351}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatPackage"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Package"
|
||||
id="xpack.siem.overview.auditBeatPackageTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-auditbeatPackage"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={2003}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={246351}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatProcess"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Process"
|
||||
id="xpack.siem.overview.auditBeatProcessTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-auditbeatProcess"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={1200}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={246351}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatUser"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="User"
|
||||
id="xpack.siem.overview.auditBeatUserTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-auditbeatUser"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={1979}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={246351}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
<EuiHorizontalRule
|
||||
margin="xs"
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
gutterSize="none"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame"
|
||||
id="xpack.siem.overview.hostStatGroupEndgame"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={156714735}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={157258653}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="host-stat-accordion-groupendgame"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="107,307"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat Login"
|
||||
id="xpack.siem.overview.auditBeatLoginTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-auditbeatLogin"
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameDns"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="DNS"
|
||||
id="xpack.siem.overview.endgameDnsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameDns"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39123}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameFile"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="File"
|
||||
id="xpack.siem.overview.endgameFileTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameFile"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39456}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameImageLoad"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Image Load"
|
||||
id="xpack.siem.overview.endgameImageLoadTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameImageLoad"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39789}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameNetwork"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Network"
|
||||
id="xpack.siem.overview.endgameNetworkTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameNetwork"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39101112}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameProcess"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Process"
|
||||
id="xpack.siem.overview.endgameProcessTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameProcess"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39131415}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameRegistry"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Registry"
|
||||
id="xpack.siem.overview.endgameRegistryTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameRegistry"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39161718}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="endgameSecurity"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Security"
|
||||
id="xpack.siem.overview.endgameSecurityTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-endgameSecurity"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={39202122}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={156714735}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
<EuiHorizontalRule
|
||||
margin="xs"
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
gutterSize="none"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat"
|
||||
id="xpack.siem.overview.hostStatGroupFilebeat"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={568}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={157258653}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="host-stat-accordion-groupfilebeat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="60,015"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat Package"
|
||||
id="xpack.siem.overview.auditBeatPackageTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-auditbeatPackage"
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="filebeatSystemModule"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="System Module"
|
||||
id="xpack.siem.overview.filebeatSystemModuleTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-filebeatSystemModule"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={568}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={568}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
<EuiHorizontalRule
|
||||
margin="xs"
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
gutterSize="none"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Winlogbeat"
|
||||
id="xpack.siem.overview.hostStatGroupWinlogbeat"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={296999}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={157258653}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="host-stat-accordion-groupwinlogbeat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="2,003"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat Process"
|
||||
id="xpack.siem.overview.auditBeatProcessTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-auditbeatProcess"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="1,200"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat User"
|
||||
id="xpack.siem.overview.auditBeatUserTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-auditbeatUser"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="1,979"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame DNS"
|
||||
id="xpack.siem.overview.endgameDnsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameDns"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,123"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame File"
|
||||
id="xpack.siem.overview.endgameFileTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameFile"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,456"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame Image Load"
|
||||
id="xpack.siem.overview.endgameImageLoadTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameImageLoad"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,789"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame Network"
|
||||
id="xpack.siem.overview.endgameNetworkTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameNetwork"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,101,112"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame Process"
|
||||
id="xpack.siem.overview.endgameProcessTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameProcess"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,131,415"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame Registry"
|
||||
id="xpack.siem.overview.endgameRegistryTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameRegistry"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,161,718"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Endgame Security"
|
||||
id="xpack.siem.overview.endgameSecurityTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-endgameSecurity"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="39,202,122"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat System Module"
|
||||
id="xpack.siem.overview.filebeatSystemModuleTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-filebeatSystemModule"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="568"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Winlogbeat"
|
||||
id="xpack.siem.overview.winlogbeatTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="host-stat-winlogbeat"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="296,999"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="winlogbeat"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Winlogbeat"
|
||||
id="xpack.siem.overview.winlogbeatTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="host-stat-winlogbeat"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={296999}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={296999}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
</styled.div>
|
||||
`;
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { OverviewHostStats } from '.';
|
||||
import { mockData } from './mock';
|
||||
import { TestProviders } from '../../../../mock/test_providers';
|
||||
|
||||
describe('Overview Host Stat Data', () => {
|
||||
describe('rendering', () => {
|
||||
|
@ -18,23 +19,51 @@ describe('Overview Host Stat Data', () => {
|
|||
});
|
||||
});
|
||||
describe('loading', () => {
|
||||
test('it does not show loading indicator when not loading', () => {
|
||||
const wrapper = shallow(<OverviewHostStats data={mockData.OverviewHost} loading={false} />);
|
||||
const loadingWrapper = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="host-stat-auditbeatAuditd"]')
|
||||
test('it does NOT show loading indicator when loading is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<OverviewHostStats data={mockData.OverviewHost} loading={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
// click the accordion to expand it
|
||||
wrapper
|
||||
.find('button')
|
||||
.first()
|
||||
.childAt(0);
|
||||
expect(loadingWrapper.prop('isLoading')).toBe(false);
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="host-stat-auditbeatAuditd"]')
|
||||
.first()
|
||||
.find('[data-test-subj="stat-value-loading-spinner"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
test('it does show loading indicator when loading', () => {
|
||||
const wrapper = shallow(<OverviewHostStats data={mockData.OverviewHost} loading={true} />);
|
||||
const loadingWrapper = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="host-stat-auditbeatAuditd"]')
|
||||
test('it shows loading indicator when loading is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<OverviewHostStats data={mockData.OverviewHost} loading={true} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
// click the accordion to expand it
|
||||
wrapper
|
||||
.find('button')
|
||||
.first()
|
||||
.childAt(0);
|
||||
expect(loadingWrapper.prop('isLoading')).toBe(true);
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="host-stat-auditbeatAuditd"]')
|
||||
.first()
|
||||
.find('[data-test-subj="stat-value-loading-spinner"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,209 +4,120 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListDescription,
|
||||
EuiDescriptionListTitle,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { has } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OverviewHostData } from '../../../../graphql/types';
|
||||
import { getEmptyTagValue } from '../../../empty_value';
|
||||
import { FormattedStat, StatGroup } from '../types';
|
||||
import { StatValue } from '../stat_value';
|
||||
|
||||
interface OverviewHostProps {
|
||||
data: OverviewHostData;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const overviewHostStats = (data: OverviewHostData) => [
|
||||
export const getOverviewHostStats = (data: OverviewHostData): FormattedStat[] => [
|
||||
{
|
||||
description:
|
||||
has('auditbeatAuditd', data) && data.auditbeatAuditd !== null
|
||||
? numeral(data.auditbeatAuditd).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatAuditTitle"
|
||||
defaultMessage="Auditbeat Audit"
|
||||
/>
|
||||
),
|
||||
count: data.auditbeatAuditd ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.auditBeatAuditTitle" defaultMessage="Audit" />,
|
||||
id: 'auditbeatAuditd',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('auditbeatFIM', data) && data.auditbeatFIM !== null
|
||||
? numeral(data.auditbeatFIM).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.auditbeatFIM ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatFimTitle"
|
||||
defaultMessage="Auditbeat File Integrity Module"
|
||||
defaultMessage="File Integrity Module"
|
||||
/>
|
||||
),
|
||||
id: 'auditbeatFIM',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('auditbeatLogin', data) && data.auditbeatLogin !== null
|
||||
? numeral(data.auditbeatLogin).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatLoginTitle"
|
||||
defaultMessage="Auditbeat Login"
|
||||
/>
|
||||
),
|
||||
count: data.auditbeatLogin ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.auditBeatLoginTitle" defaultMessage="Login" />,
|
||||
id: 'auditbeatLogin',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('auditbeatPackage', data) && data.auditbeatPackage !== null
|
||||
? numeral(data.auditbeatPackage).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.auditbeatPackage ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatPackageTitle"
|
||||
defaultMessage="Auditbeat Package"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.auditBeatPackageTitle" defaultMessage="Package" />
|
||||
),
|
||||
id: 'auditbeatPackage',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('auditbeatProcess', data) && data.auditbeatProcess !== null
|
||||
? numeral(data.auditbeatProcess).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.auditbeatProcess ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatProcessTitle"
|
||||
defaultMessage="Auditbeat Process"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.auditBeatProcessTitle" defaultMessage="Process" />
|
||||
),
|
||||
id: 'auditbeatProcess',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('auditbeatUser', data) && data.auditbeatUser !== null
|
||||
? numeral(data.auditbeatUser).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatUserTitle"
|
||||
defaultMessage="Auditbeat User"
|
||||
/>
|
||||
),
|
||||
count: data.auditbeatUser ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.auditBeatUserTitle" defaultMessage="User" />,
|
||||
id: 'auditbeatUser',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameDns', data) && data.endgameDns !== null
|
||||
? numeral(data.endgameDns).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage id="xpack.siem.overview.endgameDnsTitle" defaultMessage="Endgame DNS" />
|
||||
),
|
||||
count: data.endgameDns ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.endgameDnsTitle" defaultMessage="DNS" />,
|
||||
id: 'endgameDns',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameFile', data) && data.endgameFile !== null
|
||||
? numeral(data.endgameFile).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage id="xpack.siem.overview.endgameFileTitle" defaultMessage="Endgame File" />
|
||||
),
|
||||
count: data.endgameFile ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.endgameFileTitle" defaultMessage="File" />,
|
||||
id: 'endgameFile',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameImageLoad', data) && data.endgameImageLoad !== null
|
||||
? numeral(data.endgameImageLoad).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.endgameImageLoad ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.endgameImageLoadTitle"
|
||||
defaultMessage="Endgame Image Load"
|
||||
defaultMessage="Image Load"
|
||||
/>
|
||||
),
|
||||
id: 'endgameImageLoad',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameNetwork', data) && data.endgameNetwork !== null
|
||||
? numeral(data.endgameNetwork).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.endgameNetwork ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.endgameNetworkTitle"
|
||||
defaultMessage="Endgame Network"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.endgameNetworkTitle" defaultMessage="Network" />
|
||||
),
|
||||
id: 'endgameNetwork',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameProcess', data) && data.endgameProcess !== null
|
||||
? numeral(data.endgameProcess).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.endgameProcess ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.endgameProcessTitle"
|
||||
defaultMessage="Endgame Process"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.endgameProcessTitle" defaultMessage="Process" />
|
||||
),
|
||||
id: 'endgameProcess',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameRegistry', data) && data.endgameRegistry !== null
|
||||
? numeral(data.endgameRegistry).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.endgameRegistry ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.endgameRegistryTitle"
|
||||
defaultMessage="Endgame Registry"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.endgameRegistryTitle" defaultMessage="Registry" />
|
||||
),
|
||||
id: 'endgameRegistry',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('endgameSecurity', data) && data.endgameSecurity !== null
|
||||
? numeral(data.endgameSecurity).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.endgameSecurity ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.endgameSecurityTitle"
|
||||
defaultMessage="Endgame Security"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.endgameSecurityTitle" defaultMessage="Security" />
|
||||
),
|
||||
id: 'endgameSecurity',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('filebeatSystemModule', data) && data.filebeatSystemModule !== null
|
||||
? numeral(data.filebeatSystemModule).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.filebeatSystemModule ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.filebeatSystemModuleTitle"
|
||||
defaultMessage="Filebeat System Module"
|
||||
defaultMessage="System Module"
|
||||
/>
|
||||
),
|
||||
id: 'filebeatSystemModule',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('winlogbeat', data) && data.winlogbeat !== null
|
||||
? numeral(data.winlogbeat).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.winlogbeat ?? 0,
|
||||
title: (
|
||||
<FormattedMessage id="xpack.siem.overview.winlogbeatTitle" defaultMessage="Winlogbeat" />
|
||||
),
|
||||
|
@ -214,31 +125,128 @@ const overviewHostStats = (data: OverviewHostData) => [
|
|||
},
|
||||
];
|
||||
|
||||
export const DescriptionListDescription = styled(EuiDescriptionListDescription)`
|
||||
text-align: right;
|
||||
const HostStatsContainer = styled.div`
|
||||
.accordion-button {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
DescriptionListDescription.displayName = 'DescriptionListDescription';
|
||||
const hostStatGroups: StatGroup[] = [
|
||||
{
|
||||
groupId: 'auditbeat',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostStatGroupAuditbeat"
|
||||
defaultMessage="Auditbeat"
|
||||
/>
|
||||
),
|
||||
statIds: [
|
||||
'auditbeatAuditd',
|
||||
'auditbeatFIM',
|
||||
'auditbeatLogin',
|
||||
'auditbeatPackage',
|
||||
'auditbeatProcess',
|
||||
'auditbeatUser',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupId: 'endgame',
|
||||
name: (
|
||||
<FormattedMessage id="xpack.siem.overview.hostStatGroupEndgame" defaultMessage="Endgame" />
|
||||
),
|
||||
statIds: [
|
||||
'endgameDns',
|
||||
'endgameFile',
|
||||
'endgameImageLoad',
|
||||
'endgameNetwork',
|
||||
'endgameProcess',
|
||||
'endgameRegistry',
|
||||
'endgameSecurity',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupId: 'filebeat',
|
||||
name: (
|
||||
<FormattedMessage id="xpack.siem.overview.hostStatGroupFilebeat" defaultMessage="Filebeat" />
|
||||
),
|
||||
statIds: ['filebeatSystemModule'],
|
||||
},
|
||||
{
|
||||
groupId: 'winlogbeat',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostStatGroupWinlogbeat"
|
||||
defaultMessage="Winlogbeat"
|
||||
/>
|
||||
),
|
||||
statIds: ['winlogbeat'],
|
||||
},
|
||||
];
|
||||
|
||||
const StatValue = React.memo<{ isLoading: boolean; value: React.ReactNode | null | undefined }>(
|
||||
({ isLoading, value }) => (
|
||||
<>{isLoading ? <EuiLoadingSpinner size="m" /> : value != null ? value : getEmptyTagValue()}</>
|
||||
)
|
||||
);
|
||||
const Title = styled.div`
|
||||
margin-left: 24px;
|
||||
`;
|
||||
|
||||
StatValue.displayName = 'StatValue';
|
||||
export const OverviewHostStats = React.memo<OverviewHostProps>(({ data, loading }) => {
|
||||
const allHostStats = getOverviewHostStats(data);
|
||||
const allHostStatsCount = allHostStats.reduce((total, stat) => total + stat.count, 0);
|
||||
|
||||
export const OverviewHostStats = React.memo<OverviewHostProps>(({ data, loading }) => (
|
||||
<EuiDescriptionList type="column">
|
||||
{overviewHostStats(data).map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<EuiDescriptionListTitle>{item.title}</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription data-test-subj={`host-stat-${item.id}`}>
|
||||
<StatValue isLoading={loading} value={item.description} />
|
||||
</DescriptionListDescription>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</EuiDescriptionList>
|
||||
));
|
||||
return (
|
||||
<HostStatsContainer data-test-subj="overview-hosts-stats">
|
||||
{hostStatGroups.map((statGroup, i) => {
|
||||
const statsForGroup = allHostStats.filter(s => statGroup.statIds.includes(s.id));
|
||||
const statsForGroupCount = statsForGroup.reduce((total, stat) => total + stat.count, 0);
|
||||
|
||||
const accordionButton = useMemo(
|
||||
() => (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>{statGroup.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatValue
|
||||
count={statsForGroupCount}
|
||||
isGroupStat={true}
|
||||
isLoading={loading}
|
||||
max={allHostStatsCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[statGroup, statsForGroupCount, loading, allHostStatsCount]
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment key={statGroup.groupId}>
|
||||
<EuiAccordion
|
||||
id={`host-stat-accordion-group${statGroup.groupId}`}
|
||||
buttonContent={accordionButton}
|
||||
buttonContentClassName="accordion-button"
|
||||
>
|
||||
{statsForGroup.map(stat => (
|
||||
<EuiFlexGroup key={stat.id} justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s">
|
||||
<Title>{stat.title}</Title>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={`host-stat-${stat.id}`} grow={false}>
|
||||
<StatValue
|
||||
count={stat.count}
|
||||
isGroupStat={false}
|
||||
isLoading={loading}
|
||||
max={statsForGroupCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
</EuiAccordion>
|
||||
{i !== hostStatGroups.length - 1 && <EuiHorizontalRule margin="xs" />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</HostStatsContainer>
|
||||
);
|
||||
});
|
||||
|
||||
OverviewHostStats.displayName = 'OverviewHostStats';
|
||||
|
|
|
@ -5,23 +5,28 @@
|
|||
*/
|
||||
|
||||
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants';
|
||||
import { ESQuery } from '../../../../../common/typed_json';
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { useUiSetting$ } from '../../../../lib/kibana';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import {
|
||||
ID as OverviewNetworkQueryId,
|
||||
OverviewNetworkQuery,
|
||||
} from '../../../../containers/overview/overview_network';
|
||||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { OverviewNetworkStats } from '../overview_network_stats';
|
||||
import { getOverviewNetworkStats, OverviewNetworkStats } from '../overview_network_stats';
|
||||
import { getNetworkUrl } from '../../../link_to';
|
||||
import { InspectButtonContainer } from '../../../inspect';
|
||||
|
||||
export interface OwnProps {
|
||||
startDate: number;
|
||||
endDate: number;
|
||||
filterQuery?: ESQuery | string;
|
||||
setQuery: ({
|
||||
id,
|
||||
inspect,
|
||||
|
@ -37,49 +42,76 @@ export interface OwnProps {
|
|||
|
||||
const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats);
|
||||
|
||||
const OverviewNetworkComponent: React.FC<OwnProps> = ({ endDate, startDate, setQuery }) => (
|
||||
<EuiFlexItem>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewNetworkQueryId}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkSubtitle"
|
||||
defaultMessage="Showing: Last 24 hours"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkTitle"
|
||||
defaultMessage="Network events"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButton href={getNetworkUrl()}>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkAction"
|
||||
defaultMessage="View network"
|
||||
/>
|
||||
</EuiButton>
|
||||
</HeaderSection>
|
||||
export const OverviewNetwork = React.memo<OwnProps>(
|
||||
({ endDate, filterQuery, startDate, setQuery }) => {
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
|
||||
<OverviewNetworkQuery endDate={endDate} sourceId="default" startDate={startDate}>
|
||||
{({ overviewNetwork, loading, id, inspect, refetch }) => (
|
||||
<OverviewNetworkStatsManage
|
||||
loading={loading}
|
||||
data={overviewNetwork}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
/>
|
||||
)}
|
||||
</OverviewNetworkQuery>
|
||||
</EuiPanel>
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<OverviewNetworkQuery
|
||||
endDate={endDate}
|
||||
filterQuery={filterQuery}
|
||||
sourceId="default"
|
||||
startDate={startDate}
|
||||
>
|
||||
{({ overviewNetwork, loading, id, inspect, refetch }) => {
|
||||
const networkEventsCount = getOverviewNetworkStats(overviewNetwork).reduce(
|
||||
(total, stat) => total + stat.count,
|
||||
0
|
||||
);
|
||||
const formattedNetworkEventsCount = numeral(networkEventsCount).format(
|
||||
defaultNumberFormat
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewNetworkQueryId}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
defaultMessage="Showing: {formattedNetworkEventsCount} {networkEventsCount, plural, one {event} other {events}}"
|
||||
id="xpack.siem.overview.overviewNetwork.networkSubtitle"
|
||||
values={{
|
||||
formattedNetworkEventsCount,
|
||||
networkEventsCount,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkTitle"
|
||||
defaultMessage="Network events"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButton href={getNetworkUrl()}>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkAction"
|
||||
defaultMessage="View network"
|
||||
/>
|
||||
</EuiButton>
|
||||
</HeaderSection>
|
||||
|
||||
<OverviewNetworkStatsManage
|
||||
loading={loading}
|
||||
data={overviewNetwork}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</OverviewNetworkQuery>
|
||||
</EuiPanel>
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const OverviewNetwork = React.memo(OverviewNetworkComponent);
|
||||
OverviewNetwork.displayName = 'OverviewNetwork';
|
||||
|
|
|
@ -1,143 +1,407 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Overview Network Stat Data rendering it renders the default OverviewNetworkStats 1`] = `
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
<styled.div
|
||||
data-test-subj="overview-network-stats"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat Socket"
|
||||
id="xpack.siem.overview.auditBeatSocketTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-auditbeatSocket"
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
data-test-subj="network-stat-group-auditbeat"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Auditbeat"
|
||||
id="xpack.siem.overview.networkStatGroupAuditbeat"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={12}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={13748195}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="network-stat-accordion-groupauditbeat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="12"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat Cisco"
|
||||
id="xpack.siem.overview.filebeatCiscoTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-filebeatCisco"
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="auditbeatSocket"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Socket"
|
||||
id="xpack.siem.overview.auditBeatSocketTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-auditbeatSocket"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={12}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={12}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
<EuiHorizontalRule
|
||||
margin="xs"
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
data-test-subj="network-stat-group-filebeat"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat"
|
||||
id="xpack.siem.overview.networkStatGroupFilebeat"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={70860}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={13748195}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="network-stat-accordion-groupfilebeat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="999"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat Netflow"
|
||||
id="xpack.siem.overview.filebeatNetflowTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-filebeatNetflow"
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="filebeatCisco"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cisco"
|
||||
id="xpack.siem.overview.filebeatCiscoTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-filebeatCisco"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={999}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={70860}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="filebeatNetflow"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Netflow"
|
||||
id="xpack.siem.overview.filebeatNetflowTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-filebeatNetflow"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={7777}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={70860}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="filebeatPanw"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Palo Alto Networks"
|
||||
id="xpack.siem.overview.filebeatPanwTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-filebeatPanw"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={66}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={70860}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="filebeatSuricata"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Suricata"
|
||||
id="xpack.siem.overview.fileBeatSuricataTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-filebeatSuricata"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={60015}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={70860}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="filebeatZeek"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Zeek"
|
||||
id="xpack.siem.overview.fileBeatZeekTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-filebeatZeek"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={2003}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={70860}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
<EuiHorizontalRule
|
||||
margin="xs"
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<ForwardRef
|
||||
data-test-subj="network-stat-group-packetbeat"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Packetbeat"
|
||||
id="xpack.siem.overview.networkStatGroupPacketbeat"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<Memo(StatValue)
|
||||
count={13677323}
|
||||
isGroupStat={true}
|
||||
isLoading={false}
|
||||
max={13748195}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ForwardRef>
|
||||
}
|
||||
buttonContentClassName="accordion-button"
|
||||
id="network-stat-accordion-grouppacketbeat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="7,777"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat Palo Alto Networks"
|
||||
id="xpack.siem.overview.filebeatPanwTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-filebeatPanw"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="66"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat Suricata"
|
||||
id="xpack.siem.overview.fileBeatSuricataTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-filebeatSuricata"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="60,015"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Filebeat Zeek"
|
||||
id="xpack.siem.overview.fileBeatZeekTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-filebeatZeek"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="2,003"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Packetbeat DNS"
|
||||
id="xpack.siem.overview.packetBeatDnsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-packetbeatDNS"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="10,277,307"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Packetbeat Flow"
|
||||
id="xpack.siem.overview.packetBeatFlowTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-packetbeatFlow"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="16"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Packetbeat TLS"
|
||||
id="xpack.siem.overview.packetbeatTLSTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription
|
||||
data-test-subj="network-stat-packetbeatTLS"
|
||||
>
|
||||
<StatValue
|
||||
isLoading={false}
|
||||
value="3,400,000"
|
||||
/>
|
||||
</DescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="packetbeatDNS"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="DNS"
|
||||
id="xpack.siem.overview.packetBeatDnsTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-packetbeatDNS"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={10277307}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={13677323}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="packetbeatFlow"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="Flow"
|
||||
id="xpack.siem.overview.packetBeatFlowTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-packetbeatFlow"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={16}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={13677323}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
key="packetbeatTLS"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="s"
|
||||
>
|
||||
<styled.div>
|
||||
<FormattedMessage
|
||||
defaultMessage="TLS"
|
||||
id="xpack.siem.overview.packetbeatTLSTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</styled.div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
data-test-subj="network-stat-packetbeatTLS"
|
||||
grow={false}
|
||||
>
|
||||
<StatValue
|
||||
count={3400000}
|
||||
isGroupStat={false}
|
||||
isLoading={false}
|
||||
max={13677323}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
</styled.div>
|
||||
`;
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { OverviewNetworkStats } from '.';
|
||||
import { mockData } from './mock';
|
||||
import { TestProviders } from '../../../../mock/test_providers';
|
||||
|
||||
describe('Overview Network Stat Data', () => {
|
||||
describe('rendering', () => {
|
||||
|
@ -20,28 +21,52 @@ describe('Overview Network Stat Data', () => {
|
|||
});
|
||||
});
|
||||
describe('loading', () => {
|
||||
test('it does not show loading indicator when not loading', () => {
|
||||
const wrapper = shallow(
|
||||
<OverviewNetworkStats data={mockData.OverviewNetwork} loading={false} />
|
||||
test('it does NOT show loading indicator when loading is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<OverviewNetworkStats data={mockData.OverviewNetwork} loading={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const loadingWrapper = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="network-stat-auditbeatSocket"]')
|
||||
// click the accordion to expand it
|
||||
wrapper
|
||||
.find('button')
|
||||
.first()
|
||||
.childAt(0);
|
||||
expect(loadingWrapper.prop('isLoading')).toBe(false);
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="network-stat-auditbeatSocket"]')
|
||||
.first()
|
||||
.find('[data-test-subj="stat-value-loading-spinner"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
test('it does show loading indicator when not loading', () => {
|
||||
const wrapper = shallow(
|
||||
<OverviewNetworkStats data={mockData.OverviewNetwork} loading={true} />
|
||||
|
||||
test('it shows the loading indicator when loading is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<OverviewNetworkStats data={mockData.OverviewNetwork} loading={true} />
|
||||
</TestProviders>
|
||||
);
|
||||
const loadingWrapper = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="network-stat-auditbeatSocket"]')
|
||||
|
||||
// click the accordion to expand it
|
||||
wrapper
|
||||
.find('button')
|
||||
.first()
|
||||
.childAt(0);
|
||||
expect(loadingWrapper.prop('isLoading')).toBe(true);
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="network-stat-auditbeatSocket"]')
|
||||
.first()
|
||||
.find('[data-test-subj="stat-value-loading-spinner"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,168 +4,191 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListDescription,
|
||||
EuiDescriptionListTitle,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { has } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OverviewNetworkData } from '../../../../graphql/types';
|
||||
import { getEmptyTagValue } from '../../../empty_value';
|
||||
import { FormattedStat, StatGroup } from '../types';
|
||||
import { StatValue } from '../stat_value';
|
||||
|
||||
interface OverviewNetworkProps {
|
||||
data: OverviewNetworkData;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const overviewNetworkStats = (data: OverviewNetworkData) => [
|
||||
export const getOverviewNetworkStats = (data: OverviewNetworkData): FormattedStat[] => [
|
||||
{
|
||||
description:
|
||||
has('auditbeatSocket', data) && data.auditbeatSocket !== null
|
||||
? numeral(data.auditbeatSocket).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.auditbeatSocket ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.auditBeatSocketTitle"
|
||||
defaultMessage="Auditbeat Socket"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.auditBeatSocketTitle" defaultMessage="Socket" />
|
||||
),
|
||||
id: 'auditbeatSocket',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('filebeatCisco', data) && data.filebeatCisco !== null
|
||||
? numeral(data.filebeatCisco).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.filebeatCiscoTitle"
|
||||
defaultMessage="Filebeat Cisco"
|
||||
/>
|
||||
),
|
||||
count: data.filebeatCisco ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.filebeatCiscoTitle" defaultMessage="Cisco" />,
|
||||
id: 'filebeatCisco',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('filebeatNetflow', data) && data.filebeatNetflow !== null
|
||||
? numeral(data.filebeatNetflow).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.filebeatNetflow ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.filebeatNetflowTitle"
|
||||
defaultMessage="Filebeat Netflow"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.filebeatNetflowTitle" defaultMessage="Netflow" />
|
||||
),
|
||||
id: 'filebeatNetflow',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('filebeatPanw', data) && data.filebeatPanw !== null
|
||||
? numeral(data.filebeatPanw).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.filebeatPanw ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.filebeatPanwTitle"
|
||||
defaultMessage="Filebeat Palo Alto Networks"
|
||||
defaultMessage="Palo Alto Networks"
|
||||
/>
|
||||
),
|
||||
id: 'filebeatPanw',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('filebeatSuricata', data) && data.filebeatSuricata !== null
|
||||
? numeral(data.filebeatSuricata).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
count: data.filebeatSuricata ?? 0,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.fileBeatSuricataTitle"
|
||||
defaultMessage="Filebeat Suricata"
|
||||
/>
|
||||
<FormattedMessage id="xpack.siem.overview.fileBeatSuricataTitle" defaultMessage="Suricata" />
|
||||
),
|
||||
id: 'filebeatSuricata',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('filebeatZeek', data) && data.filebeatZeek !== null
|
||||
? numeral(data.filebeatZeek).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage id="xpack.siem.overview.fileBeatZeekTitle" defaultMessage="Filebeat Zeek" />
|
||||
),
|
||||
count: data.filebeatZeek ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.fileBeatZeekTitle" defaultMessage="Zeek" />,
|
||||
id: 'filebeatZeek',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('packetbeatDNS', data) && data.packetbeatDNS !== null
|
||||
? numeral(data.packetbeatDNS).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.packetBeatDnsTitle"
|
||||
defaultMessage="Packetbeat DNS"
|
||||
/>
|
||||
),
|
||||
count: data.packetbeatDNS ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.packetBeatDnsTitle" defaultMessage="DNS" />,
|
||||
id: 'packetbeatDNS',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('packetbeatFlow', data) && data.packetbeatFlow !== null
|
||||
? numeral(data.packetbeatFlow).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.packetBeatFlowTitle"
|
||||
defaultMessage="Packetbeat Flow"
|
||||
/>
|
||||
),
|
||||
count: data.packetbeatFlow ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.packetBeatFlowTitle" defaultMessage="Flow" />,
|
||||
id: 'packetbeatFlow',
|
||||
},
|
||||
{
|
||||
description:
|
||||
has('packetbeatTLS', data) && data.packetbeatTLS !== null
|
||||
? numeral(data.packetbeatTLS).format('0,0')
|
||||
: getEmptyTagValue(),
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.packetbeatTLSTitle"
|
||||
defaultMessage="Packetbeat TLS"
|
||||
/>
|
||||
),
|
||||
count: data.packetbeatTLS ?? 0,
|
||||
title: <FormattedMessage id="xpack.siem.overview.packetbeatTLSTitle" defaultMessage="TLS" />,
|
||||
id: 'packetbeatTLS',
|
||||
},
|
||||
];
|
||||
|
||||
export const DescriptionListDescription = styled(EuiDescriptionListDescription)`
|
||||
text-align: right;
|
||||
const networkStatGroups: StatGroup[] = [
|
||||
{
|
||||
groupId: 'auditbeat',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkStatGroupAuditbeat"
|
||||
defaultMessage="Auditbeat"
|
||||
/>
|
||||
),
|
||||
statIds: ['auditbeatSocket'],
|
||||
},
|
||||
{
|
||||
groupId: 'filebeat',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkStatGroupFilebeat"
|
||||
defaultMessage="Filebeat"
|
||||
/>
|
||||
),
|
||||
statIds: [
|
||||
'filebeatCisco',
|
||||
'filebeatNetflow',
|
||||
'filebeatPanw',
|
||||
'filebeatSuricata',
|
||||
'filebeatZeek',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupId: 'packetbeat',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkStatGroupPacketbeat"
|
||||
defaultMessage="Packetbeat"
|
||||
/>
|
||||
),
|
||||
statIds: ['packetbeatDNS', 'packetbeatFlow', 'packetbeatTLS'],
|
||||
},
|
||||
];
|
||||
|
||||
const NetworkStatsContainer = styled.div`
|
||||
.accordion-button {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
DescriptionListDescription.displayName = 'DescriptionListDescription';
|
||||
const Title = styled.div`
|
||||
margin-left: 24px;
|
||||
`;
|
||||
|
||||
const StatValue = React.memo<{ isLoading: boolean; value: React.ReactNode | null | undefined }>(
|
||||
({ isLoading, value }) => (
|
||||
<>{isLoading ? <EuiLoadingSpinner size="m" /> : value != null ? value : getEmptyTagValue()}</>
|
||||
)
|
||||
);
|
||||
export const OverviewNetworkStats = React.memo<OverviewNetworkProps>(({ data, loading }) => {
|
||||
const allNetworkStats = getOverviewNetworkStats(data);
|
||||
const allNetworkStatsCount = allNetworkStats.reduce((total, stat) => total + stat.count, 0);
|
||||
|
||||
StatValue.displayName = 'StatValue';
|
||||
return (
|
||||
<NetworkStatsContainer data-test-subj="overview-network-stats">
|
||||
{networkStatGroups.map((statGroup, i) => {
|
||||
const statsForGroup = allNetworkStats.filter(s => statGroup.statIds.includes(s.id));
|
||||
const statsForGroupCount = statsForGroup.reduce((total, stat) => total + stat.count, 0);
|
||||
|
||||
export const OverviewNetworkStats = React.memo<OverviewNetworkProps>(({ data, loading }) => (
|
||||
<EuiDescriptionList type="column">
|
||||
{overviewNetworkStats(data).map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<EuiDescriptionListTitle>{item.title}</EuiDescriptionListTitle>
|
||||
<DescriptionListDescription data-test-subj={`network-stat-${item.id}`}>
|
||||
<StatValue isLoading={loading} value={item.description} />
|
||||
</DescriptionListDescription>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</EuiDescriptionList>
|
||||
));
|
||||
const accordionButton = useMemo(
|
||||
() => (
|
||||
<EuiFlexGroup
|
||||
data-test-subj={`network-stat-group-${statGroup.groupId}`}
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>{statGroup.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatValue
|
||||
count={statsForGroupCount}
|
||||
isGroupStat={true}
|
||||
isLoading={loading}
|
||||
max={allNetworkStatsCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[statGroup, statsForGroupCount, loading, allNetworkStatsCount]
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment key={statGroup.groupId}>
|
||||
<EuiAccordion
|
||||
id={`network-stat-accordion-group${statGroup.groupId}`}
|
||||
buttonContent={accordionButton}
|
||||
buttonContentClassName="accordion-button"
|
||||
>
|
||||
{statsForGroup.map(stat => (
|
||||
<EuiFlexGroup key={stat.id} justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s">
|
||||
<Title>{stat.title}</Title>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={`network-stat-${stat.id}`} grow={false}>
|
||||
<StatValue
|
||||
count={stat.count}
|
||||
isGroupStat={false}
|
||||
isLoading={loading}
|
||||
max={statsForGroupCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
</EuiAccordion>
|
||||
{i !== networkStatGroups.length - 1 && <EuiHorizontalRule margin="xs" />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</NetworkStatsContainer>
|
||||
);
|
||||
});
|
||||
|
||||
OverviewNetworkStats.displayName = 'OverviewNetworkStats';
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiProgress, EuiText } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
|
||||
import { useUiSetting$ } from '../../../lib/kibana';
|
||||
|
||||
const ProgressContainer = styled.div`
|
||||
width: 100px;
|
||||
`;
|
||||
|
||||
export const StatValue = React.memo<{
|
||||
count: number;
|
||||
isLoading: boolean;
|
||||
isGroupStat: boolean;
|
||||
max: number;
|
||||
}>(({ count, isGroupStat, isLoading, max }) => {
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<EuiLoadingSpinner data-test-subj="stat-value-loading-spinner" size="m" />
|
||||
) : (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color={isGroupStat ? 'default' : 'subdued'} size={isGroupStat ? 'm' : 's'}>
|
||||
{numeral(count).format(defaultNumberFormat)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ProgressContainer>
|
||||
<EuiProgress
|
||||
color={isGroupStat ? 'primary' : 'subdued'}
|
||||
max={max}
|
||||
size="m"
|
||||
value={count}
|
||||
/>
|
||||
</ProgressContainer>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
StatValue.displayName = 'StatValue';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export type OverviewStatId =
|
||||
| 'auditbeatAuditd'
|
||||
| 'auditbeatFIM'
|
||||
| 'auditbeatLogin'
|
||||
| 'auditbeatPackage'
|
||||
| 'auditbeatProcess'
|
||||
| 'auditbeatSocket'
|
||||
| 'auditbeatUser'
|
||||
| 'endgameDns'
|
||||
| 'endgameFile'
|
||||
| 'endgameImageLoad'
|
||||
| 'endgameNetwork'
|
||||
| 'endgameProcess'
|
||||
| 'endgameRegistry'
|
||||
| 'endgameSecurity'
|
||||
| 'filebeatCisco'
|
||||
| 'filebeatNetflow'
|
||||
| 'filebeatPanw'
|
||||
| 'filebeatSuricata'
|
||||
| 'filebeatSystemModule'
|
||||
| 'filebeatZeek'
|
||||
| 'packetbeatDNS'
|
||||
| 'packetbeatFlow'
|
||||
| 'packetbeatTLS'
|
||||
| 'winlogbeat';
|
||||
|
||||
export interface FormattedStat {
|
||||
count: number;
|
||||
id: OverviewStatId;
|
||||
title: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface StatGroup {
|
||||
name: string | React.ReactNode;
|
||||
groupId: string;
|
||||
statIds: OverviewStatId[];
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getPinnedEventCount, getNotesCount } from '../../open_timeline/helpers';
|
||||
import { OpenTimelineResult } from '../../open_timeline/types';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
const Icon = styled(EuiIcon)`
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const FlexGroup = styled(EuiFlexGroup)`
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
const IconWithCount = React.memo<{ count: number; icon: string; tooltip: string }>(
|
||||
({ count, icon, tooltip }) => (
|
||||
<EuiToolTip content={tooltip}>
|
||||
<FlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<Icon color="subdued" size="s" type={icon} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
{count}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</FlexGroup>
|
||||
</EuiToolTip>
|
||||
)
|
||||
);
|
||||
|
||||
IconWithCount.displayName = 'IconWithCount';
|
||||
|
||||
export const RecentTimelineCounts = React.memo<{
|
||||
timeline: OpenTimelineResult;
|
||||
}>(({ timeline }) => {
|
||||
return (
|
||||
<>
|
||||
<IconWithCount
|
||||
count={getPinnedEventCount(timeline)}
|
||||
icon="pinFilled"
|
||||
tooltip={i18n.PINNED_EVENTS}
|
||||
/>
|
||||
<IconWithCount count={getNotesCount(timeline)} icon="editorComment" tooltip={i18n.NOTES} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RecentTimelineCounts.displayName = 'RecentTimelineCounts';
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButtonGroup, EuiButtonGroupOption } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { FilterMode } from '../types';
|
||||
|
||||
const toggleButtonIcons: EuiButtonGroupOption[] = [
|
||||
{
|
||||
id: 'favorites',
|
||||
label: 'Favorites',
|
||||
iconType: 'starFilled',
|
||||
},
|
||||
{
|
||||
id: `recently-updated`,
|
||||
label: 'Last updated',
|
||||
iconType: 'documentEdit',
|
||||
},
|
||||
];
|
||||
|
||||
export const Filters = React.memo<{
|
||||
filterBy: FilterMode;
|
||||
setFilterBy: (filterBy: FilterMode) => void;
|
||||
}>(({ filterBy, setFilterBy }) => (
|
||||
<EuiButtonGroup
|
||||
options={toggleButtonIcons}
|
||||
idSelected={filterBy}
|
||||
onChange={f => {
|
||||
setFilterBy(f as FilterMode);
|
||||
}}
|
||||
isIconOnly
|
||||
/>
|
||||
));
|
||||
|
||||
Filters.displayName = 'Filters';
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
EuiToolTip,
|
||||
EuiButtonIcon,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { isUntitled } from '../../open_timeline/helpers';
|
||||
import { OnOpenTimeline, OpenTimelineResult } from '../../open_timeline/types';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export interface MeApiResponse {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export const RecentTimelineHeader = React.memo<{
|
||||
onOpenTimeline: OnOpenTimeline;
|
||||
timeline: OpenTimelineResult;
|
||||
}>(({ onOpenTimeline, timeline }) => {
|
||||
const { title, savedObjectId } = timeline;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<EuiLink
|
||||
onClick={() => onOpenTimeline({ duplicate: false, timelineId: `${savedObjectId}` })}
|
||||
>
|
||||
{isUntitled(timeline) ? i18n.UNTITLED_TIMELINE : title}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={i18n.OPEN_AS_DUPLICATE}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.OPEN_AS_DUPLICATE}
|
||||
data-test-subj="open-duplicate"
|
||||
isDisabled={savedObjectId == null}
|
||||
iconSize="s"
|
||||
iconType="copy"
|
||||
onClick={() =>
|
||||
onOpenTimeline({
|
||||
duplicate: true,
|
||||
timelineId: `${savedObjectId}`,
|
||||
})
|
||||
}
|
||||
size="s"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
RecentTimelineHeader.displayName = 'RecentTimelineHeader';
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { throwIfNotOk } from '../../hooks/api/api';
|
||||
import { MeApiResponse } from './recent_timelines';
|
||||
|
||||
export const getMeApiUrl = (getBasePath: () => string): string =>
|
||||
`${getBasePath()}/internal/security/me`;
|
||||
|
||||
export const fetchUsername = async (meApiUrl: string) => {
|
||||
const response = await fetch(meApiUrl, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
await throwIfNotOk(response);
|
||||
const apiResponse: MeApiResponse = await response.json();
|
||||
|
||||
return apiResponse.username;
|
||||
};
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import ApolloClient from 'apollo-client';
|
||||
import { EuiHorizontalRule, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { AllTimelinesQuery } from '../../containers/timeline/all';
|
||||
import { SortFieldTimeline, Direction } from '../../graphql/types';
|
||||
import { fetchUsername, getMeApiUrl } from './helpers';
|
||||
import { queryTimelineById, dispatchUpdateTimeline } from '../open_timeline/helpers';
|
||||
import { DispatchUpdateTimeline, OnOpenTimeline } from '../open_timeline/types';
|
||||
import { RecentTimelines } from './recent_timelines';
|
||||
import { updateIsLoading as dispatchUpdateIsLoading } from '../../store/timeline/actions';
|
||||
import { FilterMode } from './types';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
export interface MeApiResponse {
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
apolloClient: ApolloClient<{}>;
|
||||
filterBy: FilterMode;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => void;
|
||||
updateTimeline: DispatchUpdateTimeline;
|
||||
}
|
||||
|
||||
export type Props = OwnProps & DispatchProps;
|
||||
|
||||
const StatefulRecentTimelinesComponent = React.memo<Props>(
|
||||
({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => {
|
||||
const actionDispatcher = updateIsLoading as ActionCreator<{ id: string; isLoading: boolean }>;
|
||||
const [username, setUsername] = useState<string | null | undefined>(undefined);
|
||||
const LoadingSpinner = useMemo(() => <EuiLoadingSpinner size="m" />, []);
|
||||
const onOpenTimeline: OnOpenTimeline = useCallback(
|
||||
({ duplicate, timelineId }: { duplicate: boolean; timelineId: string }) => {
|
||||
queryTimelineById({
|
||||
apolloClient,
|
||||
duplicate,
|
||||
timelineId,
|
||||
updateIsLoading: actionDispatcher,
|
||||
updateTimeline,
|
||||
});
|
||||
},
|
||||
[apolloClient, updateIsLoading, updateTimeline]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const loggedInUser = await fetchUsername(getMeApiUrl(chrome.getBasePath));
|
||||
|
||||
if (!canceled) {
|
||||
setUsername(loggedInUser);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!canceled) {
|
||||
setUsername(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (username === undefined) {
|
||||
return LoadingSpinner;
|
||||
} else if (username == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: why does `createdBy: <username>` specified as a `search` query does not match results?
|
||||
|
||||
const noTimelinesMessage =
|
||||
filterBy === 'favorites' ? i18n.NO_FAVORITE_TIMELINES : i18n.NO_TIMELINES;
|
||||
|
||||
return (
|
||||
<AllTimelinesQuery
|
||||
pageInfo={{
|
||||
pageIndex: 1,
|
||||
pageSize: 5,
|
||||
}}
|
||||
search={''}
|
||||
sort={{
|
||||
sortField: SortFieldTimeline.updated,
|
||||
sortOrder: Direction.desc,
|
||||
}}
|
||||
onlyUserFavorite={filterBy === 'favorites'}
|
||||
>
|
||||
{({ timelines, loading }) => (
|
||||
<>
|
||||
{loading ? (
|
||||
<>{LoadingSpinner}</>
|
||||
) : (
|
||||
<RecentTimelines
|
||||
noTimelinesMessage={noTimelinesMessage}
|
||||
onOpenTimeline={onOpenTimeline}
|
||||
timelines={timelines}
|
||||
/>
|
||||
)}
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<EuiText size="xs">
|
||||
<EuiLink href="#/link-to/timelines">{i18n.VIEW_ALL_TIMELINES}</EuiLink>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</AllTimelinesQuery>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
StatefulRecentTimelinesComponent.displayName = 'StatefulRecentTimelinesComponent';
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
updateIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) =>
|
||||
dispatch(dispatchUpdateIsLoading({ id, isLoading })),
|
||||
updateTimeline: dispatchUpdateTimeline(dispatch),
|
||||
});
|
||||
|
||||
export const StatefulRecentTimelines = connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(StatefulRecentTimelinesComponent);
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { RecentTimelineHeader } from './header';
|
||||
import { OnOpenTimeline, OpenTimelineResult } from '../open_timeline/types';
|
||||
|
||||
import { RecentTimelineCounts } from './counts';
|
||||
|
||||
export interface MeApiResponse {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export const RecentTimelines = React.memo<{
|
||||
noTimelinesMessage: string;
|
||||
onOpenTimeline: OnOpenTimeline;
|
||||
timelines: OpenTimelineResult[];
|
||||
}>(({ noTimelinesMessage, onOpenTimeline, timelines }) => {
|
||||
if (timelines.length === 0) {
|
||||
return (
|
||||
<>
|
||||
<EuiText color="subdued" size="s">
|
||||
{noTimelinesMessage}
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{timelines.map((t, i) => (
|
||||
<div key={`${t.savedObjectId}-${i}`}>
|
||||
<RecentTimelineHeader onOpenTimeline={onOpenTimeline} timeline={t} />
|
||||
<RecentTimelineCounts timeline={t} />
|
||||
{t.description && t.description.length && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="xs">
|
||||
{t.description}
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
{i !== timelines.length - 1 && <EuiSpacer size="l" />}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RecentTimelines.displayName = 'RecentTimelines';
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_RETRIEVING_USER_DETAILS = i18n.translate(
|
||||
'xpack.siem.recentTimelines.errorRetrievingUserDetailsMessage',
|
||||
{
|
||||
defaultMessage: 'Recent Timelines: An error occurred while retrieving user details',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_FAVORITE_TIMELINES = i18n.translate(
|
||||
'xpack.siem.recentTimelines.noFavoriteTimelinesMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
"You haven't favorited any timelines yet. Get out there and start threat hunting!",
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_TIMELINES = i18n.translate('xpack.siem.recentTimelines.noTimelinesMessage', {
|
||||
defaultMessage: "You haven't created any timelines yet. Get out there and start threat hunting!",
|
||||
});
|
||||
|
||||
export const NOTES = i18n.translate('xpack.siem.recentTimelines.notesTooltip', {
|
||||
defaultMessage: 'Notes',
|
||||
});
|
||||
|
||||
export const OPEN_AS_DUPLICATE = i18n.translate(
|
||||
'xpack.siem.recentTimelines.openAsDuplicateTooltip',
|
||||
{
|
||||
defaultMessage: 'Open as a duplicate timeline',
|
||||
}
|
||||
);
|
||||
|
||||
export const PINNED_EVENTS = i18n.translate('xpack.siem.recentTimelines.pinnedEventsTooltip', {
|
||||
defaultMessage: 'Pinned events',
|
||||
});
|
||||
|
||||
export const UNTITLED_TIMELINE = i18n.translate(
|
||||
'xpack.siem.recentTimelines.untitledTimelineLabel',
|
||||
{
|
||||
defaultMessage: 'Untitled timeline',
|
||||
}
|
||||
);
|
||||
|
||||
export const VIEW_ALL_TIMELINES = i18n.translate(
|
||||
'xpack.siem.recentTimelines.viewAllTimelinesLink',
|
||||
{
|
||||
defaultMessage: 'View all timelines',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export type FilterMode = 'favorites' | 'recently-updated';
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
export const SidebarHeader = React.memo<{ children?: React.ReactNode; title: string }>(
|
||||
({ children, title }) => (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>{title}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>{children}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
SidebarHeader.displayName = 'SidebarHeader';
|
|
@ -145,7 +145,7 @@ describe('UrlStateContainer', () => {
|
|||
).toEqual({
|
||||
hash: '',
|
||||
pathname: examplePath,
|
||||
search: [CONSTANTS.overviewPage, CONSTANTS.timelinePage].includes(page)
|
||||
search: [CONSTANTS.timelinePage].includes(page)
|
||||
? '?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))'
|
||||
: `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
|
||||
state: '',
|
||||
|
|
|
@ -39,7 +39,13 @@ export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
|||
CONSTANTS.timerange,
|
||||
CONSTANTS.timeline,
|
||||
],
|
||||
overview: [CONSTANTS.timeline, CONSTANTS.timerange],
|
||||
overview: [
|
||||
CONSTANTS.appQuery,
|
||||
CONSTANTS.filters,
|
||||
CONSTANTS.savedQuery,
|
||||
CONSTANTS.timerange,
|
||||
CONSTANTS.timeline,
|
||||
],
|
||||
timeline: [CONSTANTS.timeline, CONSTANTS.timerange],
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Position } from '@elastic/charts';
|
||||
import React from 'react';
|
||||
import { compose } from 'redux';
|
||||
|
||||
|
@ -16,6 +17,7 @@ import { MatrixHistogram } from '../../components/matrix_histogram';
|
|||
import {
|
||||
MatrixHistogramOption,
|
||||
MatrixHistogramMappingTypes,
|
||||
GetTitle,
|
||||
GetSubTitle,
|
||||
} from '../../components/matrix_histogram/types';
|
||||
import { UpdateDateRange } from '../../components/charts/common';
|
||||
|
@ -30,15 +32,17 @@ export interface OwnProps extends QueryTemplateProps {
|
|||
deleteQuery?: ({ id }: { id: string }) => void;
|
||||
isEventsType?: boolean;
|
||||
errorMessage: string;
|
||||
headerChildren?: React.ReactNode;
|
||||
hideHistogramIfEmpty?: boolean;
|
||||
id: string;
|
||||
legendPosition?: Position;
|
||||
mapping?: MatrixHistogramMappingTypes;
|
||||
query: Maybe<string>;
|
||||
setQuery: SetQuery;
|
||||
sourceId: string;
|
||||
stackByOptions: MatrixHistogramOption[];
|
||||
subtitle?: string | GetSubTitle;
|
||||
title: string;
|
||||
title: string | GetTitle;
|
||||
type: hostsModel.HostsType | networkModel.NetworkType;
|
||||
updateDateRange: UpdateDateRange;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const EMPTY_TITLE = i18n.translate('xpack.siem.pages.common.emptyTitle', {
|
||||
defaultMessage: 'Welcome to SIEM. Let’s get you started.',
|
||||
});
|
||||
|
||||
export const EMPTY_MESSAGE = i18n.translate('xpack.siem.pages.common.emptyMessage', {
|
||||
defaultMessage:
|
||||
'To begin using security information and event management, you’ll need to begin adding SIEM-related data to Kibana by installing and configuring our data shippers, called Beats. Let’s do that now!',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_PRIMARY = i18n.translate('xpack.siem.pages.common.emptyActionPrimary', {
|
||||
defaultMessage: 'Add data with Beats',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_SECONDARY = i18n.translate(
|
||||
'xpack.siem.pages.common.emptyActionSecondary',
|
||||
{
|
||||
defaultMessage: 'View getting started guide',
|
||||
}
|
||||
);
|
|
@ -9,7 +9,7 @@ import chrome from 'ui/chrome';
|
|||
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import * as i18n from './translations';
|
||||
import * as i18n from '../common/translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
|
@ -21,8 +21,9 @@ export const DetectionEngineEmptyPage = React.memo(() => (
|
|||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={useKibana().services.docLinks.links.siem}
|
||||
actionSecondaryUrl={useKibana().services.docLinks.links.siem.gettingStarted}
|
||||
data-test-subj="empty-page"
|
||||
message={i18n.EMPTY_MESSAGE}
|
||||
title={i18n.EMPTY_TITLE}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -5,16 +5,18 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { documentationLinks } from 'ui/documentation_links';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
export const DetectionEngineNoIndex = React.memo(() => (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="documents"
|
||||
actionPrimaryLabel={i18n.GO_TO_DOCUMENTATION}
|
||||
actionPrimaryUrl={documentationLinks.siem}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionPrimaryTarget="_blank"
|
||||
message={i18n.NO_INDEX_MSG_BODY}
|
||||
data-test-subj="no_index"
|
||||
|
|
|
@ -5,16 +5,18 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { documentationLinks } from 'ui/documentation_links';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
export const DetectionEngineUserUnauthenticated = React.memo(() => (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="documents"
|
||||
actionPrimaryLabel={i18n.GO_TO_DOCUMENTATION}
|
||||
actionPrimaryUrl={documentationLinks.siem}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionPrimaryTarget="_blank"
|
||||
message={i18n.USER_UNAUTHENTICATED_MSG_BODY}
|
||||
data-test-subj="no_index"
|
||||
|
|
|
@ -10,7 +10,7 @@ import chrome from 'ui/chrome';
|
|||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import * as i18n from '../common/translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
|
@ -25,8 +25,9 @@ export const HostsEmptyPage = React.memo(() => {
|
|||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={docLinks.links.siem}
|
||||
actionSecondaryUrl={docLinks.links.siem.gettingStarted}
|
||||
data-test-subj="empty-page"
|
||||
message={i18n.EMPTY_MESSAGE}
|
||||
title={i18n.EMPTY_TITLE}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -18,11 +18,15 @@ import * as i18n from '../translations';
|
|||
const HOSTS_PAGE_TIMELINE_ID = 'hosts-page';
|
||||
const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
|
||||
|
||||
const eventsStackByOptions: MatrixHistogramOption[] = [
|
||||
export const eventsStackByOptions: MatrixHistogramOption[] = [
|
||||
{
|
||||
text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION,
|
||||
value: 'event.action',
|
||||
},
|
||||
{
|
||||
text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET,
|
||||
value: 'event.dataset',
|
||||
},
|
||||
];
|
||||
|
||||
export const EventsQueryTabBody = ({
|
||||
|
|
|
@ -60,23 +60,17 @@ export const NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET = i18n.translate(
|
||||
'xpack.siem.hosts.navigation.eventsStackByEventDataset',
|
||||
{
|
||||
defaultMessage: 'dataset',
|
||||
}
|
||||
);
|
||||
|
||||
export const NAVIGATION_ALERTS_TITLE = i18n.translate('xpack.siem.hosts.navigation.alertsTitle', {
|
||||
defaultMessage: 'Alerts',
|
||||
});
|
||||
|
||||
export const EMPTY_TITLE = i18n.translate('xpack.siem.hosts.emptyTitle', {
|
||||
defaultMessage:
|
||||
'It looks like you don’t have any indices relevant to hosts in the SIEM application',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_PRIMARY = i18n.translate('xpack.siem.hosts.emptyActionPrimary', {
|
||||
defaultMessage: 'View setup instructions',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_SECONDARY = i18n.translate('xpack.siem.hosts.emptyActionSecondary', {
|
||||
defaultMessage: 'Go to documentation',
|
||||
});
|
||||
|
||||
export const ERROR_FETCHING_AUTHENTICATIONS_DATA = i18n.translate(
|
||||
'xpack.siem.hosts.navigaton.matrixHistogram.errorFetchingAuthenticationsData',
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ import chrome from 'ui/chrome';
|
|||
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import * as i18n from './translations';
|
||||
import * as i18n from '../common/translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
|
@ -24,9 +24,10 @@ export const NetworkEmptyPage = React.memo(() => {
|
|||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={docLinks.links.siem}
|
||||
actionSecondaryUrl={docLinks.links.siem.gettingStarted}
|
||||
data-test-subj="empty-page"
|
||||
title={i18n.EMPTY_TITLE}
|
||||
message={i18n.EMPTY_MESSAGE}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -14,19 +14,6 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.network.pageTitle', {
|
|||
defaultMessage: 'Network',
|
||||
});
|
||||
|
||||
export const EMPTY_TITLE = i18n.translate('xpack.siem.network.emptyTitle', {
|
||||
defaultMessage:
|
||||
'It looks like you don’t have any indices relevant to network in the SIEM application',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_PRIMARY = i18n.translate('xpack.siem.network.emptyActionPrimary', {
|
||||
defaultMessage: 'View setup instructions',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_SECONDARY = i18n.translate('xpack.siem.network.emptyActionSecondary', {
|
||||
defaultMessage: 'Go to documentation',
|
||||
});
|
||||
|
||||
export const NAVIGATION_FLOWS_TITLE = i18n.translate('xpack.siem.network.navigation.flowsTitle', {
|
||||
defaultMessage: 'Flows',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
ERROR_FETCHING_ALERTS_DATA,
|
||||
SHOWING,
|
||||
UNIT,
|
||||
} from '../../../components/alerts_viewer/translations';
|
||||
import { alertsStackByOptions } from '../../../components/alerts_viewer';
|
||||
import { getTabsOnHostsUrl } from '../../../components/link_to/redirect_to_hosts';
|
||||
import { MatrixHistogramContainer } from '../../../containers/matrix_histogram';
|
||||
import { MatrixHistogramGqlQuery } from '../../../containers/matrix_histogram/index.gql_query';
|
||||
import { MatrixHistogramOption } from '../../../components/matrix_histogram/types';
|
||||
import { useKibana, useUiSetting$ } from '../../../lib/kibana';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { SetAbsoluteRangeDatePicker } from '../../network/types';
|
||||
import { esQuery } from '../../../../../../../../src/plugins/data/public';
|
||||
import { inputsModel } from '../../../store';
|
||||
import { HostsTableType, HostsType } from '../../../store/hosts/model';
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
const ID = 'alertsByCategoryOverview';
|
||||
|
||||
const NO_FILTERS: esFilters.Filter[] = [];
|
||||
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
|
||||
|
||||
interface Props {
|
||||
deleteQuery?: ({ id }: { id: string }) => void;
|
||||
filters?: esFilters.Filter[];
|
||||
from: number;
|
||||
indexPattern: IIndexPattern;
|
||||
query?: Query;
|
||||
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
|
||||
setQuery: (params: {
|
||||
id: string;
|
||||
inspect: inputsModel.InspectQuery | null;
|
||||
loading: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
}) => void;
|
||||
to: number;
|
||||
}
|
||||
|
||||
const ViewAlertsButton = styled(EuiButton)`
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
||||
export const AlertsByCategory = React.memo<Props>(
|
||||
({
|
||||
deleteQuery,
|
||||
filters = NO_FILTERS,
|
||||
from,
|
||||
indexPattern,
|
||||
query = DEFAULT_QUERY,
|
||||
setAbsoluteRangeDatePicker,
|
||||
setQuery,
|
||||
to,
|
||||
}) => {
|
||||
const kibana = useKibana();
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
const updateDateRangeCallback = useCallback(
|
||||
(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
|
||||
},
|
||||
[setAbsoluteRangeDatePicker]
|
||||
);
|
||||
const alertsCountViewAlertsButton = useMemo(
|
||||
() => (
|
||||
<ViewAlertsButton href={getTabsOnHostsUrl(HostsTableType.alerts)}>
|
||||
{i18n.VIEW_ALERTS}
|
||||
</ViewAlertsButton>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const getTitle = useCallback(
|
||||
(option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text),
|
||||
[]
|
||||
);
|
||||
const getSubtitle = useCallback(
|
||||
(totalCount: number) =>
|
||||
`${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`,
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<MatrixHistogramContainer
|
||||
dataKey="AlertsHistogram"
|
||||
deleteQuery={deleteQuery}
|
||||
defaultStackByOption={alertsStackByOptions[0]}
|
||||
endDate={to}
|
||||
errorMessage={ERROR_FETCHING_ALERTS_DATA}
|
||||
filterQuery={convertToBuildEsQuery({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
indexPattern,
|
||||
queries: [query],
|
||||
filters,
|
||||
})}
|
||||
headerChildren={alertsCountViewAlertsButton}
|
||||
id={ID}
|
||||
isAlertsHistogram={true}
|
||||
legendPosition={'right'}
|
||||
query={MatrixHistogramGqlQuery}
|
||||
setQuery={setQuery}
|
||||
sourceId="default"
|
||||
stackByOptions={alertsStackByOptions}
|
||||
startDate={from}
|
||||
title={getTitle}
|
||||
subtitle={getSubtitle}
|
||||
type={HostsType.page}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
AlertsByCategory.displayName = 'AlertsByCategory';
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OverviewHost } from '../../../components/page/overview/overview_host';
|
||||
import { OverviewNetwork } from '../../../components/page/overview/overview_network';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { esQuery } from '../../../../../../../../src/plugins/data/public';
|
||||
import { inputsModel } from '../../../store';
|
||||
|
||||
const HorizontalSpacer = styled(EuiFlexItem)`
|
||||
width: 24px;
|
||||
`;
|
||||
|
||||
const NO_FILTERS: esFilters.Filter[] = [];
|
||||
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
|
||||
|
||||
interface Props {
|
||||
filters?: esFilters.Filter[];
|
||||
from: number;
|
||||
indexPattern: IIndexPattern;
|
||||
query?: Query;
|
||||
setQuery: (params: {
|
||||
id: string;
|
||||
inspect: inputsModel.InspectQuery | null;
|
||||
loading: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
}) => void;
|
||||
to: number;
|
||||
}
|
||||
|
||||
export const EventCounts = React.memo<Props>(
|
||||
({ filters = NO_FILTERS, from, indexPattern, query = DEFAULT_QUERY, setQuery, to }) => {
|
||||
const kibana = useKibana();
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={true}>
|
||||
<OverviewHost
|
||||
endDate={to}
|
||||
filterQuery={convertToBuildEsQuery({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
indexPattern,
|
||||
queries: [query],
|
||||
filters,
|
||||
})}
|
||||
startDate={from}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<HorizontalSpacer grow={false} />
|
||||
|
||||
<EuiFlexItem grow={true}>
|
||||
<OverviewNetwork
|
||||
endDate={to}
|
||||
filterQuery={convertToBuildEsQuery({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
indexPattern,
|
||||
queries: [query],
|
||||
filters,
|
||||
})}
|
||||
startDate={from}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EventCounts.displayName = 'EventCounts';
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
ERROR_FETCHING_EVENTS_DATA,
|
||||
SHOWING,
|
||||
UNIT,
|
||||
} from '../../../components/events_viewer/translations';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { SetAbsoluteRangeDatePicker } from '../../network/types';
|
||||
import { getTabsOnHostsUrl } from '../../../components/link_to/redirect_to_hosts';
|
||||
import { MatrixHistogramContainer } from '../../../containers/matrix_histogram';
|
||||
import { MatrixHistogramGqlQuery } from '../../../containers/matrix_histogram/index.gql_query';
|
||||
import { MatrixHistogramOption } from '../../../components/matrix_histogram/types';
|
||||
import { eventsStackByOptions } from '../../hosts/navigation';
|
||||
import { useKibana, useUiSetting$ } from '../../../lib/kibana';
|
||||
import { esQuery } from '../../../../../../../../src/plugins/data/public';
|
||||
import { inputsModel } from '../../../store';
|
||||
import { HostsTableType, HostsType } from '../../../store/hosts/model';
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
const NO_FILTERS: esFilters.Filter[] = [];
|
||||
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
|
||||
|
||||
const ID = 'eventsByDatasetOverview';
|
||||
|
||||
interface Props {
|
||||
deleteQuery?: ({ id }: { id: string }) => void;
|
||||
filters?: esFilters.Filter[];
|
||||
from: number;
|
||||
indexPattern: IIndexPattern;
|
||||
query?: Query;
|
||||
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
|
||||
setQuery: (params: {
|
||||
id: string;
|
||||
inspect: inputsModel.InspectQuery | null;
|
||||
loading: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
}) => void;
|
||||
to: number;
|
||||
}
|
||||
|
||||
const ViewEventsButton = styled(EuiButton)`
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
||||
export const EventsByDataset = React.memo<Props>(
|
||||
({
|
||||
deleteQuery,
|
||||
filters = NO_FILTERS,
|
||||
from,
|
||||
indexPattern,
|
||||
query = DEFAULT_QUERY,
|
||||
setAbsoluteRangeDatePicker,
|
||||
setQuery,
|
||||
to,
|
||||
}) => {
|
||||
const kibana = useKibana();
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
const updateDateRangeCallback = useCallback(
|
||||
(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
|
||||
},
|
||||
[setAbsoluteRangeDatePicker]
|
||||
);
|
||||
const eventsCountViewEventsButton = useMemo(
|
||||
() => (
|
||||
<ViewEventsButton href={getTabsOnHostsUrl(HostsTableType.events)}>
|
||||
{i18n.VIEW_EVENTS}
|
||||
</ViewEventsButton>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const getTitle = useCallback(
|
||||
(option: MatrixHistogramOption) => i18n.EVENTS_COUNT_BY(option.text),
|
||||
[]
|
||||
);
|
||||
const getSubtitle = useCallback(
|
||||
(totalCount: number) =>
|
||||
`${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`,
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<MatrixHistogramContainer
|
||||
dataKey="EventsHistogram"
|
||||
deleteQuery={deleteQuery}
|
||||
defaultStackByOption={eventsStackByOptions[1]}
|
||||
endDate={to}
|
||||
errorMessage={ERROR_FETCHING_EVENTS_DATA}
|
||||
filterQuery={convertToBuildEsQuery({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
indexPattern,
|
||||
queries: [query],
|
||||
filters,
|
||||
})}
|
||||
headerChildren={eventsCountViewEventsButton}
|
||||
id={ID}
|
||||
isEventsType={true}
|
||||
legendPosition={'right'}
|
||||
query={MatrixHistogramGqlQuery}
|
||||
setQuery={setQuery}
|
||||
sourceId="default"
|
||||
stackByOptions={eventsStackByOptions}
|
||||
startDate={from}
|
||||
title={getTitle}
|
||||
subtitle={getSubtitle}
|
||||
type={HostsType.page}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EventsByDataset.displayName = 'EventsByDataset';
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { OverviewComponent } from './overview';
|
||||
import { StatefulOverview } from './overview';
|
||||
|
||||
export const Overview = memo(() => <OverviewComponent />);
|
||||
export const Overview = memo(() => <StatefulOverview />);
|
||||
|
||||
Overview.displayName = 'Overview';
|
||||
|
|
|
@ -10,10 +10,35 @@ import React from 'react';
|
|||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import '../../mock/match_media';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { mocksSource } from '../../containers/source/mock';
|
||||
import { Overview } from './index';
|
||||
|
||||
jest.mock('ui/chrome', () => ({
|
||||
getBasePath: () => {
|
||||
return '<basepath>';
|
||||
},
|
||||
getKibanaVersion: () => {
|
||||
return 'v8.0.0';
|
||||
},
|
||||
breadcrumbs: {
|
||||
set: jest.fn(),
|
||||
},
|
||||
getUiSettingsClient: () => ({
|
||||
get: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../components/search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
jest.mock('../../components/query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
|
||||
let localSource: Array<{
|
||||
request: {};
|
||||
result: {
|
||||
|
|
|
@ -4,76 +4,146 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { compose } from 'redux';
|
||||
import { Query, esFilters } from 'src/plugins/data/public';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { AlertsByCategory } from './alerts_by_category';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { OverviewHost } from '../../components/page/overview/overview_host';
|
||||
import { OverviewNetwork } from '../../components/page/overview/overview_network';
|
||||
import { SiemSearchBar } from '../../components/search_bar';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { GlobalTime } from '../../containers/global_time';
|
||||
import { WithSource, indicesExistOrDataTemporarilyUnavailable } from '../../containers/source';
|
||||
import { EventsByDataset } from './events_by_dataset';
|
||||
import { EventCounts } from './event_counts';
|
||||
import { SetAbsoluteRangeDatePicker } from '../network/types';
|
||||
import { OverviewEmpty } from './overview_empty';
|
||||
import { StatefulSidebar } from './sidebar';
|
||||
import { SignalsByCategory } from './signals_by_category';
|
||||
import { inputsSelectors, State } from '../../store';
|
||||
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
import { Summary } from './summary';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
|
||||
const NO_FILTERS: esFilters.Filter[] = [];
|
||||
const SidebarFlexItem = styled(EuiFlexItem)`
|
||||
margin-right: 24px;
|
||||
`;
|
||||
|
||||
export const OverviewComponent = React.memo(() => {
|
||||
const docLinks = useKibana().services.docLinks;
|
||||
const dateEnd = Date.now();
|
||||
const dateRange = moment.duration(24, 'hours').asMilliseconds();
|
||||
const dateStart = dateEnd - dateRange;
|
||||
interface OverviewComponentReduxProps {
|
||||
query?: Query;
|
||||
filters?: esFilters.Filter[];
|
||||
setAbsoluteRangeDatePicker?: SetAbsoluteRangeDatePicker;
|
||||
}
|
||||
|
||||
return (
|
||||
const OverviewComponent = React.memo<OverviewComponentReduxProps>(
|
||||
({ filters = NO_FILTERS, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker }) => (
|
||||
<>
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
badgeOptions={{
|
||||
beta: true,
|
||||
text: i18n.PAGE_BADGE_LABEL,
|
||||
tooltip: i18n.PAGE_BADGE_TOOLTIP,
|
||||
}}
|
||||
border
|
||||
subtitle={i18n.PAGE_SUBTITLE}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar id="global" indexPattern={indexPattern} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<GlobalTime>
|
||||
{({ setQuery }) => (
|
||||
<EuiFlexGroup>
|
||||
<Summary />
|
||||
<OverviewHost endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
<OverviewNetwork endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</GlobalTime>
|
||||
) : (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="gear"
|
||||
actionPrimaryLabel={i18n.EMPTY_ACTION_PRIMARY}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={docLinks.links.siem}
|
||||
data-test-subj="empty-page"
|
||||
title={i18n.EMPTY_TITLE}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
</WrapperPage>
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={i18n.PAGE_TITLE} />
|
||||
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<SidebarFlexItem grow={false}>
|
||||
<StatefulSidebar />
|
||||
</SidebarFlexItem>
|
||||
|
||||
<EuiFlexItem grow={true}>
|
||||
<GlobalTime>
|
||||
{({ from, deleteQuery, setQuery, to }) => (
|
||||
<>
|
||||
<EventsByDataset
|
||||
deleteQuery={deleteQuery}
|
||||
filters={filters}
|
||||
from={from}
|
||||
indexPattern={indexPattern}
|
||||
query={query}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker!}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EventCounts
|
||||
filters={filters}
|
||||
from={from}
|
||||
indexPattern={indexPattern}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<AlertsByCategory
|
||||
deleteQuery={deleteQuery}
|
||||
filters={filters}
|
||||
from={from}
|
||||
indexPattern={indexPattern}
|
||||
query={query}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker!}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<SignalsByCategory
|
||||
filters={filters}
|
||||
from={from}
|
||||
indexPattern={indexPattern}
|
||||
query={query}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker!}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<OverviewEmpty />
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
|
||||
OverviewComponent.displayName = 'OverviewComponent';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
|
||||
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
|
||||
|
||||
const mapStateToProps = (state: State): OverviewComponentReduxProps => ({
|
||||
query: getGlobalQuerySelector(state),
|
||||
filters: getGlobalFiltersQuerySelector(state),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export const StatefulOverview = compose<React.ComponentClass<OverviewComponentReduxProps>>(
|
||||
connect(makeMapStateToProps, { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker })
|
||||
)(OverviewComponent);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import * as i18nCommon from '../../common/translations';
|
||||
import { EmptyPage } from '../../../components/empty_page';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
export const OverviewEmpty = React.memo(() => {
|
||||
const docLinks = useKibana().services.docLinks;
|
||||
|
||||
return (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="gear"
|
||||
actionPrimaryLabel={i18nCommon.EMPTY_ACTION_PRIMARY}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18nCommon.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={docLinks.links.siem.gettingStarted}
|
||||
data-test-subj="empty-page"
|
||||
message={i18nCommon.EMPTY_MESSAGE}
|
||||
title={i18nCommon.EMPTY_TITLE}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
OverviewEmpty.displayName = 'OverviewEmpty';
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { FilterMode } from '../../../components/recent_timelines/types';
|
||||
import { Sidebar } from './sidebar';
|
||||
|
||||
export const StatefulSidebar = React.memo(() => {
|
||||
const [filterBy, setFilterBy] = useState<FilterMode>('favorites');
|
||||
|
||||
return <Sidebar filterBy={filterBy} setFilterBy={setFilterBy} />;
|
||||
});
|
||||
|
||||
StatefulSidebar.displayName = 'StatefulSidebar';
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Filters } from '../../../components/recent_timelines/filters';
|
||||
import { ENABLE_NEWS_FEED_SETTING, NEWS_FEED_URL_SETTING } from '../../../../common/constants';
|
||||
import { StatefulRecentTimelines } from '../../../components/recent_timelines';
|
||||
import { StatefulNewsFeed } from '../../../components/news_feed';
|
||||
import { FilterMode } from '../../../components/recent_timelines/types';
|
||||
import { SidebarHeader } from '../../../components/sidebar_header';
|
||||
import { useApolloClient } from '../../../utils/apollo_context';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
const SidebarFlexGroup = styled(EuiFlexGroup)`
|
||||
width: 305px;
|
||||
`;
|
||||
|
||||
export const Sidebar = React.memo<{
|
||||
filterBy: FilterMode;
|
||||
setFilterBy: (filterBy: FilterMode) => void;
|
||||
}>(({ filterBy, setFilterBy }) => {
|
||||
const apolloClient = useApolloClient();
|
||||
const RecentTimelinesFilters = useMemo(
|
||||
() => <Filters filterBy={filterBy} setFilterBy={setFilterBy} />,
|
||||
[filterBy, setFilterBy]
|
||||
);
|
||||
|
||||
return (
|
||||
<SidebarFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SidebarHeader title={i18n.RECENT_TIMELINES}>{RecentTimelinesFilters}</SidebarHeader>
|
||||
<StatefulRecentTimelines apolloClient={apolloClient!} filterBy={filterBy} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="xxl" />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatefulNewsFeed
|
||||
enableNewsFeedSetting={ENABLE_NEWS_FEED_SETTING}
|
||||
newsFeedSetting={NEWS_FEED_URL_SETTING}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</SidebarFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
Sidebar.displayName = 'Sidebar';
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
|
||||
|
||||
import { SignalsHistogramPanel } from '../../detection_engine/components/signals_histogram_panel';
|
||||
import { SetAbsoluteRangeDatePicker } from '../../network/types';
|
||||
import { inputsModel } from '../../../store';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
const NO_FILTERS: esFilters.Filter[] = [];
|
||||
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' };
|
||||
|
||||
interface Props {
|
||||
filters?: esFilters.Filter[];
|
||||
from: number;
|
||||
indexPattern: IIndexPattern;
|
||||
query?: Query;
|
||||
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
|
||||
setQuery: (params: {
|
||||
id: string;
|
||||
inspect: inputsModel.InspectQuery | null;
|
||||
loading: boolean;
|
||||
refetch: inputsModel.Refetch;
|
||||
}) => void;
|
||||
to: number;
|
||||
}
|
||||
|
||||
export const SignalsByCategory = React.memo<Props>(
|
||||
({ filters = NO_FILTERS, from, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, to }) => {
|
||||
const updateDateRangeCallback = useCallback(
|
||||
(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
|
||||
},
|
||||
[setAbsoluteRangeDatePicker]
|
||||
);
|
||||
|
||||
return (
|
||||
<SignalsHistogramPanel
|
||||
filters={filters}
|
||||
from={from}
|
||||
query={query}
|
||||
showTotalSignalsCount={true}
|
||||
showLinkToSignals={true}
|
||||
defaultStackByOption={{
|
||||
text: `${i18n.SIGNALS_BY_CATEGORY}`,
|
||||
value: 'signal.rule.threats',
|
||||
}}
|
||||
legendPosition={'right'}
|
||||
to={to}
|
||||
title={i18n.SIGNALS_BY_CATEGORY}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SignalsByCategory.displayName = 'SignalsByCategory';
|
|
@ -29,7 +29,7 @@ export const Summary = React.memo(() => {
|
|||
defaultMessage="Welcome to Security Information & Event Management (SIEM). Get started by reviewing our {docs} or {data}. For information about upcoming features and tutorials, be sure to check out our {siemSolution} page."
|
||||
values={{
|
||||
docs: (
|
||||
<EuiLink href={docLinks.links.siem} target="blank">
|
||||
<EuiLink href={docLinks.links.siem.guide} target="blank">
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.startedText.docsLinkText"
|
||||
defaultMessage="documentation"
|
||||
|
|
|
@ -6,6 +6,22 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ALERTS_COUNT_BY = (groupByField: string) =>
|
||||
i18n.translate('xpack.siem.overview.alertsCountByTitle', {
|
||||
values: { groupByField },
|
||||
defaultMessage: 'Alerts count by {groupByField}',
|
||||
});
|
||||
|
||||
export const EVENTS_COUNT_BY = (groupByField: string) =>
|
||||
i18n.translate('xpack.siem.overview.eventsCountByTitle', {
|
||||
values: { groupByField },
|
||||
defaultMessage: 'Events count by {groupByField}',
|
||||
});
|
||||
|
||||
export const NEWS_FEED_TITLE = i18n.translate('xpack.siem.overview.newsFeedSidebarTitle', {
|
||||
defaultMessage: 'Security news',
|
||||
});
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.overview.pageTitle', {
|
||||
defaultMessage: 'SIEM',
|
||||
});
|
||||
|
@ -23,14 +39,18 @@ export const PAGE_BADGE_TOOLTIP = i18n.translate('xpack.siem.overview.pageBadgeT
|
|||
'SIEM is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.',
|
||||
});
|
||||
|
||||
export const EMPTY_TITLE = i18n.translate('xpack.siem.overview.emptyTitle', {
|
||||
defaultMessage: 'It looks like you don’t have any indices relevant to the SIEM application',
|
||||
export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimelinesSidebarTitle', {
|
||||
defaultMessage: 'Recent timelines',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_PRIMARY = i18n.translate('xpack.siem.overview.emptyActionPrimary', {
|
||||
defaultMessage: 'View setup instructions',
|
||||
export const SIGNALS_BY_CATEGORY = i18n.translate('xpack.siem.overview.signalsByCategoryTitle', {
|
||||
defaultMessage: 'Signals count by MITRE ATT&CK\\u2122 category',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_SECONDARY = i18n.translate('xpack.siem.overview.emptyActionSecondary', {
|
||||
defaultMessage: 'Go to documentation',
|
||||
export const VIEW_ALERTS = i18n.translate('xpack.siem.overview.viewAlertsButtonLabel', {
|
||||
defaultMessage: 'View alerts',
|
||||
});
|
||||
|
||||
export const VIEW_EVENTS = i18n.translate('xpack.siem.overview.viewEventsButtonLabel', {
|
||||
defaultMessage: 'View events',
|
||||
});
|
||||
|
|
|
@ -11193,9 +11193,6 @@
|
|||
"xpack.siem.host.details.overview.platformTitle": "プラットフォーム",
|
||||
"xpack.siem.host.details.overview.regionTitle": "地域",
|
||||
"xpack.siem.host.details.versionLabel": "バージョン",
|
||||
"xpack.siem.hosts.emptyActionPrimary": "セットアップの手順を表示",
|
||||
"xpack.siem.hosts.emptyActionSecondary": "ドキュメントに移動",
|
||||
"xpack.siem.hosts.emptyTitle": "SIEM アプリケーションのホストに関連したインデックスがないようです",
|
||||
"xpack.siem.hosts.kqlPlaceholder": "例: host.name: \"foo\"",
|
||||
"xpack.siem.hosts.navigation.allHostsTitle": "すべてのホスト",
|
||||
"xpack.siem.hosts.navigation.anomaliesTitle": "異常",
|
||||
|
@ -11273,9 +11270,6 @@
|
|||
"xpack.siem.navigation.network": "ネットワーク",
|
||||
"xpack.siem.navigation.overview": "概要",
|
||||
"xpack.siem.navigation.timelines": "タイムライン",
|
||||
"xpack.siem.network.emptyActionPrimary": "セットアップの手順を表示",
|
||||
"xpack.siem.network.emptyActionSecondary": "ドキュメントに移動",
|
||||
"xpack.siem.network.emptyTitle": "SIEM アプリケーションのネットワークに関連したインデックスがないようです",
|
||||
"xpack.siem.network.ipDetails.ipOverview.asDestinationDropDownOptionLabel": "送信先として",
|
||||
"xpack.siem.network.ipDetails.ipOverview.asSourceDropDownOptionLabel": "送信元として",
|
||||
"xpack.siem.network.ipDetails.ipOverview.autonomousSystemTitle": "自動システム",
|
||||
|
@ -11387,9 +11381,6 @@
|
|||
"xpack.siem.overview.auditBeatProcessTitle": "Auditbeatプロセス",
|
||||
"xpack.siem.overview.auditBeatSocketTitle": "Auditbeatソケット",
|
||||
"xpack.siem.overview.auditBeatUserTitle": "Auditbeatユーザー",
|
||||
"xpack.siem.overview.emptyActionPrimary": "セットアップの手順を表示",
|
||||
"xpack.siem.overview.emptyActionSecondary": "ドキュメントに移動",
|
||||
"xpack.siem.overview.emptyTitle": "SIEM アプリケーションに関連したインデックスがないようです",
|
||||
"xpack.siem.overview.endgameDnsTitle": "Endgame DNS",
|
||||
"xpack.siem.overview.endgameFileTitle": "Endgame ファイル",
|
||||
"xpack.siem.overview.endgameImageLoadTitle": "Endgame 画像の読み込み",
|
||||
|
@ -11407,10 +11398,8 @@
|
|||
"xpack.siem.overview.filebeatSystemModuleTitle": "Filebeat システムモジュール",
|
||||
"xpack.siem.overview.fileBeatZeekTitle": "Filebeat Zeek",
|
||||
"xpack.siem.overview.hostsAction": "ホストを表示",
|
||||
"xpack.siem.overview.hostsSubtitle": "表示中:過去 24 時間",
|
||||
"xpack.siem.overview.hostsTitle": "ホストイベント",
|
||||
"xpack.siem.overview.networkAction": "ネットワークを表示",
|
||||
"xpack.siem.overview.networkSubtitle": "表示中:過去 24 時間",
|
||||
"xpack.siem.overview.networkTitle": "ネットワークイベント",
|
||||
"xpack.siem.overview.packetBeatDnsTitle": "Packetbeat DNS",
|
||||
"xpack.siem.overview.packetBeatFlowTitle": "Packetbeatフロー",
|
||||
|
|
|
@ -11192,9 +11192,6 @@
|
|||
"xpack.siem.host.details.overview.platformTitle": "平台",
|
||||
"xpack.siem.host.details.overview.regionTitle": "地区",
|
||||
"xpack.siem.host.details.versionLabel": "版本",
|
||||
"xpack.siem.hosts.emptyActionPrimary": "查看设置说明",
|
||||
"xpack.siem.hosts.emptyActionSecondary": "前往文档",
|
||||
"xpack.siem.hosts.emptyTitle": "似乎您在 SIEM 应用程序中没有与主机相关的索引",
|
||||
"xpack.siem.hosts.kqlPlaceholder": "例如 host.name:“foo”",
|
||||
"xpack.siem.hosts.navigation.allHostsTitle": "所有主机",
|
||||
"xpack.siem.hosts.navigation.anomaliesTitle": "异常",
|
||||
|
@ -11272,9 +11269,6 @@
|
|||
"xpack.siem.navigation.network": "网络",
|
||||
"xpack.siem.navigation.overview": "概览",
|
||||
"xpack.siem.navigation.timelines": "时间线",
|
||||
"xpack.siem.network.emptyActionPrimary": "查看设置说明",
|
||||
"xpack.siem.network.emptyActionSecondary": "前往文档",
|
||||
"xpack.siem.network.emptyTitle": "似乎您在 SIEM 应用程序中没有与网络相关的索引",
|
||||
"xpack.siem.network.ipDetails.ipOverview.asDestinationDropDownOptionLabel": "作为目标",
|
||||
"xpack.siem.network.ipDetails.ipOverview.asSourceDropDownOptionLabel": "作为源",
|
||||
"xpack.siem.network.ipDetails.ipOverview.autonomousSystemTitle": "自治系统",
|
||||
|
@ -11386,9 +11380,6 @@
|
|||
"xpack.siem.overview.auditBeatProcessTitle": "Auditbeat 进程",
|
||||
"xpack.siem.overview.auditBeatSocketTitle": "Auditbeat 套接字",
|
||||
"xpack.siem.overview.auditBeatUserTitle": "Auditbeat 用户",
|
||||
"xpack.siem.overview.emptyActionPrimary": "查看设置说明",
|
||||
"xpack.siem.overview.emptyActionSecondary": "前往文档",
|
||||
"xpack.siem.overview.emptyTitle": "似乎您没有与 SIEM 应用程序相关的索引",
|
||||
"xpack.siem.overview.endgameDnsTitle": "Endgame DNS",
|
||||
"xpack.siem.overview.endgameFileTitle": "Endgame 文件",
|
||||
"xpack.siem.overview.endgameImageLoadTitle": "Endgame 图像加载",
|
||||
|
@ -11406,10 +11397,8 @@
|
|||
"xpack.siem.overview.filebeatSystemModuleTitle": "Filebeat 系统模块",
|
||||
"xpack.siem.overview.fileBeatZeekTitle": "Filebeat Zeek",
|
||||
"xpack.siem.overview.hostsAction": "查看主机",
|
||||
"xpack.siem.overview.hostsSubtitle": "显示:过去 24 小时",
|
||||
"xpack.siem.overview.hostsTitle": "主机事件",
|
||||
"xpack.siem.overview.networkAction": "查看网络",
|
||||
"xpack.siem.overview.networkSubtitle": "显示:过去 24 小时",
|
||||
"xpack.siem.overview.networkTitle": "网络事件",
|
||||
"xpack.siem.overview.packetBeatDnsTitle": "Packetbeat DNS",
|
||||
"xpack.siem.overview.packetBeatFlowTitle": "Packetbeat 流",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue