From 56ff72186778fa694a8c10e10c41bfd80d9a34b6 Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Tue, 14 Jan 2020 21:03:57 -0700 Subject: [PATCH] [SIEM] New Overview Page (#54783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [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-day](https://user-images.githubusercontent.com/4459398/72396016-90f53600-36f8-11ea-9b41-6d54d09de589.png) ![overview-night](https://user-images.githubusercontent.com/4459398/72394575-fb57a780-36f3-11ea-868e-8fcd2c5c4543.png) ### 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 ![news-settings](https://user-images.githubusercontent.com/4459398/72362776-fd4c4700-36b0-11ea-805b-3c7353f2c1cd.png) - 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 ![ftue](https://user-images.githubusercontent.com/4459398/72361771-43a0a680-36af-11ea-969f-5872ac4a01a1.png) - 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 ![help](https://user-images.githubusercontent.com/4459398/72362132-d80b0900-36af-11ea-9b58-1fd3b923b7c8.png) - 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 --- ...ibana-plugin-public.doclinksstart.links.md | 5 +- .../kibana-plugin-public.doclinksstart.md | 2 +- .../core/public/kibana-plugin-public.md | 302 +++---- docs/management/advanced-options.asciidoc | 2 + .../public/doc_links/doc_links_service.ts | 10 +- src/core/public/public.api.md | 5 +- .../language_switcher.test.tsx.snap | 10 +- .../query_string_input.test.tsx.snap | 30 +- .../legacy/plugins/siem/common/constants.ts | 9 + .../integration/lib/overview/selectors.ts | 4 + .../smoke_tests/overview/overview.spec.ts | 16 +- x-pack/legacy/plugins/siem/index.ts | 26 + .../public/components/alerts_viewer/index.tsx | 10 +- .../components/alerts_viewer/translations.ts | 8 + .../__snapshots__/index.test.tsx.snap | 1 + .../public/components/empty_page/index.tsx | 1 + .../components/events_viewer/translations.ts | 7 + .../components/formatted_date/index.tsx | 53 +- .../__snapshots__/index.test.tsx.snap | 47 +- .../public/components/header_global/index.tsx | 26 +- .../public/components/help_menu/index.tsx | 9 + .../siem/public/components/link_to/index.ts | 2 +- .../public/components/link_to/link_to.tsx | 4 +- .../components/matrix_histogram/index.tsx | 32 +- .../components/matrix_histogram/types.ts | 6 +- .../components/matrix_histogram/utils.ts | 6 +- .../public/components/news_feed/helpers.ts | 89 ++ .../public/components/news_feed/index.tsx | 57 ++ .../public/components/news_feed/news_feed.tsx | 42 + .../components/news_feed/news_link/index.tsx | 24 + .../components/news_feed/no_news/index.tsx | 24 + .../components/news_feed/post/index.tsx | 62 ++ .../components/news_feed/translations.ts | 19 + .../siem/public/components/news_feed/types.ts | 42 + .../public/components/page/manage_query.tsx | 3 + .../page/overview/overview_host/index.tsx | 119 ++- .../__snapshots__/index.test.tsx.snap | 852 +++++++++++++----- .../overview_host_stats/index.test.tsx | 59 +- .../overview/overview_host_stats/index.tsx | 296 +++--- .../page/overview/overview_network/index.tsx | 120 ++- .../__snapshots__/index.test.tsx.snap | 534 ++++++++--- .../overview_network_stats/index.test.tsx | 59 +- .../overview/overview_network_stats/index.tsx | 227 ++--- .../components/page/overview/stat_value.tsx | 54 ++ .../public/components/page/overview/types.ts | 43 + .../recent_timelines/counts/index.tsx | 59 ++ .../recent_timelines/filters/index.tsx | 39 + .../recent_timelines/header/index.tsx | 66 ++ .../components/recent_timelines/helpers.ts | 26 + .../components/recent_timelines/index.tsx | 141 +++ .../recent_timelines/recent_timelines.tsx | 55 ++ .../recent_timelines/translations.ts | 55 ++ .../components/recent_timelines/types.ts | 7 + .../components/sidebar_header/index.tsx | 27 + .../components/url_state/index.test.tsx | 2 +- .../siem/public/components/url_state/types.ts | 8 +- .../containers/matrix_histogram/index.tsx | 6 +- .../siem/public/pages/common/translations.ts | 27 + .../detection_engine_empty_page.tsx | 5 +- .../detection_engine_no_signal_index.tsx | 6 +- .../detection_engine_user_unauthenticated.tsx | 6 +- .../public/pages/hosts/hosts_empty_page.tsx | 5 +- .../navigation/events_query_tab_body.tsx | 6 +- .../siem/public/pages/hosts/translations.ts | 20 +- .../pages/network/network_empty_page.tsx | 5 +- .../siem/public/pages/network/translations.ts | 13 - .../overview/alerts_by_category/index.tsx | 127 +++ .../pages/overview/event_counts/index.tsx | 80 ++ .../overview/events_by_dataset/index.tsx | 127 +++ .../siem/public/pages/overview/index.tsx | 4 +- .../public/pages/overview/overview.test.tsx | 25 + .../siem/public/pages/overview/overview.tsx | 184 ++-- .../pages/overview/overview_empty/index.tsx | 35 + .../public/pages/overview/sidebar/index.tsx | 18 + .../public/pages/overview/sidebar/sidebar.tsx | 56 ++ .../overview/signals_by_category/index.tsx | 63 ++ .../siem/public/pages/overview/summary.tsx | 2 +- .../public/pages/overview/translations.ts | 32 +- .../translations/translations/ja-JP.json | 11 - .../translations/translations/zh-CN.json | 11 - 80 files changed, 3627 insertions(+), 1090 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/helpers.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/news_link/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/news_feed/types.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/page/overview/types.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/helpers.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/recent_timelines.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/recent_timelines/types.ts create mode 100644 x-pack/legacy/plugins/siem/public/components/sidebar_header/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/common/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx diff --git a/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md index cbda9abead9d1..9e662c543eb56 100644 --- a/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md @@ -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; diff --git a/docs/development/core/public/kibana-plugin-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-public.doclinksstart.md index c43569e24c63e..cefac180d88c5 100644 --- a/docs/development/core/public/kibana-plugin-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-public.doclinksstart.links.md) | {
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: string;
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
} | | +| [links](./kibana-plugin-public.doclinksstart.links.md) | {
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
} | | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 64cbdd880fed1..27ca9f2d9fd57 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -1,151 +1,151 @@ - - -[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 Plugin setup lifecycle | -| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin 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 message and stack 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 ui/new_platform module. | -| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform 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 PluginInitializer. | -| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | -| [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 attributes 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 find() 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 confirm to to prompt a message to the user before leaving the page, or default 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 plugin export at the root of a plugin's public 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) | - + + +[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 Plugin setup lifecycle | +| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin 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 message and stack 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 ui/new_platform module. | +| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform 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 PluginInitializer. | +| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | +| [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 attributes 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 find() 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 confirm to to prompt a message to the user before leaving the page, or default 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 plugin export at the root of a plugin's public 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) | + diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 757c6f10f2a99..695a4d4f45b02 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -217,6 +217,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. diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 36b220f16f395..1046f7a17dc51 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -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; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index aef689162f45a..610b08708c681 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -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; diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap index 4ec29ca409b80..6432f8049641a 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap @@ -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", }, diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 4c8edd85eb559..1fb39710f6754 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -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", }, diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index e79c9db4a2ed0..e67b533e46bce 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -29,6 +29,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 */ diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/overview/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/overview/selectors.ts index cf48ba716830d..a8af321ff9832 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/overview/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/overview/selectors.ts @@ -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"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts index 4ef3eb67cafc9..be66fdc86be36 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts @@ -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 => { diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index 1907dbac707e3..edbb62feb580f 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -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, } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; @@ -118,6 +121,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: '

Enables the News feed

', + }), + 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: '

News feed content will be retrieved from this URL

', + }), + category: ['siem'], + requiresPageReload: true, + }, }, mappings: savedObjectMappings, }, diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx index 9e0207cf306ee..87d83f7f2972c 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx @@ -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 = ({ } + iconType="securityAnalyticsApp" title={

My Super Title diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx index f2b0ec1ab5e60..a067c1d28f87f 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx @@ -43,6 +43,7 @@ export const EmptyPage = React.memo( ...rest }) => ( {title}

} body={message &&

{message}

} actions={ diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts b/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts index 6e6be02a6085d..376ddf72ce481 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts @@ -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', }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx index 4e5903c02abf7..fb579a14b0457 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx @@ -10,17 +10,60 @@ 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(); +export const PreferenceFormattedDate = React.memo<{ dateFormat?: string; value: Date }>( + ({ value, dateFormat = useDateFormat() }) => ( + <>{moment.tz(value, useTimeZone()).format(dateFormat)} + ) +); + +PreferenceFormattedDate.displayName = 'PreferenceFormattedDate'; - return <>{moment.tz(value, timeZone).format(dateFormat)}; +/** + * 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$('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.displayName = 'PreferenceFormattedDate'; +PreferenceFormattedP1DTDate.displayName = 'PreferenceFormattedP1DTDate'; /** * Renders the specified date value in a format determined by the user's preferences, diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap index 849f3616524cc..aaefb4a83ded4 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap @@ -30,48 +30,11 @@ exports[`HeaderGlobal it renders 1`] = ` - + + + diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx index 53365a4daa34a..db6ff7cf55f92 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -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(({ hideDetectionEngine - key !== SiemPageName.detectionEngine, navTabs) - : navTabs + + {({ indicesExist }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + key !== SiemPageName.detectionEngine, navTabs) + : navTabs + } + /> + ) : ( + key === SiemPageName.overview, navTabs)} + /> + ) } - /> + diff --git a/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx b/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx index d42ee08e86407..732d83ac6e736 100644 --- a/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/help_menu/index.tsx @@ -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', diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/index.ts b/x-pack/legacy/plugins/siem/public/components/link_to/index.ts index 10198345755c3..ad6147e5aad76 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/link_to/index.ts @@ -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'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 5a7f6ef1274c9..b19ed8e44be9d 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -38,11 +38,11 @@ export const LinkToPage = React.memo(({ match }) => ( /> { const barchartConfigs = getBarchartConfigs({ from: startDate, + legendPosition, to: endDate, onBrushEnd: updateDateRange, scaleType, yTickFormatter, showLegend, - legendPosition, }); const [showInspect, setShowInspect] = useState(false); const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); @@ -90,6 +91,8 @@ export const MatrixHistogramComponent: React.FC( defaultStackByOption ); + + const [titleWithStackByField, setTitle] = useState(''); const [subtitleWithCounts, setSubtitle] = useState(''); const [hideHistogram, setHideHistogram] = useState(hideHistogramIfEmpty); const [barChartData, setBarChartData] = useState(null); @@ -130,6 +133,8 @@ export const MatrixHistogramComponent: React.FC { + 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 = 0 ? subtitleWithCounts : null)} > - {stackByOptions && ( - - )} + + + {stackByOptions && ( + + )} + + {headerChildren} + {loading ? ( diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts index 39a16b6c1b3d1..f10533460a776 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/types.ts @@ -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; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts index 5d79bbc0ceb23..c20b5ee30d919 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts @@ -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: { diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/helpers.ts b/x-pack/legacy/plugins/siem/public/components/news_feed/helpers.ts new file mode 100644 index 0000000000000..497127cdfba3d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/helpers.ts @@ -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(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 => { + 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); diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/index.tsx new file mode 100644 index 0000000000000..95f12758d5e63 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/index.tsx @@ -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$(enableNewsFeedSetting); + const [newsFeedUrlSetting] = useUiSetting$(newsFeedSetting); + const [news, setNews] = useState(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 ? : null}; +}); + +StatefulNewsFeed.displayName = 'StatefulNewsFeed'; diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx new file mode 100644 index 0000000000000..d41ce357d9b7b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx @@ -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(({ news }) => { + if (news == null) { + return ; + } + + if (news.length === 0) { + return ; + } + + return ( + <> + + {news.map((n: NewsItem) => ( + + + + + ))} + + ); +}); + +NewsFeed.displayName = 'NewsFeed'; diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/news_link/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/news_link/index.tsx new file mode 100644 index 0000000000000..b50ed578fb0bc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/news_link/index.tsx @@ -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 }) => ( + + {children} + +); diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx new file mode 100644 index 0000000000000..bd6648025d2aa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/no_news/index.tsx @@ -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(() => ( + <> + + {i18n.NO_NEWS_MESSAGE} + + {i18n.ADVANCED_SETTINGS_LINK_TITLE} + + {'.'} + + +)); + +NoNews.displayName = 'NoNews'; diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx new file mode 100644 index 0000000000000..cb2542a497f08 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx @@ -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 ( + + + + {title} + + + + + +
{description}
+
+
+ + + {imageUrl && ( + + + + )} + +
+ ); +}); + +Post.displayName = 'Post'; diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts b/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts new file mode 100644 index 0000000000000..71981723cc937 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/translations.ts @@ -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', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/types.ts b/x-pack/legacy/plugins/siem/public/components/news_feed/types.ts new file mode 100644 index 0000000000000..2ee5a4c3c02aa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/news_feed/types.ts @@ -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[]; +} diff --git a/x-pack/legacy/plugins/siem/public/components/page/manage_query.tsx b/x-pack/legacy/plugins/siem/public/components/page/manage_query.tsx index 3274bc485d6cd..138c38c02065b 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/manage_query.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/manage_query.tsx @@ -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; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx index a70d9d0080271..e069e64c66a92 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx @@ -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( + ({ endDate, filterQuery, startDate, setQuery }) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + + return ( + + + + + {({ overviewHost, loading, id, inspect, refetch }) => { + const hostEventsCount = getOverviewHostStats(overviewHost).reduce( + (total, stat) => total + stat.count, + 0 + ); + const formattedHostEventsCount = numeral(hostEventsCount).format( + defaultNumberFormat + ); -const OverviewHostComponent: React.FC = ({ endDate, startDate, setQuery }) => ( - - - - - } - title={ - - } - > - - - - + return ( + <> + + } + title={ + + } + > + + + + - - {({ overviewHost, loading, id, inspect, refetch }) => ( - - )} - - - - + + + ); + }} + + + + + ); + } ); -export const OverviewHost = React.memo(OverviewHostComponent); +OverviewHost.displayName = 'OverviewHost'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap index 142a9a03b7844..21a4568e24133 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap @@ -1,233 +1,637 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Overview Host Stat Data rendering it renders the default OverviewHostStats 1`] = ` - - - - - + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="host-stat-accordion-groupauditbeat" + initialIsOpen={false} + paddingSize="none" > - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="host-stat-accordion-groupendgame" + initialIsOpen={false} + paddingSize="none" > - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="host-stat-accordion-groupfilebeat" + initialIsOpen={false} + paddingSize="none" > - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="host-stat-accordion-groupwinlogbeat" + initialIsOpen={false} + paddingSize="none" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx index f99b2687d7072..4240ea441284c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx @@ -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(); - 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( + + + + ); + + // 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(); - const loadingWrapper = wrapper - .dive() - .find('[data-test-subj="host-stat-auditbeatAuditd"]') + test('it shows loading indicator when loading is true', () => { + const wrapper = mount( + + + + ); + + // 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); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx index aa2c6d61451bc..4134cc5ef4e4e 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx @@ -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: ( - - ), + count: data.auditbeatAuditd ?? 0, + title: , id: 'auditbeatAuditd', }, { - description: - has('auditbeatFIM', data) && data.auditbeatFIM !== null - ? numeral(data.auditbeatFIM).format('0,0') - : getEmptyTagValue(), + count: data.auditbeatFIM ?? 0, title: ( ), id: 'auditbeatFIM', }, { - description: - has('auditbeatLogin', data) && data.auditbeatLogin !== null - ? numeral(data.auditbeatLogin).format('0,0') - : getEmptyTagValue(), - title: ( - - ), + count: data.auditbeatLogin ?? 0, + title: , id: 'auditbeatLogin', }, { - description: - has('auditbeatPackage', data) && data.auditbeatPackage !== null - ? numeral(data.auditbeatPackage).format('0,0') - : getEmptyTagValue(), + count: data.auditbeatPackage ?? 0, title: ( - + ), id: 'auditbeatPackage', }, { - description: - has('auditbeatProcess', data) && data.auditbeatProcess !== null - ? numeral(data.auditbeatProcess).format('0,0') - : getEmptyTagValue(), + count: data.auditbeatProcess ?? 0, title: ( - + ), id: 'auditbeatProcess', }, { - description: - has('auditbeatUser', data) && data.auditbeatUser !== null - ? numeral(data.auditbeatUser).format('0,0') - : getEmptyTagValue(), - title: ( - - ), + count: data.auditbeatUser ?? 0, + title: , id: 'auditbeatUser', }, { - description: - has('endgameDns', data) && data.endgameDns !== null - ? numeral(data.endgameDns).format('0,0') - : getEmptyTagValue(), - title: ( - - ), + count: data.endgameDns ?? 0, + title: , id: 'endgameDns', }, { - description: - has('endgameFile', data) && data.endgameFile !== null - ? numeral(data.endgameFile).format('0,0') - : getEmptyTagValue(), - title: ( - - ), + count: data.endgameFile ?? 0, + title: , id: 'endgameFile', }, { - description: - has('endgameImageLoad', data) && data.endgameImageLoad !== null - ? numeral(data.endgameImageLoad).format('0,0') - : getEmptyTagValue(), + count: data.endgameImageLoad ?? 0, title: ( ), id: 'endgameImageLoad', }, { - description: - has('endgameNetwork', data) && data.endgameNetwork !== null - ? numeral(data.endgameNetwork).format('0,0') - : getEmptyTagValue(), + count: data.endgameNetwork ?? 0, title: ( - + ), id: 'endgameNetwork', }, { - description: - has('endgameProcess', data) && data.endgameProcess !== null - ? numeral(data.endgameProcess).format('0,0') - : getEmptyTagValue(), + count: data.endgameProcess ?? 0, title: ( - + ), id: 'endgameProcess', }, { - description: - has('endgameRegistry', data) && data.endgameRegistry !== null - ? numeral(data.endgameRegistry).format('0,0') - : getEmptyTagValue(), + count: data.endgameRegistry ?? 0, title: ( - + ), id: 'endgameRegistry', }, { - description: - has('endgameSecurity', data) && data.endgameSecurity !== null - ? numeral(data.endgameSecurity).format('0,0') - : getEmptyTagValue(), + count: data.endgameSecurity ?? 0, title: ( - + ), id: 'endgameSecurity', }, { - description: - has('filebeatSystemModule', data) && data.filebeatSystemModule !== null - ? numeral(data.filebeatSystemModule).format('0,0') - : getEmptyTagValue(), + count: data.filebeatSystemModule ?? 0, title: ( ), id: 'filebeatSystemModule', }, { - description: - has('winlogbeat', data) && data.winlogbeat !== null - ? numeral(data.winlogbeat).format('0,0') - : getEmptyTagValue(), + count: data.winlogbeat ?? 0, title: ( ), @@ -214,31 +125,128 @@ const overviewHostStats = (data: OverviewHostData) => [ }, ]; -export const DescriptionListDescription = styled(EuiDescriptionListDescription)` - text-align: right; +const HostStatsContainer = styled.div` + .accordion-button { + width: 100%; + } +`; + +const hostStatGroups: StatGroup[] = [ + { + groupId: 'auditbeat', + name: ( + + ), + statIds: [ + 'auditbeatAuditd', + 'auditbeatFIM', + 'auditbeatLogin', + 'auditbeatPackage', + 'auditbeatProcess', + 'auditbeatUser', + ], + }, + { + groupId: 'endgame', + name: ( + + ), + statIds: [ + 'endgameDns', + 'endgameFile', + 'endgameImageLoad', + 'endgameNetwork', + 'endgameProcess', + 'endgameRegistry', + 'endgameSecurity', + ], + }, + { + groupId: 'filebeat', + name: ( + + ), + statIds: ['filebeatSystemModule'], + }, + { + groupId: 'winlogbeat', + name: ( + + ), + statIds: ['winlogbeat'], + }, +]; + +const Title = styled.div` + margin-left: 24px; `; -DescriptionListDescription.displayName = 'DescriptionListDescription'; +export const OverviewHostStats = React.memo(({ data, loading }) => { + const allHostStats = getOverviewHostStats(data); + const allHostStatsCount = allHostStats.reduce((total, stat) => total + stat.count, 0); -const StatValue = React.memo<{ isLoading: boolean; value: React.ReactNode | null | undefined }>( - ({ isLoading, value }) => ( - <>{isLoading ? : value != null ? value : getEmptyTagValue()} - ) -); + return ( + + {hostStatGroups.map((statGroup, i) => { + const statsForGroup = allHostStats.filter(s => statGroup.statIds.includes(s.id)); + const statsForGroupCount = statsForGroup.reduce((total, stat) => total + stat.count, 0); -StatValue.displayName = 'StatValue'; + const accordionButton = useMemo( + () => ( + + + {statGroup.name} + + + + + + ), + [statGroup, statsForGroupCount, loading, allHostStatsCount] + ); -export const OverviewHostStats = React.memo(({ data, loading }) => ( - - {overviewHostStats(data).map((item, index) => ( - - {item.title} - - - - - ))} - -)); + return ( + + + {statsForGroup.map(stat => ( + + + + {stat.title} + + + + + + + ))} + + {i !== hostStatGroups.length - 1 && } + + ); + })} + + ); +}); OverviewHostStats.displayName = 'OverviewHostStats'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx index af8c87ff38596..36af58c4879a7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx @@ -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 = ({ endDate, startDate, setQuery }) => ( - - - - - } - title={ - - } - > - - - - +export const OverviewNetwork = React.memo( + ({ endDate, filterQuery, startDate, setQuery }) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - - {({ overviewNetwork, loading, id, inspect, refetch }) => ( - - )} - - - - + return ( + + + + + {({ overviewNetwork, loading, id, inspect, refetch }) => { + const networkEventsCount = getOverviewNetworkStats(overviewNetwork).reduce( + (total, stat) => total + stat.count, + 0 + ); + const formattedNetworkEventsCount = numeral(networkEventsCount).format( + defaultNumberFormat + ); + + return ( + <> + + } + title={ + + } + > + + + + + + + + ); + }} + + + + + ); + } ); -export const OverviewNetwork = React.memo(OverviewNetworkComponent); +OverviewNetwork.displayName = 'OverviewNetwork'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap index 9db61c474e220..4544c05f7b180 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/__snapshots__/index.test.tsx.snap @@ -1,143 +1,407 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Overview Network Stat Data rendering it renders the default OverviewNetworkStats 1`] = ` - - - - - + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="network-stat-accordion-groupauditbeat" + initialIsOpen={false} + paddingSize="none" > - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="network-stat-accordion-groupfilebeat" + initialIsOpen={false} + paddingSize="none" > - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + buttonContentClassName="accordion-button" + id="network-stat-accordion-grouppacketbeat" + initialIsOpen={false} + paddingSize="none" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx index 08093c5d38c15..cf1a7d20b73ec 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx @@ -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( - + test('it does NOT show loading indicator when loading is false', () => { + const wrapper = mount( + + + ); - 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( - + + test('it shows the loading indicator when loading is true', () => { + const wrapper = mount( + + + ); - 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); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx index 81d374bc0286b..123f7f21a75fd 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx @@ -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: ( - + ), id: 'auditbeatSocket', }, { - description: - has('filebeatCisco', data) && data.filebeatCisco !== null - ? numeral(data.filebeatCisco).format('0,0') - : getEmptyTagValue(), - title: ( - - ), + count: data.filebeatCisco ?? 0, + title: , id: 'filebeatCisco', }, { - description: - has('filebeatNetflow', data) && data.filebeatNetflow !== null - ? numeral(data.filebeatNetflow).format('0,0') - : getEmptyTagValue(), + count: data.filebeatNetflow ?? 0, title: ( - + ), id: 'filebeatNetflow', }, { - description: - has('filebeatPanw', data) && data.filebeatPanw !== null - ? numeral(data.filebeatPanw).format('0,0') - : getEmptyTagValue(), + count: data.filebeatPanw ?? 0, title: ( ), id: 'filebeatPanw', }, { - description: - has('filebeatSuricata', data) && data.filebeatSuricata !== null - ? numeral(data.filebeatSuricata).format('0,0') - : getEmptyTagValue(), + count: data.filebeatSuricata ?? 0, title: ( - + ), id: 'filebeatSuricata', }, { - description: - has('filebeatZeek', data) && data.filebeatZeek !== null - ? numeral(data.filebeatZeek).format('0,0') - : getEmptyTagValue(), - title: ( - - ), + count: data.filebeatZeek ?? 0, + title: , id: 'filebeatZeek', }, { - description: - has('packetbeatDNS', data) && data.packetbeatDNS !== null - ? numeral(data.packetbeatDNS).format('0,0') - : getEmptyTagValue(), - title: ( + count: data.packetbeatDNS ?? 0, + title: , + id: 'packetbeatDNS', + }, + { + count: data.packetbeatFlow ?? 0, + title: , + id: 'packetbeatFlow', + }, + { + count: data.packetbeatTLS ?? 0, + title: , + id: 'packetbeatTLS', + }, +]; + +const networkStatGroups: StatGroup[] = [ + { + groupId: 'auditbeat', + name: ( ), - id: 'packetbeatDNS', + statIds: ['auditbeatSocket'], }, { - description: - has('packetbeatFlow', data) && data.packetbeatFlow !== null - ? numeral(data.packetbeatFlow).format('0,0') - : getEmptyTagValue(), - title: ( + groupId: 'filebeat', + name: ( ), - id: 'packetbeatFlow', + statIds: [ + 'filebeatCisco', + 'filebeatNetflow', + 'filebeatPanw', + 'filebeatSuricata', + 'filebeatZeek', + ], }, { - description: - has('packetbeatTLS', data) && data.packetbeatTLS !== null - ? numeral(data.packetbeatTLS).format('0,0') - : getEmptyTagValue(), - title: ( + groupId: 'packetbeat', + name: ( ), - id: 'packetbeatTLS', + statIds: ['packetbeatDNS', 'packetbeatFlow', 'packetbeatTLS'], }, ]; -export const DescriptionListDescription = styled(EuiDescriptionListDescription)` - text-align: right; +const NetworkStatsContainer = styled.div` + .accordion-button { + width: 100%; + } +`; + +const Title = styled.div` + margin-left: 24px; `; -DescriptionListDescription.displayName = 'DescriptionListDescription'; +export const OverviewNetworkStats = React.memo(({ data, loading }) => { + const allNetworkStats = getOverviewNetworkStats(data); + const allNetworkStatsCount = allNetworkStats.reduce((total, stat) => total + stat.count, 0); -const StatValue = React.memo<{ isLoading: boolean; value: React.ReactNode | null | undefined }>( - ({ isLoading, value }) => ( - <>{isLoading ? : value != null ? value : getEmptyTagValue()} - ) -); + return ( + + {networkStatGroups.map((statGroup, i) => { + const statsForGroup = allNetworkStats.filter(s => statGroup.statIds.includes(s.id)); + const statsForGroupCount = statsForGroup.reduce((total, stat) => total + stat.count, 0); -StatValue.displayName = 'StatValue'; + const accordionButton = useMemo( + () => ( + + + {statGroup.name} + + + + + + ), + [statGroup, statsForGroupCount, loading, allNetworkStatsCount] + ); -export const OverviewNetworkStats = React.memo(({ data, loading }) => ( - - {overviewNetworkStats(data).map((item, index) => ( - - {item.title} - - - - - ))} - -)); + return ( + + + {statsForGroup.map(stat => ( + + + + {stat.title} + + + + + + + ))} + + {i !== networkStatGroups.length - 1 && } + + ); + })} + + ); +}); OverviewNetworkStats.displayName = 'OverviewNetworkStats'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx new file mode 100644 index 0000000000000..5a496ba78eb6c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/stat_value.tsx @@ -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$(DEFAULT_NUMBER_FORMAT); + + return ( + <> + {isLoading ? ( + + ) : ( + + + + {numeral(count).format(defaultNumberFormat)} + + + + + + + + + )} + + ); +}); + +StatValue.displayName = 'StatValue'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/types.ts b/x-pack/legacy/plugins/siem/public/components/page/overview/types.ts new file mode 100644 index 0000000000000..9333aa386dbc0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/types.ts @@ -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[]; +} diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx new file mode 100644 index 0000000000000..42ac3c19ff792 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx @@ -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 }) => ( + + + + + + + + + {count} + + + + + ) +); + +IconWithCount.displayName = 'IconWithCount'; + +export const RecentTimelineCounts = React.memo<{ + timeline: OpenTimelineResult; +}>(({ timeline }) => { + return ( + <> + + + + ); +}); + +RecentTimelineCounts.displayName = 'RecentTimelineCounts'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx new file mode 100644 index 0000000000000..de8a3de8094d0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx @@ -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 }) => ( + { + setFilterBy(f as FilterMode); + }} + isIconOnly + /> +)); + +Filters.displayName = 'Filters'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx new file mode 100644 index 0000000000000..886a2345248a2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/header/index.tsx @@ -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 ( + + + + onOpenTimeline({ duplicate: false, timelineId: `${savedObjectId}` })} + > + {isUntitled(timeline) ? i18n.UNTITLED_TIMELINE : title} + + + + + + + + onOpenTimeline({ + duplicate: true, + timelineId: `${savedObjectId}`, + }) + } + size="s" + /> + + + + ); +}); + +RecentTimelineHeader.displayName = 'RecentTimelineHeader'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/helpers.ts b/x-pack/legacy/plugins/siem/public/components/recent_timelines/helpers.ts new file mode 100644 index 0000000000000..61b49da01dc3a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/helpers.ts @@ -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; +}; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx new file mode 100644 index 0000000000000..f1e22d1901d47 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx @@ -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( + ({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => { + const actionDispatcher = updateIsLoading as ActionCreator<{ id: string; isLoading: boolean }>; + const [username, setUsername] = useState(undefined); + const LoadingSpinner = useMemo(() => , []); + 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: ` specified as a `search` query does not match results? + + const noTimelinesMessage = + filterBy === 'favorites' ? i18n.NO_FAVORITE_TIMELINES : i18n.NO_TIMELINES; + + return ( + + {({ timelines, loading }) => ( + <> + {loading ? ( + <>{LoadingSpinner} + ) : ( + + )} + + + {i18n.VIEW_ALL_TIMELINES} + + + )} + + ); + } +); + +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); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/recent_timelines.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/recent_timelines.tsx new file mode 100644 index 0000000000000..a310d0613d49c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/recent_timelines.tsx @@ -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 ( + <> + + {noTimelinesMessage} + + + ); + } + + return ( + <> + {timelines.map((t, i) => ( +
+ + + {t.description && t.description.length && ( + <> + + + {t.description} + + + )} + {i !== timelines.length - 1 && } +
+ ))} + + ); +}); + +RecentTimelines.displayName = 'RecentTimelines'; diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts b/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts new file mode 100644 index 0000000000000..e547272fde6e1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts @@ -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', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/types.ts b/x-pack/legacy/plugins/siem/public/components/recent_timelines/types.ts new file mode 100644 index 0000000000000..d99209dfb1267 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/types.ts @@ -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'; diff --git a/x-pack/legacy/plugins/siem/public/components/sidebar_header/index.tsx b/x-pack/legacy/plugins/siem/public/components/sidebar_header/index.tsx new file mode 100644 index 0000000000000..90949f1bdd147 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/sidebar_header/index.tsx @@ -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 }) => ( + <> + + + +

{title}

+
+
+ + {children} +
+ + + ) +); + +SidebarHeader.displayName = 'SidebarHeader'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx index 67823bea9e170..ab290c2f2fd67 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx @@ -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: '', diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index 09eef7f809773..a48653a7ea6f4 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -45,7 +45,13 @@ export const URL_STATE_KEYS: Record = { 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], }; diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx index 0167f6253c3b1..b2b38e8c34139 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx @@ -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; setQuery: SetQuery; sourceId: string; stackByOptions: MatrixHistogramOption[]; subtitle?: string | GetSubTitle; - title: string; + title: string | GetTitle; type: hostsModel.HostsType | networkModel.NetworkType; updateDateRange: UpdateDateRange; } diff --git a/x-pack/legacy/plugins/siem/public/pages/common/translations.ts b/x-pack/legacy/plugins/siem/public/pages/common/translations.ts new file mode 100644 index 0000000000000..3e20338375616 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/common/translations.ts @@ -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', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx index a217fd6a737e7..bf7a2109fd3b5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx @@ -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} /> )); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx index 713bd6239d80e..1be6317a91607 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx @@ -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(() => ( ( { 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} /> ); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx index 2a7d2fab577c3..df83ad056943a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx @@ -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 = ({ diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts index e91f48a5c2c01..3541f58555c95 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts @@ -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', { diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx index e22802fd29d49..78a3ae147fd0f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx @@ -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} /> ); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/translations.ts b/x-pack/legacy/plugins/siem/public/pages/network/translations.ts index 35224fd615978..00adce9b7ad8a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/translations.ts @@ -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', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx new file mode 100644 index 0000000000000..b038dc383df1e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/alerts_by_category/index.tsx @@ -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( + ({ + deleteQuery, + filters = NO_FILTERS, + from, + indexPattern, + query = DEFAULT_QUERY, + setAbsoluteRangeDatePicker, + setQuery, + to, + }) => { + const kibana = useKibana(); + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + const alertsCountViewAlertsButton = useMemo( + () => ( + + {i18n.VIEW_ALERTS} + + ), + [] + ); + + const getTitle = useCallback( + (option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text), + [] + ); + const getSubtitle = useCallback( + (totalCount: number) => + `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, + [] + ); + + return ( + + ); + } +); + +AlertsByCategory.displayName = 'AlertsByCategory'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx new file mode 100644 index 0000000000000..d0bb46a1965dc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx @@ -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( + ({ filters = NO_FILTERS, from, indexPattern, query = DEFAULT_QUERY, setQuery, to }) => { + const kibana = useKibana(); + + return ( + + + + + + + + + + + + ); + } +); + +EventCounts.displayName = 'EventCounts'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx new file mode 100644 index 0000000000000..b4f945c802e56 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx @@ -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( + ({ + deleteQuery, + filters = NO_FILTERS, + from, + indexPattern, + query = DEFAULT_QUERY, + setAbsoluteRangeDatePicker, + setQuery, + to, + }) => { + const kibana = useKibana(); + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + const eventsCountViewEventsButton = useMemo( + () => ( + + {i18n.VIEW_EVENTS} + + ), + [] + ); + + const getTitle = useCallback( + (option: MatrixHistogramOption) => i18n.EVENTS_COUNT_BY(option.text), + [] + ); + const getSubtitle = useCallback( + (totalCount: number) => + `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, + [] + ); + + return ( + + ); + } +); + +EventsByDataset.displayName = 'EventsByDataset'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx index e0af54acde310..65b401f00a86e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx @@ -6,8 +6,8 @@ import React, { memo } from 'react'; -import { OverviewComponent } from './overview'; +import { StatefulOverview } from './overview'; -export const Overview = memo(() => ); +export const Overview = memo(() => ); Overview.displayName = 'Overview'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx index eff61bf6a9710..be43ae8f5ed64 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx @@ -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 ''; + }, + 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: { diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx index a0e94431054cc..572a19ca67b8a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx @@ -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( + ({ filters = NO_FILTERS, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker }) => ( <> - - - - - {({ indicesExist }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - {({ setQuery }) => ( - - - - - - )} - - ) : ( - - ) - } - - + + {({ indicesExist, indexPattern }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + + + + + + + + + + {({ from, deleteQuery, setQuery, to }) => ( + <> + + + + + + + + + + + + + + + )} + + + + + + ) : ( + + ) + } + - ); -}); + ) +); + 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>( + connect(makeMapStateToProps, { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker }) +)(OverviewComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx new file mode 100644 index 0000000000000..43883515574ac --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview_empty/index.tsx @@ -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 ( + + ); +}); + +OverviewEmpty.displayName = 'OverviewEmpty'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx new file mode 100644 index 0000000000000..ad2821edde411 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/index.tsx @@ -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('favorites'); + + return ; +}); + +StatefulSidebar.displayName = 'StatefulSidebar'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx new file mode 100644 index 0000000000000..d3b85afe62a2a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/sidebar/sidebar.tsx @@ -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( + () => , + [filterBy, setFilterBy] + ); + + return ( + + + {RecentTimelinesFilters} + + + + + + + + + + + + ); +}); + +Sidebar.displayName = 'Sidebar'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx new file mode 100644 index 0000000000000..ce6e6ccf4e9e3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/overview/signals_by_category/index.tsx @@ -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( + ({ 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 ( + + ); + } +); + +SignalsByCategory.displayName = 'SignalsByCategory'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx index 51cfcbe9374ab..da16cb28c6171 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx @@ -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: ( - + + 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 SIGNALS_BY_CATEGORY = i18n.translate('xpack.siem.overview.signalsByCategoryTitle', { + defaultMessage: 'Signals count by MITRE ATT&CK\\u2122 category', }); -export const EMPTY_ACTION_PRIMARY = i18n.translate('xpack.siem.overview.emptyActionPrimary', { - defaultMessage: 'View setup instructions', +export const VIEW_ALERTS = i18n.translate('xpack.siem.overview.viewAlertsButtonLabel', { + defaultMessage: 'View alerts', }); -export const EMPTY_ACTION_SECONDARY = i18n.translate('xpack.siem.overview.emptyActionSecondary', { - defaultMessage: 'Go to documentation', +export const VIEW_EVENTS = i18n.translate('xpack.siem.overview.viewEventsButtonLabel', { + defaultMessage: 'View events', }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7796c33b5388b..8ec3d605f4fba 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11158,9 +11158,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": "異常", @@ -11238,9 +11235,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": "自動システム", @@ -11352,9 +11346,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 画像の読み込み", @@ -11372,10 +11363,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フロー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c89159568ada6..f20de892f6d0d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11157,9 +11157,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": "异常", @@ -11237,9 +11234,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": "自治系统", @@ -11351,9 +11345,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 图像加载", @@ -11371,10 +11362,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 流",