[Monitoring] Monitoring eui (#27064)

* [Monitoring] Cluster alerts table to EUI (#26031)

* Convert cluster alerts page to use EUI tables. Also adds baseline support for all monitoring tables

* Fix tests

* Remove these two files

* Keep the original table but offer a new one so existing UIs still work

* Use different base table controller for the EUI table

* Use EUI specific asc and desc constants

* [Monitoring] Elasticsearch monitoring to EUI (#26217)

* Convert cluster alerts page to use EUI tables. Also adds baseline support for all monitoring tables

* Fix tests

* Remove these two files

* Keep the original table but offer a new one so existing UIs still work

* Use different base table controller for the EUI table

* Use EUI specific asc and desc constants

* Update summary status

* ES nodes

* Indices page

* ML job listing

* Fix tests up

* Node listing page

* Advanced node page

* Advanced index

* Fix tests

* Fix onBrush

* Cluster listing page

* Update snapshots

* Fix functional tests

* Fix more tests

* Remove commented out code

* Update token API calls in elaticsearch.js (#26650)

* make selection border 1px (#26739)

* Reporting phantom and chromium tests should run in parallel. (#26566)

* Reporting phantom and chromium tests should run in parallel.

* Chromium tests should be done in group 9.

* Attempting to use group 2 in ci for chromium tests.

* X-pack CI jobs should have 7 groups.

* Phantom tests should be in group 7.

* [es-management/watcher] implement k7Breadcrumbs (#26719)

* [timelion] implement k7Breadcrumbs (#26729)

* [timelion] implement k7Breadcrumbs

* [timelion] show "Create" breadcrumb by default

* [es-management] implement k7Breadcrumbs (#26711)

* [es-manaagement] implement k7Breadcrumbs

* fix i18n ids

* Fixes i18n issue in space nav selector (#26742)

## Summary

Wraps the Spaces `NavControlPopover` in `<I18nProvider>`.

Fixes #26736

* [Docs] Add beta flag to central management docs (#26558)

* Feature/translate ml-jobs-jobs_list(part_1) (#25466)

Translate ml -> jobsList(part_1)

* Corrected wrong calls from .on to .off (#24575)

Closing memory leak

* Fix saved objects client _processBatchQueue function to handle errors (#26763)

* Fix saved objects client _processBatchQueue function to handle errors

* Fix error thrown in try/catch

* chore(.gitignore): ignore sublime workspace files (#26516)

* Map inspector requests by id so single requests can be reset at a time (#26770)

* [ML] Implement k7 breadcrumbs for ML routes (#26774)

* [ML] Implement k7 breadcrumbs for ML routes

* [ML] Remove duplicate nouns from jobs and settings breadcrumbs

* [Reporting] Re-enabled Chromium API tests (#26789)

* [Reporting] Test logging

* chromium api tests fixed

* whitespace

* Fix Elasticsearch typo on connection error screen (#26815)

`Elastiscearch` -> `Elasticsearch`.

* Reporting/reveal document bytes (#26667)

* Adding a `size` property to all job-reporting meta-data and showing in reporting details pane

* Enable heartbeat telemetry (#25886)

This commit allows heartbeat telemetry data to be sent through kibana.

The change to beats was introduced in https://github.com/elastic/beats/pull/8621

* Change 'Disenroll' text to be consistent with menu option 'Unenroll'. (#26816)

* Upgrading sshpk (#26834)

sshpk is an implicit dependency of request@2.88.0

* Re-enable the chromium functional tests (#26822)

* Logging when max-bytes is larger than what's set in ES (#26482)

* Simple check if ES body-size is smaller than KBN report size

* [BeatsCM] Cleanup and refactor (#26636)

* Refactor BeatsCM

* update deps

* update more deps

* update for new EUI definitions

* update import

* Revert "update deps"

This reverts commit 759a14561d.

* use _source_includes

* remove _source_includes

* work-around due to watcher UI tests

* Keep all xpack checks safe because we cant trust its there in tests for some reason

* VALIDATION. This commit is to ensure the errors in CI are coming from beats

* remove validation that this is a beats CM issue

* More try/catch to try and find where this error is

* testing another call

* revert back to dangerouslyGetActiveInjector

* ensure expire always is a number

* fix swallowed error

* Update x-pack/plugins/beats_management/public/lib/compose/kibana.ts

Co-Authored-By: mattapperson <me@mattapperson.com>

* Update x-pack/plugins/beats_management/public/utils/page_loader.test.ts

Co-Authored-By: mattapperson <me@mattapperson.com>

* Update x-pack/plugins/beats_management/public/utils/page_loader.ts

Co-Authored-By: mattapperson <me@mattapperson.com>

* fix for new webpack import

* Fix translation map

* fix URL path

* fix other link

* removing tag from beats via tag details screen now uses container

* remove debug text

* added comment/readme about routing on the client side

* enrolled beat UI now works on overview screen

* newly enrolled beat now reloads the beats table

* fix TS errors

* [APM] Transaction group agg size config (#26683)

* [APM] Fixes #24204 by adding default configs to kibana.yml

* [APM] fixes #25940 by adding APM config to control top transation group agg size

* Revert the default configs added to kibana.yml and define joi validations for `xpack.apm.ui.transactionGroupBucketSize`

* fix broken test for incorrect config

* [APM] add docs entry for `xpack.apm.ui.transactionGroupBucketSize`

* [APM] fixes #26784 by updateing import from a default to a named import (#26785)

* allow disabling gpu in tests (#26684)

* skipping failing tests (#26877)

* Feature/translate ml - jobs(part 2) (#25528)

Translate ml -> jobs - jobs-list(part_2)

* use canvas pipeline in visualize (#25996)

* Upgrade to NodeJS 10 (#25157)

* feat(NA): upgrade node js version on file configs.

* chore(NA): migrate configs and 3rd party dependencies to work on node js 10.x

* fix(NA): add missing async function declaration.

* chore(NA): updated elastic/good package to work with node10

* chore(NA): update lockfiles.

* fix(NA): add missing dep.

* fix(NA): types for node 10.

* test(NA): fix error return type for node10.

* fix(NA): kbn-pm webpack config to unlazy a require using lazy-cache. fix(NA): build to work with node 10.

* test(NA): jest integration test for kbn-pluin-helpers.

* test(NA): fix jest tests for kbn-es.

* fix(NA): use ostmpdir instead of a tmp folder inside the fixtures.

* fix(NA): change afterEach on kbn es decompress test.

* fix(NA): change afterEach on kbn es decompress test.

* fix(NA): readd mock-fs for the tests that still use it on kbn-es and that works on node10.

* fix(NA): readd mock-fs for the tests that still use it on kbn-es and that works on node10.

* refact(NA): rewrite tests using mock-fs and completely remove this dependency.

* fix(NA): failing test implementation using jest mock in order to replace mock-fs.

* fix(NA): update jest snapshots to match new ones generated one node 10.

* fix(NA): cli/cluster mock to spyOn off method instead off spyOn removeListener as this was changed on Node 10.

* fix(NA): tests for cluster_manager to also spyOn off and on instead of addListener and removeListener

* test(NA): fix management advance settings image field test flow.

* fix(NA): apply missing types for src/core/server/plugins/discovery/plugins_discovery.ts.

* test(NA): updated 2 missing snapshots for KuiCodeEditor on kbn-ui-framework.

* refact(NA): fix eslint errors.

* refact(NA): fix ts code with tslint fix. chore(NA): update jest snapshots.

* chore(NA): migrate kbn config schema peer dependency to last used joi version to avoid warning on bootstrap.

* fix(NA): tslint errors.

* chore(NA): upgrade types node to the last version.

* fix(NA): missing utf8 input format encoding when reading a file.

* chore(NA): upgrade to node 10.14.1

* fix(NA): Buffer api usage to avoid deprecation warnings.

* fixing a11y errors so we can add a11y rules for tslint (#26902)

* [DOCS] Deprecate X-Pack-centric watcher endpoints (#26899)

* Index lifecycle (#25553)

* [WIP] Index lifecycle (#25071)

* Index lifecycle management wizard

* Adding index lifecycle management files

* Updates

* Fix errors and add flyout for node details

* New diff tool

* Scroll to change for review diff

* Some feedback on copy

* Updating copy, moving components around and fixing bugs with the diff view

* Add logic to auto enable a phase when something is set

* redesign ilm

* Adding server api tests

* Removing debug and some tweaks from dave's work

* Conditionally show this message

* Policy selection cleanup

* Updates for better UX

* [COPYEDIT | ILM] Copyedit of text in index lifecycle management UI

* Use better default text

* Remove debug

* Adding readme and comments

* Update readme

* Do not need this anymore

* Remove debug or commented out code

* Remove these - they are in the tests PR

* Toggle system indices

* Aliases are not defined here anymore

* Handle rollover better in warm phase and remove from cold,delete

* adding learn more link component and switching over to using that

* fixing UI issue when no policies exist

* various fixes

* some cleanup

* moving number_of_replicas due to API change

* modifying some messaging

* fixing typo

* fixing some diff issues and not adding sattr_name if none chosen

* making write alias required for template step and making necessary API changes

* removing alias definition from template patching as it needs to be per index

* some copy edits for clarity

* fixing issue with editing existing policy when rollover starts the warm phase

* addressing PR feedback on server side code

* addressing PR feedback

* removing additional spaces from findMatchingNodes call

* changing template to index template in one more place

* fixing issue with error message showing when bootstrap is successful

* fixing node options for warm and cold phase

* adding seconds to duration fields to match what ES supports

* changing icon for enabled steps so it does not look like an error indicator

* adjusting icon color for enabled lifecycle steps

* fixing issue with editing an existing policy with warm phase on rollover enabled

* fixing issue with default unit for age dropdowns

* fixing issues with shrink action serialization and deserialization

* fixing issue with deserialization of ES policy for shrink

* removing shrink option from UI when primary shard count is not greater than 1 for hot phase

* going straight to create policy when no policies exist

* improving lifecycle policy selection

* adding active badge instead of checkmark for active lifecycle policy phases

* some cleanup of unneeded properties and only showing save as new when it is appropriate

* removing stray fullWidth attribute

* adding missing minimum for replica count for warm phase

* adding scroll to top for review step

* fixing issue with start warm phase after rollover introduced by time representaiton change from ES

* making shrink options not show for primary shard count of 1 as you can't shrink in that situation

* fixing issue with editing existing policy and saveAsNew

* bare bones policy table implementation

* implementing delete policy behavior

* fixing sorting and paging

* fixing policy table title

* rudimentary navigation flow

* fixing delete

* Index lifecycle management wizard (#21925)

* Index lifecycle management wizard

* Adding index lifecycle management files

* Updates

* Fix errors and add flyout for node details

* New diff tool

* Scroll to change for review diff

* Some feedback on copy

* Updating copy, moving components around and fixing bugs with the diff view

* Add logic to auto enable a phase when something is set

* redesign ilm

* Adding server api tests

* Removing debug and some tweaks from dave's work

* Conditionally show this message

* Policy selection cleanup

* Updates for better UX

* [COPYEDIT | ILM] Copyedit of text in index lifecycle management UI

* Use better default text

* Remove debug

* Adding readme and comments

* Update readme

* Do not need this anymore

* Remove debug or commented out code

* Remove these - they are in the tests PR

* Toggle system indices

* Aliases are not defined here anymore

* Handle rollover better in warm phase and remove from cold,delete

* adding learn more link component and switching over to using that

* fixing UI issue when no policies exist

* various fixes

* some cleanup

* moving number_of_replicas due to API change

* modifying some messaging

* fixing typo

* fixing some diff issues and not adding sattr_name if none chosen

* making write alias required for template step and making necessary API changes

* removing alias definition from template patching as it needs to be per index

* some copy edits for clarity

* fixing issue with editing existing policy when rollover starts the warm phase

* addressing PR feedback on server side code

* addressing PR feedback

* removing additional spaces from findMatchingNodes call

* changing template to index template in one more place

* fixing issue with error message showing when bootstrap is successful

* fixing node options for warm and cold phase

* adding seconds to duration fields to match what ES supports

* changing icon for enabled steps so it does not look like an error indicator

* adjusting icon color for enabled lifecycle steps

* fixing issue with editing an existing policy with warm phase on rollover enabled

* fixing issue with default unit for age dropdowns

* fixing issues with shrink action serialization and deserialization

* fixing issue with deserialization of ES policy for shrink

* removing shrink option from UI when primary shard count is not greater than 1 for hot phase

* going straight to create policy when no policies exist

* improving lifecycle policy selection

* adding active badge instead of checkmark for active lifecycle policy phases

* some cleanup of unneeded properties and only showing save as new when it is appropriate

* removing stray fullWidth attribute

* adding missing minimum for replica count for warm phase

* adding scroll to top for review step

* fixing issue with start warm phase after rollover introduced by time representaiton change from ES

* making shrink options not show for primary shard count of 1 as you can't shrink in that situation

* fixing issue with editing existing policy and saveAsNew

* adjusting to changes in ES API

* adding version and modified date to policies table

* implementing new CRUD approach

* simplified delete

* cleanup edit_policy

* removed wizard code

* fixing issue with edit policy

* fixing issue with closing delete confirmation modal

* making max age and max size not mutually exclusive

* removing names of covered indices from policy table

* changing minimum_age to min_age

* first pass at index lifecycle extensions

* adding retry button for ilm covered index that is in error

* first pass at index lifecycle banner

* i18n work

* more i18n

* fixing issue with node attributes

* removing console.log statements

* fixing issue with deserializing number_of_shards for edit policy

* defaulting shrink to false and fixing ui spacing issue

* removing hot phase shard count from warm phase

* scrolling to first error when user submits form for edit policy

* disabling UI for index management when enabled is false in kibana.yml

* disabling index lifecycle management when enabled is false in kibana.yml

* extending index management filter to allow for searching fields

* add support for filtering to indices with errors for index lifecycle management banner

* i18n work

* fixing error wrappers

* fixing tests

* adding view JSON for index lifecycle policy on edit screen

* fixing label for i18n on policy JSON flyout

* removing console.log statements

* fixing tests

* removing console.log statements

* adding key for banner extensions

* fixing bad import for FormattedMessage

* add link to edit index lifecycle policy from index management index summary

* adding key for map of summary extensions

* adding proper icon for retry lifecycle action

* factoring out common min age component

* factoring out common NodeAllocation component

* add copy to clipboard for json policy flyout

* adding validation rules for policy names to match ES rules

* fixing issues with policy names with non-alphabetic characters like & ^ % ?

* moving create policy button to top right and adding fill

* adding better empty state for policy list

* moving shrink and force merge titles and descriptions to the left

* moving show JSON button to right and limiting width of JSON flyout

* fixing warning about lifecycle prop type for JSON flyout

* fixing issue with warning on prop isShowingErrors

* removing outdated README

* simplifying constants

* moving components to more logical places

* moving lib and api files to services

* renaming provider to enricher

* factoring out common data enricher behavior

* consolidating index management extension code

* removing unnecessary colon

* Revert "removing unnecessary colon"

This reverts commit 19712807bb.

* removing unnecessary colon

* adding callout to ilm summary for errors

* better formatting for banner extensions

* Revert "disabling UI for index management when enabled is false in kibana.yml"

This reverts commit 45d4e8c51d.

* removing unnecessary code for detecting disabled plugin

* adding config prefix of xpack.index_lifecycle_management

* making policy link use href and consolidating link encoding logic

* removed outdated comment

* removed outdated comment

* better solution for policy name in URI

* fixing issue with index management being disabled and index_lifecycle_management not

* adding link to index management list filtered to policy name from index_lifecycle_management policy list

* fixing tests

* adding popover for stack trace for ilm errors

* adding stack trace and phase definition popovers to ilm summary in index management

* adding to blacklist for node attributes and not showing node allocation section when there are not any node attributes to choose from

* not showing create policy button in upper right for empty state

* moving policy name form field to right to be consistent with the rest of the form layout

* moving save button to left and using secondary color

* added copied to clipboard toast message

* moving activate/deactivate buttons to left on edit policy page

* deleting unnecessary less file

* using spacer instead of style

* translating success message for edit policy save

* fixing missing props warnings for EuiDescribedFormGroup usages

* better error handling borrowed from rollups

* disabling delete when a policy is attached to indices

* adding remove lifecycle policy action

* fixing issue with remove ilm policy showing for non-managed indices`

* adding add lifecycle policy action to index management extensions in index lifecycle management

* adding confirm modal for remove lifecycle policy

* fixing validation

* fixing issue with back button and edit policy retaining old policy

* removing console.log

* making no policy modal for add lifecycle policy make more sense (no add button)

* Calling reloadIndices when a lifecycle policy gets added or removed

* fixing logic issue with spinner showing

* refactoring confirm modal on policy list page

* adding an add to index template button on policy list page

* fixing console warning about select value being null

* fixing issue with modal not opening from index management table manage menu

* changing app title and adding i18n for it

* more naming changes and adding beta badge

* adding filter extension to index management and using it to add filters for index lifecycle management

* fixing broken jest test

* fixing issue with banners appearing/disappearing based on filters

* adding xpack.ilm.ui.enabled to allow cloud to disable the ui

* add ability to configure list of node attributes to ignore in kibana.yml for cloud

* filtering out reserved system templates from fetch route list

* adding warning when user tries to add a policy to a template that already has a policy

* fixing a11y issues on edit policy form

* incorporating docs team feedback on copy

* adding learn more link to add policy to index template modal

* fixing app order for management

* fixing breadcrumb issue by adding redirect for BASE_PATH (and adding memory leak fix)

* making version and covered indices column smaller and adding horizontal scroll and min width

* right align actions and better width solution for columns

* bigger spacer under callout for no node attributes

* restricting width of edit page

* fixing typo

* removing unnecessary store code for index templates

* fixing react warning about boolean type

* moving beta badge in line with title on policies screen

* better UI for show JSON for edit policy

* commenting on the memory leak fix for React Router redirects

* fixing fatal error with node allocation flyout

* fixing issue with banner not showing

* moving unmanaged/managed filter to filter group to make it clearer what they mean

* removing unused code

* copy changes

* adding context menu for policy table instead of icon buttons

* adding fix errors badge for phases

* removing unnecessary close button in flyout footer

* adding spinner when nodeOptions are not present

* moving view a list button below input

* adding more explanation to add policy to index template modal

* adding documentation link for main ILM docs in ES

* only showing view nodes link when node attributes are selected

* removing colon from flyout title

* fixing layout for view nodes button for attributes

* making loading spinner larger for node attributes select

* fixing issue with button going off end of table

* removing title from empty prompt for policy table

* fixing max width for edit policy page

* copy edits

* don't show pager when number of policies is less than minimum page size

* making number of replicas optional and adding optional label

* fixing sort for policy table

* fixing flicker for node allocation

* removing redundant message for index policies defined

* fixing spacing/alignment issues on error display for summary

* fixing issues with pager not showing and controls disappearing when filters applied

* adding tests for policy table

* more test additions

* making search bar incremental for index management

* making JSON policy flyout show ES request JSON not internal representation

* adding error message when user tries to submit add policy to index without selecting policy

* adding validation for missing template on add policy to index template modal

* adding tests for ilm index management extensions

* adding tests for edit policy

* removing learn about node attributes link until docs come

* fixing prop type warning

* adding missing translations

* better tests for edit policy

* adding tests for node attribute inputs

* better tests for node attributes

* fixing policy table test

* fixing bad i18n id

* updating snapshot

* [Telemetry] Pull local Kibana usage stats  (#26496)

* add kibana stats

* fix tests

* format the stats for telemetry

* fix the os/platform stats

* add version to locally-source kibana telemetry stats

* use callWithInternalUser

* better get_kibana module unit test verification

* separate handleKibanaStats

* variable rename

* fix comment

* fix functional test

* keep the return object literal from handleLocalStats

* validate the payload fields

* add warning log if no kibana stats returned

* add missing apm-server response error monitor (#26570)

* [DOCS] Deprecate /_xpack/security in favor of /_security (#26897)

* fix ems hotlink (#26868)

* Initialize authorization mode for reporting jobs (#26762)

* wrap non error in a try/catch (#26898)

* fix(NA): change kbn pm webpack config to generate dist files in mode=none. (#26847)

* Fail out of auth flow on first provider failure (#26648)

In practical terms, the flexibility afforded by providers being able to
recover from the failures of previously configured providers isn't
compelling, but the ambiguity is not ideal.

* fixing a11y errors so we can add a11y rules to tslint (#26895)

* EUI 5.6.0 (#26839)

* eui 5.6.0

* Import IconType in infra/types/eui.d.ts

* fixing interpreter socket error (#26870)

* fixes split chart with no data (#26872)

* fixing tooltips for line chart (#26881)

* Make space selector a button (#26889)

* [ML] Adds isRequired where applicable to timeseries_chart props. (#26880)

Adds isRequired to timeseries_chart props to match the minimum required props necessary to render the component without errors reflected in the Minimal initialization test.

* Translate share (#26802)

* Only show change password form when a password change is possible (#26779)

* only show change password form when a password change is possible

* cleanup

* remove test code

* improved message

* [Beats CM] Add basic license type (#26935)

* Improve wording when creating a space (#26915)

* copy tweaks

* update save space toast

* adjust save toast

* fixing issue with multiple execution in console (#26933)

* fixing a11y error (#26906)

* [i18n] Translate ML - File Datavisualizer (Part 1) (#25641)

translate file_datavisualizer folder of Machine Learning (Part 1)

* Use new _graph endpoints (#26956)

* [ML] Do not pass datafeed query to Discover in custom URL (#26957)

* quick ILM fixes (#26966)

* fix fatal IE 11 error with undefined TextEncoder

* fixing validation issue with hot phase

* fixing double scroll bar on IE11
:

* Fixing a11y errors in querybar and suggestion_component (#26892)

* correcting a11y errors so that we can add a11y rules to tslint

* updating the jest snapshot

* updating the jest snapshot

* do not pass 'sortOrder' to EuiContextMenuItem in share context menu (#26890)

* do not pass 'sortOrder' to EuiContextMenuItem in share context menu

* add unit test for sortOrder

* avoid using lodash

* fix merge conflicts with internationization PR

* Feat: Workpad Templates (#23966)

* Added workpad manager which contains workpad_loader and workpad_templates

* Fixed term filter in workpad_templates

* design changes

* Removed console logs

Closes workpad manager modal after cloning template

Fixed filtering workpad templates

Removed console log

Added sample templates

Added more templates to test with

Removed cloneDeep

* case insensitive template search

* Case insensitive tag order in popover

* added descriptions and tags to sample data workpads

* refine list of initial templates

* remove sample data templates, make buttons bigger

* Added template and tag registries

* Fixed workpad loader resizing issue on home page

* Moved tags to ui folder

* Fixed template class

* Fixed properties in templates to match workpad

* fix lint errors (#26985)

* Fix: Support columns with dots (#26659)

Closes https://github.com/elastic/kibana/issues/26405

Upgrades tinymath so that columns with dots now work correctly.

---

To test, follow the details in #26405.

1. Get some data that has fields with dots in them. Beats data would work, or you can create a test index with just 2 documents as follows: 

```
POST test/test
{
  "string":"abc",
  "with.dot":"abc"
}
POST test/test
{
  "string":"abcd",
  "with.dot":"abcd"
}
```

2. Create a pie chart, splitting labels on one of the available fields.

Previously, given the POST info above, only `string` would work correctly. Now both (and both of their `.keyword` variants) work correctly.

![dec-04-2018 13-35-47](https://user-images.githubusercontent.com/404731/49473970-b2cf7a00-f7d0-11e8-995b-e1a5e2a2acba.gif)

* upgrade resize-observer-polyfill version (#26990)

* Fixing issues with the url.search being null in Node 10 (#26992)

Node 10 uses `null` to denote the non-existence of a querystring
parameter when `url.parse(urlString, true)` is used, the following
changes fix our usages within the security plugin.

* Hide logs from deleteAll on task: clean client modules into dll (#26884)

* refact(NA): deleteAll function in order to allow it to not log anything out.

* fix(NA): add missing no op debug and verbose functions.

* refact(NA): wrap log calls into if calls.

* [APM] Fix for library frames not collapsing (#26827)

* [APM] fixes #26525
- simplified the stackframe grouping algorithm
- add support for `stackframe.exclude_from_grouping`
- made the rendering more tolerant of edge cases

* Made improvements to code readability and added more meaningful test cases

*  [i18n] Translate untranslated labels (#26416)

* Translate some missing translations

* Fix issues

* Add topNavMenu translations

* Fix issues

* Fix topNav

* Fix issues

* Fix issues

* Fix kbnTopNav test and parametrs description

* [ML] Fix word break in anomalies and jobs tables (#26978)

* fixes other bucket request (#26874)

* [I18n] Register translations before plugins init (#26078)

* Register translations before plugins init

* Fix i18n engine initialization

* Fix translationPath$ RxJS pipeline

* Move translations registration to mixin

* Fix arrays concatenation

* Use prettier

* Fix translations relative paths

* Use globby instead of glob

* Update docs

* Move globby to dependencies

* Get rid of translation directories config

* Update globby patterns

* Search only for current locale translation files

* [Infra UI] Fix styling after breaking EUI changes (#27021)

This restores the Infrastructure and Logs UIs after upstream changes:

* The theme json import now behaves like a ES6 module.
* The `<EuiHeaderSection>` now requires the `grow` prop to be `true` in order to grow horizontally.

* [i18n] Optimize translation labels for Roles page (#26945)

* [i18n] Optimize translation labels

* Usage of ng-if instead of ng-show

* Update APM readme

* [APM] Convert errors API to typescript (#26801)

*  [I18n] Allow i18n filter usage outside of interpolation expressions  (#26803)

* [I18n] Allow i18n filter usage outside of interpolation expressions

* Remove redundant quotes from translation

* Update tests

* Resolve comments

* Fix wrong filter usage

* Introduce `recordOf` schema. Remove redundant declarations. (#26952)

* [Infra UI] Fix graphql type generation after package upgrades (#26991)

This fixes the infrastructure UI graphql type generation after relevant packages have been upgraded in #25157.

* [Monitoring] APM monitoring to EUI (#26344)

* Convert cluster alerts page to use EUI tables. Also adds baseline support for all monitoring tables

* Fix tests

* Remove these two files

* Keep the original table but offer a new one so existing UIs still work

* Use different base table controller for the EUI table

* Use EUI specific asc and desc constants

* Update summary status

* ES nodes

* Indices page

* ML job listing

* Fix tests up

* Node listing page

* Advanced node page

* Advanced index

* Fix tests

* Fix onBrush

* Cluster listing page

* Overview page

* Update snapshots

* Fix functional tests

* Beats instances

* Fix more tests

* Update cluster tests

* Logstash UIs

* Logstash tests

* APM pages

* [Monitoring] Beats monitoring to EUI (#26287)

* Convert cluster alerts page to use EUI tables. Also adds baseline support for all monitoring tables

* Fix tests

* Remove these two files

* Keep the original table but offer a new one so existing UIs still work

* Use different base table controller for the EUI table

* Use EUI specific asc and desc constants

* Update summary status

* ES nodes

* Indices page

* ML job listing

* Fix tests up

* Node listing page

* Advanced node page

* Advanced index

* Fix tests

* Fix onBrush

* Cluster listing page

* Overview page

* Update snapshots

* Fix functional tests

* Beats instances

* Fix more tests

* Update cluster tests

* [Monitoring] Kibana monitoring to EUI (#26361)

* Convert cluster alerts page to use EUI tables. Also adds baseline support for all monitoring tables

* Fix tests

* Remove these two files

* Keep the original table but offer a new one so existing UIs still work

* Use different base table controller for the EUI table

* Use EUI specific asc and desc constants

* Update summary status

* ES nodes

* Indices page

* ML job listing

* Fix tests up

* Node listing page

* Advanced node page

* Advanced index

* Fix tests

* Fix onBrush

* Cluster listing page

* Overview page

* Update snapshots

* Fix functional tests

* Beats instances

* Fix more tests

* Update cluster tests

* Logstash UIs

* Logstash tests

* APM pages

* Kibana pages

* [Monitoring] Logstash monitoring to EUI (#26298)

* Convert cluster alerts page to use EUI tables. Also adds baseline support for all monitoring tables

* Fix tests

* Remove these two files

* Keep the original table but offer a new one so existing UIs still work

* Use different base table controller for the EUI table

* Use EUI specific asc and desc constants

* Update summary status

* ES nodes

* Indices page

* ML job listing

* Fix tests up

* Node listing page

* Advanced node page

* Advanced index

* Fix tests

* Fix onBrush

* Cluster listing page

* Overview page

* Update snapshots

* Fix functional tests

* Beats instances

* Fix more tests

* Update cluster tests

* Logstash UIs

* Logstash tests

* Add this translation back in

* PR feedback
This commit is contained in:
Chris Roberson 2018-12-13 15:04:51 -05:00 committed by GitHub
parent 4d10fae8c7
commit 96338a7ea2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
123 changed files with 3596 additions and 2659 deletions

View file

@ -51,6 +51,8 @@ export const NORMALIZED_DERIVATIVE_UNIT = '1s';
* Values for column sorting in table options
* @type {number} 1 or -1
*/
export const EUI_SORT_ASCENDING = 'asc';
export const EUI_SORT_DESCENDING = 'desc';
export const SORT_ASCENDING = 1;
export const SORT_DESCENDING = -1;

View file

@ -0,0 +1,133 @@
/*
* 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 { capitalize } from 'lodash';
import { formatDateTimeLocal } from '../../../common/formatting';
import { formatTimestampToDuration } from '../../../common';
import { CALCULATE_DURATION_SINCE, EUI_SORT_DESCENDING } from '../../../common/constants';
import { mapSeverity } from './map_severity';
import { Tooltip } from 'plugins/monitoring/components/tooltip';
import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { EuiHealth, EuiIcon } from '@elastic/eui';
import { injectI18n } from '@kbn/i18n/react';
const linkToCategories = {
'elasticsearch/nodes': 'Elasticsearch Nodes',
'elasticsearch/indices': 'Elasticsearch Indices',
'kibana/instances': 'Kibana Instances',
'logstash/instances': 'Logstash Nodes',
};
const getColumns = (kbnUrl, scope) => ([
{
name: 'Status',
field: 'metadata.severity',
sortable: true,
render: severity => {
const severityIcon = mapSeverity(severity);
return (
<Tooltip text={severityIcon.title} placement="bottom" trigger="hover">
<EuiHealth color={severityIcon.color} data-test-subj="alertIcon" aria-label={severityIcon.title}>
{ capitalize(severityIcon.value) }
</EuiHealth>
</Tooltip>
);
}
},
{
name: 'Resolved',
field: 'resolved_timestamp',
sortable: true,
render: (resolvedTimestamp) => {
const resolution = {
icon: null,
text: 'Not Resolved'
};
if (resolvedTimestamp) {
resolution.text = `${formatTimestampToDuration(resolvedTimestamp, CALCULATE_DURATION_SINCE)} ago`;
} else {
resolution.icon = <EuiIcon type="alert" size="m" aria-label="Not Resolved" />;
}
return (
<span>
{ resolution.icon } { resolution.text }
</span>
);
},
},
{
name: 'Message',
field: 'message',
sortable: true,
render: (message, alert) => (
<FormattedAlert
prefix={alert.prefix}
suffix={alert.suffix}
message={message}
metadata={alert.metadata}
changeUrl={target => {
scope.$evalAsync(() => {
kbnUrl.changePath(target);
});
}}
/>
)
},
{
name: 'Category',
field: 'metadata.link',
sortable: true,
render: link => linkToCategories[link] ? linkToCategories[link] : 'General'
},
{
name: 'Last Checked',
field: 'update_timestamp',
sortable: true,
render: timestamp => formatDateTimeLocal(timestamp)
},
{
name: 'Triggered',
field: 'timestamp',
sortable: true,
render: timestamp => formatTimestampToDuration(timestamp, CALCULATE_DURATION_SINCE) + ' ago'
},
]);
const AlertsUI = ({ alerts, angular, sorting, pagination, onTableChange, intl }) => {
return (
<EuiMonitoringTable
className="alertsTable"
rows={alerts}
columns={getColumns(angular.kbnUrl, angular.scope)}
sorting={{
...sorting,
sort: {
...sorting.sort,
field: 'metadata.severity',
direction: EUI_SORT_DESCENDING,
}
}}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: intl.formatMessage({
id: 'xpack.monitoring.alerts.filterAlertsPlaceholder',
defaultMessage: 'Filter Alerts…'
})
},
}}
onTableChange={onTableChange}
/>
);
};
export const Alerts = injectI18n(AlertsUI);

View file

@ -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 { Alerts } from './alerts';

View file

@ -12,7 +12,8 @@ import {
EuiSpacer,
EuiPage,
EuiPageBody,
EuiFlexGroup
EuiFlexGroup,
EuiPageContent
} from '@elastic/eui';
import { Status } from './status';
@ -46,13 +47,15 @@ export function ApmServerInstance({ summary, metrics, ...props }) {
));
return (
<EuiPage style={{ backgroundColor: 'white' }}>
<EuiPage>
<EuiPageBody>
<Status stats={summary}/>
<EuiSpacer size="s"/>
<EuiFlexGroup wrap>
{charts}
</EuiFlexGroup>
<EuiPageContent>
<Status stats={summary}/>
<EuiSpacer size="s"/>
<EuiFlexGroup wrap>
{charts}
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);

View file

@ -29,7 +29,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: 'Name',
}),
value: name,
dataTestSubj: 'name'
'data-test-subj': 'name'
},
{
label: intl.formatMessage({
@ -37,7 +37,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: 'Output',
}),
value: output,
dataTestSubj: 'output'
'data-test-subj': 'output'
},
{
label: intl.formatMessage({
@ -45,7 +45,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: 'Version',
}),
value: version,
dataTestSubj: 'version'
'data-test-subj': 'version'
},
{
label: intl.formatMessage({
@ -53,7 +53,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: 'Uptime',
}),
value: formatMetric(uptime, 'time_since'),
dataTestSubj: 'uptime'
'data-test-subj': 'uptime'
},
{
label: intl.formatMessage({
@ -65,7 +65,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: '{timeOfLastEvent} ago' }, {
timeOfLastEvent: formatTimestampToDuration(+moment(timeOfLastEvent), CALCULATE_DURATION_SINCE)
}),
dataTestSubj: 'timeOfLastEvent',
'data-test-subj': 'timeOfLastEvent',
}
];

View file

@ -6,150 +6,126 @@
import React from 'react';
import moment from 'moment';
import { MonitoringTable } from '../../table';
import {
KuiTableRowCell,
KuiTableRow
} from '@kbn/ui-framework/components';
import { EuiLink } from '@elastic/eui';
import { uniq } from 'lodash';
import { EuiMonitoringTable } from '../../table';
import { EuiLink, EuiPage, EuiPageBody, EuiPageContent, EuiSpacer } from '@elastic/eui';
import { Status } from './status';
import { SORT_ASCENDING, SORT_DESCENDING, TABLE_ACTION_UPDATE_FILTER } from '../../../../common/constants';
import { formatMetric } from '../../../lib/format_number';
import { formatTimestampToDuration } from '../../../../common';
import { i18n } from '@kbn/i18n';
import { injectI18n } from '@kbn/i18n/react';
const filterFields = [ 'name', 'type', 'version', 'output' ];
const columns = [
{
title: i18n.translate('xpack.monitoring.apm.instances.nameTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.nameTitle', {
defaultMessage: 'Name'
}),
sortKey: 'name',
sortOrder: SORT_ASCENDING
field: 'name',
render: (name, instance) => (
<EuiLink
href={`#/apm/instances/${instance.uuid}`}
data-test-subj={`apmLink-${name}`}
>
{name}
</EuiLink>
)
},
{
title: i18n.translate('xpack.monitoring.apm.instances.outputEnabledTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.outputEnabledTitle', {
defaultMessage: 'Output Enabled'
}),
sortKey: 'output'
field: 'output'
},
{
title: i18n.translate('xpack.monitoring.apm.instances.totalEventsRateTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.totalEventsRateTitle', {
defaultMessage: 'Total Events Rate'
}),
sortKey: 'total_events_rate',
secondarySortOrder: SORT_DESCENDING
field: 'total_events_rate',
render: value => formatMetric(value, '', '/s')
},
{
title: i18n.translate('xpack.monitoring.apm.instances.bytesSentRateTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.bytesSentRateTitle', {
defaultMessage: 'Bytes Sent Rate'
}),
sortKey: 'bytes_sent_rate'
field: 'bytes_sent_rate',
render: value => formatMetric(value, 'byte', '/s')
},
{
title: i18n.translate('xpack.monitoring.apm.instances.outputErrorsTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.outputErrorsTitle', {
defaultMessage: 'Output Errors'
}),
sortKey: 'errors'
field: 'errors',
render: value => formatMetric(value, '0')
},
{
title: i18n.translate('xpack.monitoring.apm.instances.lastEventTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.lastEventTitle', {
defaultMessage: 'Last Event'
}),
sortKey: 'time_of_last_event'
field: 'time_of_last_event',
render: value => formatTimestampToDuration(+moment(value), 'since') + ' ago'
},
{
title: i18n.translate('xpack.monitoring.apm.instances.allocatedMemoryTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.allocatedMemoryTitle', {
defaultMessage: 'Allocated Memory'
}),
sortKey: 'memory'
field: 'memory',
render: value => formatMetric(value, 'byte')
},
{
title: i18n.translate('xpack.monitoring.apm.instances.versionTitle', {
name: i18n.translate('xpack.monitoring.apm.instances.versionTitle', {
defaultMessage: 'Version'
}),
sortKey: 'version'
field: 'version'
},
];
const instanceRowFactory = () => {
return function KibanaRow(props) {
const applyFiltering = filterText => () => {
props.dispatchTableAction(TABLE_ACTION_UPDATE_FILTER, filterText);
};
return (
<KuiTableRow>
<KuiTableRowCell>
<div className="monTableCell__name">
<EuiLink
href={`#/apm/instances/${props.uuid}`}
data-test-subj={`apmLink-${props.name}`}
>
{props.name}
</EuiLink>
</div>
</KuiTableRowCell>
<KuiTableRowCell>
{props.output}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.total_events_rate, '', '/s')}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.bytes_sent_rate, 'byte', '/s')}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.errors, '0')}
</KuiTableRowCell>
<KuiTableRowCell>
{formatTimestampToDuration(+moment(props.time_of_last_event), 'since') + ' ago'}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.memory, 'byte')}
</KuiTableRowCell>
<KuiTableRowCell>
<EuiLink
onClick={applyFiltering(props.version)}
>
{props.version}
</EuiLink>
</KuiTableRowCell>
</KuiTableRow>
);
};
};
function ApmServerInstancesUI({ apms, intl }) {
export function ApmServerInstancesUI({ apms, intl }) {
const {
pageIndex,
filterText,
sortKey,
sortOrder,
onNewState,
pagination,
sorting,
onTableChange,
data
} = apms;
const versions = uniq(data.apms.map(item => item.version)).map(version => {
return { value: version };
});
return (
<div>
<Status stats={apms.data.stats}/>
<MonitoringTable
className="apmInstancesTable"
rows={apms.data.apms}
pageIndex={pageIndex}
filterText={filterText}
sortKey={sortKey}
sortOrder={sortOrder}
onNewState={onNewState}
placeholder={intl.formatMessage({
id: 'xpack.monitoring.apm.instances.filterInstancesPlaceholder',
defaultMessage: 'Filter Instances…'
})}
filterFields={filterFields}
columns={columns}
rowComponent={instanceRowFactory()}
/>
</div>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<Status stats={data.stats} />
<EuiSpacer size="m"/>
<EuiMonitoringTable
className="apmInstancesTable"
rows={data.apms}
columns={columns}
sorting={sorting}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: intl.formatMessage({
id: 'xpack.monitoring.apm.instances.filterInstancesPlaceholder',
defaultMessage: 'Filter Instances…'
})
},
filters: [
{
type: 'field_value_selection',
field: 'version',
name: 'Version',
options: versions,
multiSelect: 'or',
}
]
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}

View file

@ -29,7 +29,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: 'Servers',
}),
value: total,
dataTestSubj: 'total'
'data-test-subj': 'total'
},
{
label: intl.formatMessage({
@ -37,7 +37,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: 'Total Events',
}),
value: formatMetric(totalEvents, '0.[0]a'),
dataTestSubj: 'totalEvents'
'data-test-subj': 'totalEvents'
},
{
label: intl.formatMessage({
@ -49,7 +49,7 @@ function StatusUI({ stats, intl }) {
defaultMessage: '{timeOfLastEvent} ago' }, {
timeOfLastEvent: formatTimestampToDuration(+moment(timeOfLastEvent), CALCULATE_DURATION_SINCE)
}),
dataTestSubj: 'timeOfLastEvent',
'data-test-subj': 'timeOfLastEvent',
}
];

View file

@ -12,7 +12,8 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiPageBody,
EuiPanel
EuiPanel,
EuiPageContent
} from '@elastic/eui';
import { Status } from '../instances/status';
@ -50,13 +51,15 @@ export function ApmOverview({
));
return (
<EuiPage style={{ backgroundColor: 'white' }}>
<EuiPage>
<EuiPageBody>
<Status stats={stats}/>
<EuiSpacer size="s"/>
<EuiFlexGroup wrap>
{charts}
</EuiFlexGroup>
<EuiPageContent>
<Status stats={stats}/>
<EuiSpacer size="s"/>
<EuiFlexGroup wrap>
{charts}
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);

View file

@ -7,8 +7,9 @@
import React from 'react';
import { MonitoringTimeseriesContainer } from '../../chart';
import { formatMetric } from '../../../lib/format_number';
import { EuiFlexItem, EuiFlexGroup, EuiPage, EuiPageBody, EuiFlexGrid, EuiSpacer } from '@elastic/eui';
import { EuiFlexItem, EuiPage, EuiPageBody, EuiFlexGrid, EuiSpacer, EuiPageContent } from '@elastic/eui';
import { injectI18n } from '@kbn/i18n/react';
import { SummaryStatus } from '../../summary_status';
function BeatUi({ summary, metrics, intl, ...props }) {
@ -23,58 +24,41 @@ function BeatUi({ summary, metrics, intl, ...props }) {
metrics.beat_handles,
];
const wrapChild = ({ label, value, dataTestSubj }, index) => (
<EuiFlexItem
key={`summary-status-item-${index}`}
grow={false}
data-test-subj={dataTestSubj}
>
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
{label ? label + ': ' : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<strong>{value}</strong>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
const summarytStatsTop = [
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.nameLabel', defaultMessage: 'Name' }),
value: summary.name,
dataTestSubj: 'name'
'data-test-subj': 'name'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.hostLabel', defaultMessage: 'Host' }),
value: summary.transportAddress,
dataTestSubj: 'host'
'data-test-subj': 'host'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.versionLabel', defaultMessage: 'Version' }),
value: summary.version,
dataTestSubj: 'version'
'data-test-subj': 'version'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.typeLabel', defaultMessage: 'Type' }),
value: summary.type,
dataTestSubj: 'type'
'data-test-subj': 'type'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.outputLabel', defaultMessage: 'Output' }),
value: summary.output,
dataTestSubj: 'output'
'data-test-subj': 'output'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.configReloadsLabel', defaultMessage: 'Config reloads' }),
value: formatMetric(summary.configReloads, 'int_commas'),
dataTestSubj: 'configReloads'
'data-test-subj': 'configReloads'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.uptimeLabel', defaultMessage: 'Uptime' }),
value: formatMetric(summary.uptime, 'time_since'),
dataTestSubj: 'uptime'
'data-test-subj': 'uptime'
},
];
@ -82,63 +66,48 @@ function BeatUi({ summary, metrics, intl, ...props }) {
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.eventsTotalLabel', defaultMessage: 'Events total' }),
value: formatMetric(summary.eventsTotal, 'int_commas'),
dataTestSubj: 'eventsTotal'
'data-test-subj': 'eventsTotal'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.eventsEmittedLabel', defaultMessage: 'Events emitted' }),
value: formatMetric(summary.eventsEmitted, 'int_commas'),
dataTestSubj: 'eventsEmitted'
'data-test-subj': 'eventsEmitted'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.eventsDroppedLabel', defaultMessage: 'Events dropped' }),
value: formatMetric(summary.eventsDropped, 'int_commas'),
dataTestSubj: 'eventsDropped'
'data-test-subj': 'eventsDropped'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.bytesSentLabel', defaultMessage: 'Bytes sent' }),
value: formatMetric(summary.bytesWritten, 'byte'),
dataTestSubj: 'bytesWritten'
'data-test-subj': 'bytesWritten'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.handlesLimitSoftLabel', defaultMessage: 'Handles limit (soft)' }),
value: formatMetric(summary.handlesSoftLimit, 'byte'),
dataTestSubj: 'handlesLimitSoft'
'data-test-subj': 'handlesLimitSoft'
},
{
label: intl.formatMessage({ id: 'xpack.monitoring.beats.instance.handlesLimitHardLabel', defaultMessage: 'Handles limit (hard)' }),
value: formatMetric(summary.handlesHardLimit, 'byte'),
dataTestSubj: 'handlesLimitHard'
'data-test-subj': 'handlesLimitHard'
},
];
return (
<div>
<div className="monSummaryStatus" role="status">
<div {...props}>
<EuiFlexGroup gutterSize="none" alignItems="center" data-test-subj="beatSummaryStatus01">
<EuiFlexItem grow={false}>
<EuiFlexGroup>
{summarytStatsTop.map(wrapChild)}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</div>
</div>
<div className="monSummaryStatus" role="status">
<div {...props}>
<EuiFlexGroup gutterSize="none" alignItems="center" data-test-subj="beatSummaryStatus02">
<EuiFlexItem grow={false}>
<EuiFlexGroup>
{summarytStatsBot.map(wrapChild)}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</div>
</div>
<EuiPage style={{ backgroundColor: 'white' }}>
<EuiPageBody>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<SummaryStatus
metrics={summarytStatsTop}
data-test-subj="beatSummaryStatus01"
/>
<SummaryStatus
metrics={summarytStatsBot}
data-test-subj="beatSummaryStatus02"
/>
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
@ -150,9 +119,9 @@ function BeatUi({ summary, metrics, intl, ...props }) {
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPageBody>
</EuiPage>
</div>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}

View file

@ -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 { Listing } from './listing';

View file

@ -0,0 +1,133 @@
/*
* 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, { PureComponent } from 'react';
import { uniq } from 'lodash';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui';
import { Stats } from 'plugins/monitoring/components/beats';
import { formatMetric } from 'plugins/monitoring/lib/format_number';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { i18n } from '@kbn/i18n';
import { injectI18n } from '@kbn/i18n/react';
class ListingUI extends PureComponent {
getColumns() {
const { kbnUrl, scope } = this.props.angular;
return [
{
name: i18n.translate('xpack.monitoring.beats.instances.nameTitle', { defaultMessage: 'Name' }),
field: 'name',
render: (name, beat) => (
<EuiLink
onClick={() => {
scope.$evalAsync(() => {
kbnUrl.changePath(`/beats/beat/${beat.uuid}`);
});
}}
data-test-subj={`beatLink-${name}`}
>
{name}
</EuiLink>
)
},
{
name: i18n.translate('xpack.monitoring.beats.instances.typeTitle', { defaultMessage: 'Type' }),
field: 'type',
},
{
name: i18n.translate('xpack.monitoring.beats.instances.outputEnabledTitle', { defaultMessage: 'Output Enabled' }),
field: 'output'
},
{
name: i18n.translate('xpack.monitoring.beats.instances.totalEventsRateTitle', { defaultMessage: 'Total Events Rate' }),
field: 'total_events_rate',
render: value => formatMetric(value, '', '/s')
},
{
name: i18n.translate('xpack.monitoring.beats.instances.bytesSentRateTitle', { defaultMessage: 'Bytes Sent Rate' }),
field: 'bytes_sent_rate',
render: value => formatMetric(value, 'byte', '/s')
},
{
name: i18n.translate('xpack.monitoring.beats.instances.outputErrorsTitle', { defaultMessage: 'Output Errors' }),
field: 'errors',
render: value => formatMetric(value, '0')
},
{
name: i18n.translate('xpack.monitoring.beats.instances.allocatedMemoryTitle', { defaultMessage: 'Allocated Memory' }),
field: 'memory',
render: value => formatMetric(value, 'byte')
},
{
name: i18n.translate('xpack.monitoring.beats.instances.versionTitle', { defaultMessage: 'Version' }),
field: 'version',
},
];
}
render() {
const {
stats,
data,
sorting,
pagination,
onTableChange
} = this.props;
const types = uniq(data.map(item => item.type)).map(type => {
return { value: type };
});
const versions = uniq(data.map(item => item.version)).map(version => {
return { value: version };
});
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<Stats stats={stats} />
<EuiSpacer size="m"/>
<EuiMonitoringTable
className="beatsTable"
rows={data}
columns={this.getColumns()}
sorting={sorting}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: i18n.translate('xpack.monitoring.beats.filterBeatsPlaceholder', { defaultMessage: 'Filter Beats...' }),
},
filters: [
{
type: 'field_value_selection',
field: 'type',
name: 'Type',
options: types,
multiSelect: 'or',
},
{
type: 'field_value_selection',
field: 'version',
name: 'Version',
options: versions,
multiSelect: 'or',
}
]
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
}
export const Listing = injectI18n(ListingUI);

View file

@ -3,260 +3,256 @@
exports[`Overview that overview page renders normally 1`] = `
<EuiPage
restrictWidth={false}
style={
Object {
"backgroundColor": "white",
}
}
>
<EuiPageBody
restrictWidth={false}
>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={true}
<EuiPageContent
panelPaddingSize="l"
>
<EuiFlexItem
<Stats
stats={Array []}
/>
<EuiFlexGroup
alignItems="stretch"
component="div"
grow={true}
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={true}
>
<EuiPanel
<EuiFlexItem
component="div"
grow={true}
hasShadow={false}
paddingSize="m"
>
<EuiTitle
size="s"
textTransform="none"
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<h3>
<FormattedMessage
defaultMessage="Active Beats in Last Day"
id="xpack.monitoring.beats.overview.activeBeatsInLastDayTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<InjectIntl(LatestActiveUi)
latestActive={
Array [
Object {
"count": 5,
"range": "last1m",
},
Object {
"count": 5,
"range": "last5m",
},
Object {
"count": 5,
"range": "last20m",
},
Object {
"count": 6,
"range": "last1h",
},
Object {
"count": 10,
"range": "last1d",
},
]
}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiPanel
<EuiTitle
size="s"
textTransform="none"
>
<h3>
<FormattedMessage
defaultMessage="Active Beats in Last Day"
id="xpack.monitoring.beats.overview.activeBeatsInLastDayTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<InjectIntl(LatestActiveUi)
latestActive={
Array [
Object {
"count": 5,
"range": "last1m",
},
Object {
"count": 5,
"range": "last5m",
},
Object {
"count": 5,
"range": "last20m",
},
Object {
"count": 6,
"range": "last1h",
},
Object {
"count": 10,
"range": "last1d",
},
]
}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
hasShadow={false}
paddingSize="m"
>
<EuiTitle
size="s"
textTransform="none"
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<h3>
<FormattedMessage
defaultMessage="Top 5 Beat Types in Last Day"
id="xpack.monitoring.beats.overview.top5BeatTypesInLastDayTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<LatestTypes
latestTypes={
Array [
Object {
"count": 4,
"type": "Packetbeat",
},
Object {
"count": 4,
"type": "Metricbeat",
},
Object {
"count": 2,
"type": "Heartbeat",
},
]
}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiPanel
<EuiTitle
size="s"
textTransform="none"
>
<h3>
<FormattedMessage
defaultMessage="Top 5 Beat Types in Last Day"
id="xpack.monitoring.beats.overview.top5BeatTypesInLastDayTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<LatestTypes
latestTypes={
Array [
Object {
"count": 4,
"type": "Packetbeat",
},
Object {
"count": 4,
"type": "Metricbeat",
},
Object {
"count": 2,
"type": "Heartbeat",
},
]
}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
hasShadow={false}
paddingSize="m"
>
<EuiTitle
size="s"
textTransform="none"
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<h3>
<FormattedMessage
defaultMessage="Top 5 Versions in Last Day"
id="xpack.monitoring.beats.overview.top5VersionsInLastDayTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<LatestVersions
latestVersions={
Array [
Object {
"count": 8,
"version": "6.3.1",
},
Object {
"count": 2,
"version": "6.3.0",
},
]
<EuiTitle
size="s"
textTransform="none"
>
<h3>
<FormattedMessage
defaultMessage="Top 5 Versions in Last Day"
id="xpack.monitoring.beats.overview.top5VersionsInLastDayTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<LatestVersions
latestVersions={
Array [
Object {
"count": 8,
"version": "6.3.1",
},
Object {
"count": 2,
"version": "6.3.0",
},
]
}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={true}
>
<EuiFlexItem
component="div"
grow={true}
key="0"
style={
Object {
"minWidth": "45%",
}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<Stats
stats={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={true}
>
<EuiFlexItem
component="div"
grow={true}
key="0"
style={
Object {
"minWidth": "45%",
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="1"
style={
Object {
"minWidth": "45%",
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="1"
style={
Object {
"minWidth": "45%",
}
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="2"
style={
Object {
"minWidth": "45%",
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="2"
style={
Object {
"minWidth": "45%",
}
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="3"
style={
Object {
"minWidth": "45%",
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="3"
style={
Object {
"minWidth": "45%",
}
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
`;
@ -264,121 +260,117 @@ exports[`Overview that overview page renders normally 1`] = `
exports[`Overview that overview page shows a message if there is no beats data 1`] = `
<EuiPage
restrictWidth={false}
style={
Object {
"backgroundColor": "white",
}
}
>
<EuiPageBody
restrictWidth={false}
>
<EuiCallOut
color="primary"
data-test-subj="noRecentActivityMessage"
iconType="gear"
size="m"
title="Hi there! This area is where your latest Beats activity would show up, but you don't seem to have any activity within the last day."
/>
<EuiSpacer
size="s"
/>
<Stats
stats={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={true}
<EuiPageContent
panelPaddingSize="l"
>
<EuiFlexItem
<Stats
stats={Array []}
/>
<EuiCallOut
color="primary"
data-test-subj="noRecentActivityMessage"
iconType="gear"
size="m"
title="Hi there! This area is where your latest Beats activity would show up, but you don't seem to have any activity within the last day."
/>
<EuiSpacer
size="s"
/>
<EuiFlexGroup
alignItems="stretch"
component="div"
grow={true}
key="0"
style={
Object {
"minWidth": "45%",
}
}
direction="row"
gutterSize="l"
justifyContent="flexStart"
responsive={true}
wrap={true}
>
<EuiPanel
<EuiFlexItem
component="div"
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="1"
style={
Object {
"minWidth": "45%",
key="0"
style={
Object {
"minWidth": "45%",
}
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="2"
style={
Object {
"minWidth": "45%",
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="1"
style={
Object {
"minWidth": "45%",
}
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="3"
style={
Object {
"minWidth": "45%",
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="2"
style={
Object {
"minWidth": "45%",
}
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
key="3"
style={
Object {
"minWidth": "45%",
}
}
>
<EuiPanel
grow={true}
hasShadow={false}
paddingSize="m"
>
<InjectIntl(MonitoringTimeseriesContainerUI)
series={1}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
`;

View file

@ -10,7 +10,6 @@ import { LatestVersions } from './latest_versions';
import { LatestTypes } from './latest_types';
import { Stats } from '../';
import { MonitoringTimeseriesContainer } from '../../chart';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import {
EuiCallOut,
EuiTitle,
@ -19,8 +18,10 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiPageBody,
EuiPanel
EuiPanel,
EuiPageContent
} from '@elastic/eui';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
function renderLatestActive(latestActive, latestTypes, latestVersions, intl) {
if (latestTypes && latestTypes.length > 0) {
@ -78,6 +79,7 @@ function renderLatestActive(latestActive, latestTypes, latestVersions, intl) {
defaultMessage: `Hi there! This area is where your latest Beats activity would show up, but you don't seem to have any activity within the last day.`
});
return (
<EuiCallOut
title={calloutMsg}
@ -87,7 +89,7 @@ function renderLatestActive(latestActive, latestTypes, latestVersions, intl) {
);
}
function BeatsOverviewUi({
export function BeatsOverviewUI({
latestActive,
latestTypes,
latestVersions,
@ -115,18 +117,19 @@ function BeatsOverviewUi({
));
return (
<EuiPage style={{ backgroundColor: 'white' }}>
<EuiPage>
<EuiPageBody>
{renderLatestActive(latestActive, latestTypes, latestVersions, intl)}
<EuiSpacer size="s"/>
<Stats stats={stats} />
<EuiSpacer size="s"/>
<EuiFlexGroup wrap>
{charts}
</EuiFlexGroup>
<EuiPageContent>
<Stats stats={stats} />
{renderLatestActive(latestActive, latestTypes, latestVersions, intl)}
<EuiSpacer size="s"/>
<EuiFlexGroup wrap>
{charts}
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
export const BeatsOverview = injectI18n(BeatsOverviewUi);
export const BeatsOverview = injectI18n(BeatsOverviewUI);

View file

@ -4,80 +4,51 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
import React from 'react';
import { formatMetric } from 'plugins/monitoring/lib/format_number';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { SummaryStatus } from '../summary_status';
export function Stats({ stats }) {
const types = stats.types.map(({ type, count }, index) => {
return (
<EuiFlexItem
key={`type-${index}`}
data-test-subj="typeCount"
data-test-type-count={type + ':' + count}
grow={false}
>
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
{type ? type + ': ' : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<strong>{formatMetric(count, 'int_commas')}</strong>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
const {
total,
types,
stats: {
bytesSent,
totalEvents,
}
} = stats;
const metrics = [
{
label: 'Total Beats',
value: formatMetric(total, 'int_commas'),
'data-test-subj': 'totalBeats'
},
];
metrics.push(...types.map(({ type, count }) => ({
label: type,
value: formatMetric(count, 'int_commas'),
'data-test-subj': 'typeCount',
'data-test-type-count': `${type}:${count}`
})));
metrics.push({
label: 'Total Events',
value: formatMetric(totalEvents, '0.[0]a'),
'data-test-subj': 'totalEvents'
});
metrics.push({
label: 'Bytes Sent',
value: formatMetric(bytesSent, 'byte'),
'data-test-subj': 'bytesSent'
});
return (
<div className="monSummaryStatus" role="status" data-test-subj="beatsSummaryStatus">
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.monitoring.beats.overview.totalBeatsLabel"
defaultMessage="Total Beats"
/>
:&nbsp;
</EuiFlexItem>
<EuiFlexItem grow={false}>
<strong data-test-subj="totalBeats">
{formatMetric(get(stats, 'total'), 'int_commas')}
</strong>
</EuiFlexItem>
{types}
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.monitoring.beats.overview.totalEventsLabel"
defaultMessage="Total Events"
/>
:&nbsp;
</EuiFlexItem>
<EuiFlexItem grow={false}>
<strong data-test-subj="totalEvents">
{formatMetric(get(stats, 'stats.totalEvents'), '0.[0]a')}
</strong>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.monitoring.beats.overview.bytesSentLabel"
defaultMessage="Bytes Sent"
/>
:&nbsp;
</EuiFlexItem>
<EuiFlexItem grow={false}>
<strong data-test-subj="bytesSent">
{formatMetric(get(stats, 'stats.bytesSent'), 'byte')}
</strong>
</EuiFlexItem>
</EuiFlexGroup>
</div>
<SummaryStatus
metrics={metrics}
data-test-subj="beatsSummaryStatus"
/>
);
}

View file

@ -29,7 +29,7 @@ function StatusUI({ stat, formattedLeader, oldestStat, intl }) {
defaultMessage: 'Follower Index',
}),
value: followerIndex,
dataTestSubj: 'followerIndex'
'data-test-subj': 'followerIndex'
},
{
label: intl.formatMessage({
@ -37,7 +37,7 @@ function StatusUI({ stat, formattedLeader, oldestStat, intl }) {
defaultMessage: 'Shard Id',
}),
value: shardId,
dataTestSubj: 'shardId'
'data-test-subj': 'shardId'
},
{
label: intl.formatMessage({
@ -45,7 +45,7 @@ function StatusUI({ stat, formattedLeader, oldestStat, intl }) {
defaultMessage: 'Leader Index',
}),
value: formattedLeader,
dataTestSubj: 'leaderIndex'
'data-test-subj': 'leaderIndex'
},
{
label: intl.formatMessage({
@ -53,7 +53,7 @@ function StatusUI({ stat, formattedLeader, oldestStat, intl }) {
defaultMessage: 'Ops Synced',
}),
value: formatMetric(operationsReceived - oldestOperationsReceived, 'int_commas'),
dataTestSubj: 'operationsReceived'
'data-test-subj': 'operationsReceived'
},
{
label: intl.formatMessage({
@ -61,7 +61,7 @@ function StatusUI({ stat, formattedLeader, oldestStat, intl }) {
defaultMessage: 'Failed Fetches',
}),
value: formatMetric(failedFetches - oldestFailedFetches, 'int_commas'),
dataTestSubj: 'failedFetches'
'data-test-subj': 'failedFetches'
},
];

View file

@ -30,7 +30,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Nodes',
}),
value: nodesCount,
dataTestSubj: 'nodesCount'
'data-test-subj': 'nodesCount'
},
{
label: intl.formatMessage({
@ -38,7 +38,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Indices',
}),
value: indicesCount,
dataTestSubj: 'indicesCount'
'data-test-subj': 'indicesCount'
},
{
label: intl.formatMessage({
@ -46,7 +46,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Memory',
}),
value: formatMetric(memUsed, 'byte') + ' / ' + formatMetric(memMax, 'byte'),
dataTestSubj: 'memory'
'data-test-subj': 'memory'
},
{
label: intl.formatMessage({
@ -54,7 +54,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Total Shards',
}),
value: totalShards,
dataTestSubj: 'totalShards'
'data-test-subj': 'totalShards'
},
{
label: intl.formatMessage({
@ -62,7 +62,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Unassigned Shards',
}),
value: unassignedShards,
dataTestSubj: 'unassignedShards'
'data-test-subj': 'unassignedShards'
},
{
label: intl.formatMessage({
@ -70,7 +70,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Documents',
}),
value: formatMetric(documentCount, 'int_commas'),
dataTestSubj: 'documentCount'
'data-test-subj': 'documentCount'
},
{
label: intl.formatMessage({
@ -78,7 +78,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Data',
}),
value: formatMetric(dataSize, 'byte'),
dataTestSubj: 'dataSize'
'data-test-subj': 'dataSize'
}
];

View file

@ -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 React from 'react';
import {
EuiPage,
EuiPageContent,
EuiPageBody,
EuiSpacer,
EuiFlexGrid,
EuiFlexItem,
} from '@elastic/eui';
import { IndexDetailStatus } from '../index_detail_status';
import { MonitoringTimeseriesContainer } from '../../chart';
export const AdvancedIndex = ({
indexSummary,
metrics,
...props
}) => {
const metricsToShow = [
metrics.index_1,
metrics.index_2,
metrics.index_3,
metrics.index_4,
metrics.index_total,
metrics.index_time,
metrics.index_refresh,
metrics.index_throttling,
metrics.index_disk,
metrics.index_segment_count,
metrics.index_latency,
];
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<IndexDetailStatus stats={indexSummary} />
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
<MonitoringTimeseriesContainer
series={metric}
{...props}
/>
<EuiSpacer size="m"/>
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};

View file

@ -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 React from 'react';
import {
EuiPage,
EuiPageContent,
EuiPageBody,
EuiSpacer,
EuiFlexGrid,
EuiFlexItem,
} from '@elastic/eui';
import { IndexDetailStatus } from '../index_detail_status';
import { MonitoringTimeseriesContainer } from '../../chart';
import { ShardAllocation } from '../shard_allocation/shard_allocation';
export const Index = ({
indexSummary,
metrics,
scope,
kbnUrl,
...props
}) => {
const metricsToShow = [
metrics.index_mem,
metrics.index_size,
metrics.index_search_request_rate,
metrics.index_request_rate,
metrics.index_segment_count,
metrics.index_document_count,
];
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<IndexDetailStatus stats={indexSummary} />
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
<MonitoringTimeseriesContainer
series={metric}
{...props}
/>
<EuiSpacer size="m"/>
</EuiFlexItem>
))}
</EuiFlexGrid>
<EuiSpacer size="m"/>
<ShardAllocation scope={scope} kbnUrl={kbnUrl} type="index" />
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};

View file

@ -26,7 +26,7 @@ function IndexDetailStatusUI({ stats, intl }) {
defaultMessage: 'Total',
}),
value: formatMetric(dataSize.total, '0.0 b'),
dataTestSubj: 'dataSize'
'data-test-subj': 'dataSize'
},
{
label: intl.formatMessage({
@ -34,7 +34,7 @@ function IndexDetailStatusUI({ stats, intl }) {
defaultMessage: 'Primaries',
}),
value: formatMetric(dataSize.primaries, '0.0 b'),
dataTestSubj: 'dataSizePrimaries'
'data-test-subj': 'dataSizePrimaries'
},
{
label: intl.formatMessage({
@ -42,7 +42,7 @@ function IndexDetailStatusUI({ stats, intl }) {
defaultMessage: 'Documents',
}),
value: formatMetric(documentCount, '0.[0]a'),
dataTestSubj: 'documentCount'
'data-test-subj': 'documentCount'
},
{
label: intl.formatMessage({
@ -50,7 +50,7 @@ function IndexDetailStatusUI({ stats, intl }) {
defaultMessage: 'Total Shards',
}),
value: formatMetric(totalShards, 'int_commas'),
dataTestSubj: 'totalShards'
'data-test-subj': 'totalShards'
},
{
label: intl.formatMessage({
@ -58,7 +58,7 @@ function IndexDetailStatusUI({ stats, intl }) {
defaultMessage: 'Unassigned Shards',
}),
value: formatMetric(unassignedShards, 'int_commas'),
dataTestSubj: 'unassignedShards'
'data-test-subj': 'unassignedShards'
}
];

View file

@ -4,133 +4,119 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import { capitalize, get } from 'lodash';
import { SORT_ASCENDING, SORT_DESCENDING } from '../../../../common/constants';
import React from 'react';
import { capitalize } from 'lodash';
import { LARGE_FLOAT, LARGE_BYTES, LARGE_ABBREVIATED } from '../../../../common/formatting';
import { formatMetric } from '../../../lib/format_number';
import { ElasticsearchStatusIcon } from '../status_icon';
import { ClusterStatus } from '../cluster_status';
import { MonitoringTable } from '../../table';
import { EuiLink } from '@elastic/eui';
import { KuiTableRowCell, KuiTableRow } from '@kbn/ui-framework/components';
import { SystemIndicesCheckbox } from './system_indices_checkbox';
import { EuiMonitoringTable } from '../../table';
import {
EuiLink,
EuiPage,
EuiPageContent,
EuiPageBody,
EuiSwitch,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
const filterFields = ['name', 'status'];
const columns = [
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.nameTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.nameTitle', {
defaultMessage: 'Name',
}),
sortKey: 'name',
secondarySortOrder: SORT_ASCENDING
field: 'name',
width: '350px',
sortable: true,
render: (value) => (
<div data-test-subj="name">
<EuiLink
href={`#/elasticsearch/indices/${value}`}
data-test-subj={`indexLink-${value}`}
>
{value}
</EuiLink>
</div>
),
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.statusTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.statusTitle', {
defaultMessage: 'Status',
}),
sortKey: 'status_sort',
sortOrder: SORT_DESCENDING // default sort: red, then yellow, then green
field: 'status',
sortable: true,
render: (value) => (
<div title={`Index status: ${value}`}>
<ElasticsearchStatusIcon status={value} />&nbsp;
{capitalize(value)}
</div>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.documentCountTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.documentCountTitle', {
defaultMessage: 'Document Count',
}),
sortKey: 'doc_count'
field: 'doc_count',
sortable: true,
render: value => (
<div data-test-subj="documentCount">
{formatMetric(value, LARGE_ABBREVIATED)}
</div>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.dataTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.dataTitle', {
defaultMessage: 'Data',
}),
sortKey: 'data_size'
field: 'data_size',
sortable: true,
render: value => (
<div data-test-subj="dataSize">
{formatMetric(value, LARGE_BYTES)}
</div>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.indexRateTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.indexRateTitle', {
defaultMessage: 'Index Rate',
}),
sortKey: 'index_rate'
field: 'index_rate',
sortable: true,
render: value => (
<div data-test-subj="indexRate">
{formatMetric(value, LARGE_FLOAT, '/s')}
</div>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.searchRateTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.searchRateTitle', {
defaultMessage: 'Search Rate',
}),
sortKey: 'search_rate'
field: 'search_rate',
sortable: true,
render: value => (
<div data-test-subj="searchRate">
{formatMetric(value, LARGE_FLOAT, '/s')}
</div>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.indices.unassignedShardsTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.indices.unassignedShardsTitle', {
defaultMessage: 'Unassigned Shards',
}),
sortKey: 'unassigned_shards'
field: 'unassigned_shards',
sortable: true,
render: value => (
<div data-test-subj="unassignedShards">
{formatMetric(value, '0')}
</div>
)
}
];
const IndexRow = injectI18n(({ status, ...props }) => (
<KuiTableRow>
<KuiTableRowCell data-test-subj="name">
<EuiLink
href={`#/elasticsearch/indices/${props.name}`}
data-test-subj={`indexLink-${props.name}`}
>
{props.name}
</EuiLink>
</KuiTableRowCell>
<KuiTableRowCell>
<div
title={props.intl.formatMessage({
id: 'xpack.monitoring.elasticsearch.indices.indexStatusTitle',
defaultMessage: 'Index status: {status}' }, {
status
})}
>
<ElasticsearchStatusIcon status={status} />&nbsp;
{capitalize(status)}
</div>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="documentCount">
{formatMetric(get(props, 'doc_count'), LARGE_ABBREVIATED)}
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="dataSize">
{formatMetric(get(props, 'data_size'), LARGE_BYTES)}
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="indexRate">
{formatMetric(get(props, 'index_rate'), LARGE_FLOAT, '/s')}
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="searchRate">
{formatMetric(get(props, 'search_rate'), LARGE_FLOAT, '/s')}
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="unassignedShards">
{formatMetric(get(props, 'unassigned_shards'), '0')}
</KuiTableRowCell>
</KuiTableRow>
));
const getNoDataMessage = filterText => {
const howToShowSystemIndicesDescription = (
<FormattedMessage
id="xpack.monitoring.elasticsearch.indices.howToShowSystemIndicesDescription"
defaultMessage="If you are looking for system indices (e.g., .kibana), try checking &lsquo;Show system indices&rsquo;."
/>
);
if (filterText) {
return (
<div>
<p>
<FormattedMessage
id="xpack.monitoring.elasticsearch.indices.noFilteredIndicesDescription"
defaultMessage="There are no indices that match your selection with the filter [{filterText}].
Try changing the filter or the time range selection."
values={{
filterText: filterText.trim()
}}
/>
</p>
<p>
{howToShowSystemIndicesDescription}
</p>
</div>
);
}
const getNoDataMessage = () => {
return (
<div>
<p>
@ -140,46 +126,64 @@ const getNoDataMessage = filterText => {
/>
</p>
<p>
{howToShowSystemIndicesDescription}
<FormattedMessage
id="xpack.monitoring.elasticsearch.indices.howToShowSystemIndicesDescription"
defaultMessage="If you are looking for system indices (e.g., .kibana), try checking &lsquo;Show system indices&rsquo;."
/>
</p>
</div>
);
};
const renderToolBarSection = ({ showSystemIndices, toggleShowSystemIndices, ...props }) => (
<SystemIndicesCheckbox
showSystemIndices={showSystemIndices}
toggleShowSystemIndices={toggleShowSystemIndices}
{...props}
/>
);
function ElasticsearchIndicesUI({ clusterStatus, indices, intl, ...props }) {
const ElasticsearchIndicesUI = ({
clusterStatus,
indices,
intl,
sorting,
pagination,
onTableChange,
toggleShowSystemIndices,
showSystemIndices,
}) => {
return (
<Fragment>
<ClusterStatus stats={clusterStatus} />
<MonitoringTable
className="elasticsearchIndicesTable"
rows={indices}
pageIndex={props.pageIndex}
filterText={props.filterText}
sortKey={props.sortKey}
sortOrder={props.sortOrder}
onNewState={props.onNewState}
placeholder={intl.formatMessage({
id: 'xpack.monitoring.elasticsearch.indices.monitoringTablePlaceholder',
defaultMessage: 'Filter Indices…',
})}
filterFields={filterFields}
renderToolBarSections={renderToolBarSection}
columns={columns}
rowComponent={IndexRow}
getNoDataMessage={getNoDataMessage}
showSystemIndices={props.showSystemIndices}
toggleShowSystemIndices={props.toggleShowSystemIndices}
/>
</Fragment>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<ClusterStatus stats={clusterStatus} />
<EuiSpacer size="xs"/>
<EuiSwitch
label={(
<FormattedMessage
id="xpack.monitoring.elasticsearch.indices.systemIndicesLabel"
defaultMessage="System indices"
/>
)}
checked={showSystemIndices}
onChange={e => toggleShowSystemIndices(e.target.checked)}
/>
<EuiSpacer size="m"/>
<EuiMonitoringTable
className="elasticsearchIndicesTable"
rows={indices}
columns={columns}
sorting={sorting}
pagination={pagination}
message={getNoDataMessage()}
search={{
box: {
incremental: true,
placeholder: intl.formatMessage({
id: 'xpack.monitoring.elasticsearch.indices.monitoringTablePlaceholder',
defaultMessage: 'Filter Indices…',
})
},
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
};
export const ElasticsearchIndices = injectI18n(ElasticsearchIndicesUI);

View file

@ -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 from 'react';
import {
EuiPage,
EuiPageContent,
EuiPageBody,
EuiSpacer,
EuiFlexGrid,
EuiFlexItem,
} from '@elastic/eui';
import { NodeDetailStatus } from '../node_detail_status';
import { MonitoringTimeseriesContainer } from '../../chart';
export const AdvancedNode = ({
nodeSummary,
metrics,
...props
}) => {
const metricsToShow = [
metrics.node_gc,
metrics.node_gc_time,
metrics.node_jvm_mem,
metrics.node_cpu_utilization,
metrics.node_index_1,
metrics.node_index_2,
metrics.node_index_3,
metrics.node_index_4,
metrics.node_index_time,
metrics.node_request_total,
metrics.node_index_threads,
metrics.node_read_threads,
metrics.node_cgroup_cpu,
metrics.node_cgroup_stats,
metrics.node_latency,
];
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<NodeDetailStatus stats={nodeSummary} />
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
<MonitoringTimeseriesContainer
series={metric}
{...props}
/>
<EuiSpacer size="m"/>
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};

View file

@ -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 React from 'react';
import {
EuiPage,
EuiPageContent,
EuiPageBody,
EuiSpacer,
EuiFlexGrid,
EuiFlexItem,
} from '@elastic/eui';
import { NodeDetailStatus } from '../node_detail_status';
import { MonitoringTimeseriesContainer } from '../../chart';
import { ShardAllocation } from '../shard_allocation/shard_allocation';
export const Node = ({
nodeSummary,
metrics,
scope,
kbnUrl,
...props
}) => {
const metricsToShow = [
metrics.node_jvm_mem,
metrics.node_mem,
metrics.node_cpu_metric,
metrics.node_load_average,
metrics.node_latency,
metrics.node_segment_count,
];
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<NodeDetailStatus stats={nodeSummary} />
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
<MonitoringTimeseriesContainer
series={metric}
{...props}
/>
<EuiSpacer size="m"/>
</EuiFlexItem>
))}
</EuiFlexGrid>
<EuiSpacer size="m"/>
<ShardAllocation scope={scope} kbnUrl={kbnUrl}/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};

View file

@ -26,8 +26,12 @@ function NodeDetailStatusUI({ stats, intl }) {
const metrics = [
{
label: intl.formatMessage({
id: 'xpack.monitoring.elasticsearch.nodeDetailStatus.transportAddress',
defaultMessage: 'Transport Address',
}),
value: transportAddress,
dataTestSubj: 'transportAddress'
'data-test-subj': 'transportAddress'
},
{
label: intl.formatMessage({
@ -36,7 +40,7 @@ function NodeDetailStatusUI({ stats, intl }) {
javaVirtualMachine: 'JVM'
}),
value: formatMetric(usedHeap, '0,0.[00]', '%', { prependSpace: false }),
dataTestSubj: 'jvmHeap'
'data-test-subj': 'jvmHeap'
},
{
label: intl.formatMessage({
@ -44,7 +48,7 @@ function NodeDetailStatusUI({ stats, intl }) {
defaultMessage: 'Free Disk Space',
}),
value: formatMetric(freeSpace, '0.0 b'),
dataTestSubj: 'freeDiskSpace'
'data-test-subj': 'freeDiskSpace'
},
{
label: intl.formatMessage({
@ -52,7 +56,7 @@ function NodeDetailStatusUI({ stats, intl }) {
defaultMessage: 'Documents',
}),
value: formatMetric(documents, '0.[0]a'),
dataTestSubj: 'documentCount'
'data-test-subj': 'documentCount'
},
{
label: intl.formatMessage({
@ -60,7 +64,7 @@ function NodeDetailStatusUI({ stats, intl }) {
defaultMessage: 'Data',
}),
value: formatMetric(dataSize, '0.0 b'),
dataTestSubj: 'dataSize'
'data-test-subj': 'dataSize'
},
{
label: intl.formatMessage({
@ -68,7 +72,7 @@ function NodeDetailStatusUI({ stats, intl }) {
defaultMessage: 'Indices',
}),
value: formatMetric(indexCount, 'int_commas'),
dataTestSubj: 'indicesCount'
'data-test-subj': 'indicesCount'
},
{
label: intl.formatMessage({
@ -76,7 +80,7 @@ function NodeDetailStatusUI({ stats, intl }) {
defaultMessage: 'Shards',
}),
value: formatMetric(totalShards, 'int_commas'),
dataTestSubj: 'shardsCount'
'data-test-subj': 'shardsCount'
},
{
label: intl.formatMessage({
@ -84,7 +88,7 @@ function NodeDetailStatusUI({ stats, intl }) {
defaultMessage: 'Type',
}),
value: nodeTypeLabel,
dataTestSubj: 'nodeType'
'data-test-subj': 'nodeType'
}
];

View file

@ -1,75 +1,103 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Node Listing Metric Cell should format N/A as the metric for an offline node 1`] = `
<td
class="kuiTableRowCell"
<div
class="monTableCell__number monTableCell__offline"
>
<div
class="kuiTableRowCell__liner"
>
<div
class="monTableCell__number monTableCell__offline"
>
N/A
</div>
</div>
</td>
N/A
</div>
`;
exports[`Node Listing Metric Cell should format a non-percentage metric 1`] = `
<td
class="kuiTableRowCell"
<div
class="euiStat euiStat--leftAligned"
>
<div
class="kuiTableRowCell__liner"
class="euiText euiText--small euiStat__description"
>
<p />
</div>
<p
class="euiTitle euiTitle--large euiStat__title"
/>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
intl="[object Object]"
>
<div
class="monTableCell__metricCellMetric"
class="euiFlexItem euiFlexItem--flexGrowZero"
>
206.3 GB
<h4
class="euiTitle euiTitle--medium"
>
206.3 GB 
<span
class="fa fa-long-arrow-down"
/>
</h4>
</div>
<span
class="monTableCell__metricCellSlopeArrow fa fa-long-arrow-down"
/>
<div
class="monTableCell__metricCellMixMax"
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<div>
<div
class="euiText euiText--extraSmall"
>
206.5 GB max
</div>
<div>
<div
class="euiText euiText--extraSmall"
>
206.3 GB min
</div>
</div>
</div>
</td>
<p />
</div>
`;
exports[`Node Listing Metric Cell should format a percentage metric 1`] = `
<td
class="kuiTableRowCell"
<div
class="euiStat euiStat--leftAligned"
>
<div
class="kuiTableRowCell__liner"
class="euiText euiText--small euiStat__description"
>
<p />
</div>
<p
class="euiTitle euiTitle--large euiStat__title"
/>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
intl="[object Object]"
>
<div
class="monTableCell__metricCellMetric"
class="euiFlexItem euiFlexItem--flexGrowZero"
>
0%
<h4
class="euiTitle euiTitle--medium"
>
0% 
<span
class="fa fa-long-arrow-down"
/>
</h4>
</div>
<span
class="monTableCell__metricCellSlopeArrow fa fa-long-arrow-down"
/>
<div
class="monTableCell__metricCellMixMax"
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<div>
<div
class="euiText euiText--extraSmall"
>
2% max
</div>
<div>
<div
class="euiText euiText--extraSmall"
>
0% min
</div>
</div>
</div>
</td>
<p />
</div>
`;

View file

@ -7,19 +7,13 @@
import React from 'react';
import { get } from 'lodash';
import { formatMetric } from '../../../lib/format_number';
import { KuiTableRowCell } from '@kbn/ui-framework/components';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiStat, EuiText, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
function OfflineCell() {
return (
<KuiTableRowCell>
<div className="monTableCell__number monTableCell__offline">
<FormattedMessage
id="xpack.monitoring.elasticsearch.nodes.noDataInCellLabel"
defaultMessage="N/A"
/>
</div>
</KuiTableRowCell>
<div className="monTableCell__number monTableCell__offline">
N/A
</div>
);
}
@ -43,20 +37,30 @@ function MetricCell({ isOnline, metric = {}, isPercent, ...props }) {
const format = get(metric, 'metric.format');
return (
<KuiTableRowCell>
<div className="monTableCell__metricCellMetric" data-test-subj={props['data-test-subj']}>
{ metricVal(lastVal, format, isPercent) }
</div>
<span className={`monTableCell__metricCellSlopeArrow fa fa-long-arrow-${getSlopeArrow(slope)}`} />
<div className="monTableCell__metricCellMixMax">
<div>
{ metricVal(maxVal, format, isPercent) + ' max' }
</div>
<div>
{ metricVal(minVal, format, isPercent) + ' min' }
</div>
</div>
</KuiTableRowCell>
<EuiStat
description=""
title={(
<EuiFlexGroup alignItems="center" {...props}>
<EuiFlexItem grow={false}>
<EuiTitle size="m">
<h4>
{ metricVal(lastVal, format, isPercent) }
&nbsp;
<span className={`fa fa-long-arrow-${getSlopeArrow(slope)}`} />
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs">
{ metricVal(maxVal, format, isPercent) + ' max' }
</EuiText>
<EuiText size="xs">
{ metricVal(minVal, format, isPercent) + ' min' }
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
)}
/>
);
}

View file

@ -4,227 +4,228 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import { get } from 'lodash';
import { SORT_ASCENDING } from '../../../../common/constants';
import React from 'react';
import { NodeStatusIcon } from '../node';
import { extractIp } from '../../../lib/extract_ip'; // TODO this is only used for elasticsearch nodes summary / node detail, so it should be moved to components/elasticsearch/nodes/lib
import { ClusterStatus } from '../cluster_status';
import { MonitoringTable } from '../../table';
import { EuiMonitoringTable } from '../../table';
import { MetricCell, OfflineCell } from './cells';
import { EuiLink, EuiToolTip } from '@elastic/eui';
import { KuiTableRowCell, KuiTableRow } from '@kbn/ui-framework/components';
import {
EuiLink,
EuiToolTip,
EuiSpacer,
EuiPage,
EuiPageContent,
EuiPageBody,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { injectI18n } from '@kbn/i18n/react';
const filterFields = ['name'];
const getColumns = showCgroupMetricsElasticsearch => {
const cols = [];
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.nameColumnTitle', {
defaultMessage: 'Name',
}),
sortKey: 'name',
sortOrder: SORT_ASCENDING
});
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumnTitle', {
defaultMessage: 'Status',
}),
sortKey: 'isOnline'
});
const cpuUsageColumnTitle = i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle', {
defaultMessage: 'CPU Usage',
});
cols.push({
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.nameColumnTitle', {
defaultMessage: 'Name',
}),
field: 'name',
sortable: true,
render: (value, node) => (
<div>
<div className="monTableCell__name">
<EuiToolTip
position="bottom"
content={node.nodeTypeLabel}
>
<span className={`fa ${node.nodeTypeClass}`} />
</EuiToolTip>
&nbsp;
<span data-test-subj="name">
<EuiLink
href={`#/elasticsearch/nodes/${node.resolver}`}
data-test-subj={`nodeLink-${node.resolver}`}
>
{value}
</EuiLink>
</span>
</div>
<div className="monTableCell__transportAddress">
{extractIp(node.transport_address)}
</div>
</div>
)
});
cols.push({
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumnTitle', {
defaultMessage: 'Status',
}),
field: 'isOnline',
sortable: true,
render: value => {
const status = value ? 'Online' : 'Offline';
return (
<div className="monTableCell__status">
<NodeStatusIcon
isOnline={value}
status={status}
/>{' '}
{status}
</div>
);
}
});
if (showCgroupMetricsElasticsearch) {
cols.push({
title: cpuUsageColumnTitle,
sortKey: 'node_cgroup_quota'
name: cpuUsageColumnTitle,
field: 'node_cgroup_quota',
sortable: true,
render: (value, node) => (
<MetricCell
isOnline={node.isOnline}
metric={value}
isPercent={true}
data-test-subj="cpuQuota"
/>
)
});
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuThrottlingColumnTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuThrottlingColumnTitle', {
defaultMessage: 'CPU Throttling',
}),
sortKey: 'node_cgroup_throttled'
field: 'node_cgroup_throttled',
sortable: true,
render: (value, node) => (
<MetricCell
isOnline={node.isOnline}
metric={value}
isPercent={false}
data-test-subj="cpuThrottled"
/>
)
});
} else {
cols.push({
title: cpuUsageColumnTitle,
sortKey: 'node_cpu_utilization'
name: cpuUsageColumnTitle,
field: 'node_cpu_utilization',
sortable: true,
render: (value, node) => (
<MetricCell
isOnline={node.isOnline}
metric={value}
isPercent={true}
data-test-subj="cpuUsage"
/>
)
});
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.loadAverageColumnTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.loadAverageColumnTitle', {
defaultMessage: 'Load Average',
}),
sortKey: 'node_load_average'
field: 'node_load_average',
sortable: true,
render: (value, node) => (
<MetricCell
isOnline={node.isOnline}
metric={value}
isPercent={false}
data-test-subj="loadAverage"
/>
)
});
}
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.jvmMemoryColumnTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.jvmMemoryColumnTitle', {
defaultMessage: '{javaVirtualMachine} Memory',
values: {
javaVirtualMachine: 'JVM'
}
}),
sortKey: 'node_jvm_mem_percent'
field: 'node_jvm_mem_percent',
sortable: true,
render: (value, node) => (
<MetricCell
isOnline={node.isOnline}
metric={value}
isPercent={true}
data-test-subj="jvmMemory"
/>
)
});
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.diskFreeSpaceColumnTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.diskFreeSpaceColumnTitle', {
defaultMessage: 'Disk Free Space',
}),
sortKey: 'node_free_space'
field: 'node_free_space',
sortable: true,
width: '300px',
render: (value, node) => (
<MetricCell
isOnline={node.isOnline}
metric={value}
isPercent={false}
data-test-subj="diskFreeSpace"
/>
)
});
cols.push({
title: i18n.translate('xpack.monitoring.elasticsearch.nodes.shardsColumnTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.shardsColumnTitle', {
defaultMessage: 'Shards',
}),
sortKey: 'shardCount'
field: 'shardCount',
sortable: true,
render: (value, node) => {
return node.isOnline ? (
<div className="monTableCell__number" data-test-subj="shards">
{value}
</div>
) : <OfflineCell/>;
}
});
return cols;
};
const nodeRowFactory = showCgroupMetricsElasticsearch => {
return class NodeRow extends React.Component {
constructor(props) {
super(props);
}
isOnline() {
return this.props.isOnline === true;
}
getCpuComponents() {
const isOnline = this.isOnline();
if (showCgroupMetricsElasticsearch) {
return [
<MetricCell
key="cpuCol1"
isOnline={isOnline}
metric={get(this.props, 'node_cgroup_quota')}
isPercent={true}
data-test-subj="cpuQuota"
/>,
<MetricCell
key="cpuCol2"
isOnline={isOnline}
metric={get(this.props, 'node_cgroup_throttled')}
isPercent={false}
data-test-subj="cpuThrottled"
/>
];
}
return [
<MetricCell
key="cpuCol1"
isOnline={isOnline}
metric={get(this.props, 'node_cpu_utilization')}
isPercent={true}
data-test-subj="cpuUsage"
/>,
<MetricCell
key="cpuCol2"
isOnline={isOnline}
metric={get(this.props, 'node_load_average')}
isPercent={false}
data-test-subj="loadAverage"
/>
];
}
getShardCount() {
if (this.isOnline()) {
return (
<KuiTableRowCell>
<div className="monTableCell__number" data-test-subj="shards">
{get(this.props, 'shardCount')}
</div>
</KuiTableRowCell>
);
}
return <OfflineCell />;
}
render() {
const isOnline = this.isOnline();
const status = this.props.isOnline ? 'Online' : 'Offline';
return (
<KuiTableRow>
<KuiTableRowCell>
<div className="monTableCell__name">
<EuiToolTip
position="bottom"
content={this.props.nodeTypeLabel}
>
<span className={`fa ${this.props.nodeTypeClass}`} />
</EuiToolTip>
&nbsp;
<span data-test-subj="name">
<EuiLink
href={`#/elasticsearch/nodes/${this.props.resolver}`}
data-test-subj={`nodeLink-${this.props.resolver}`}
>
{this.props.name}
</EuiLink>
</span>
</div>
<div className="monTableCell__transportAddress">
{extractIp(this.props.transport_address)}
</div>
</KuiTableRowCell>
<KuiTableRowCell>
<div className="monTableCell__status">
<NodeStatusIcon
isOnline={this.props.isOnline}
status={status}
/>{' '}
{status}
</div>
</KuiTableRowCell>
{this.getCpuComponents()}
<MetricCell
isOnline={isOnline}
metric={get(this.props, 'node_jvm_mem_percent')}
isPercent={true}
data-test-subj="jvmMemory"
/>
<MetricCell
isOnline={isOnline}
metric={get(this.props, 'node_free_space')}
isPercent={false}
data-test-subj="diskFreeSpace"
/>
{this.getShardCount()}
</KuiTableRow>
);
}
};
};
function ElasticsearchNodesUI({ clusterStatus, nodes, showCgroupMetricsElasticsearch, intl, ...props }) {
const columns = getColumns(showCgroupMetricsElasticsearch);
const { sorting, pagination, onTableChange } = props;
return (
<Fragment>
<ClusterStatus stats={clusterStatus} />
<MonitoringTable
className="elasticsearchNodesTable"
rows={nodes}
pageIndex={props.pageIndex}
filterText={props.filterText}
sortKey={props.sortKey}
sortOrder={props.sortOrder}
onNewState={props.onNewState}
placeholder={intl.formatMessage({
id: 'xpack.monitoring.elasticsearch.nodes.monitoringTablePlaceholder',
defaultMessage: 'Filter Nodes…',
})}
filterFields={filterFields}
columns={columns}
rowComponent={nodeRowFactory(showCgroupMetricsElasticsearch)}
/>
</Fragment>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<ClusterStatus stats={clusterStatus} />
<EuiSpacer size="m"/>
<EuiMonitoringTable
className="elasticsearchNodesTable"
rows={nodes}
columns={columns}
sorting={sorting}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: intl.formatMessage({
id: 'xpack.monitoring.elasticsearch.nodes.monitoringTablePlaceholder',
defaultMessage: 'Filter Nodes…',
}),
},
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}

View file

@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import React from 'react';
import { ClusterStatus } from '../cluster_status';
import { ShardActivity } from '../shard_activity';
import { MonitoringTimeseriesContainer } from '../../chart';
import { EuiPage, EuiFlexGrid, EuiFlexItem, EuiSpacer, EuiPageBody } from '@elastic/eui';
import { EuiPage, EuiFlexGrid, EuiFlexItem, EuiSpacer, EuiPageBody, EuiPageContent } from '@elastic/eui';
export function ElasticsearchOverview({
clusterStatus,
@ -24,10 +24,11 @@ export function ElasticsearchOverview({
];
return (
<Fragment>
<ClusterStatus stats={clusterStatus} />
<EuiPage style={{ backgroundColor: 'white' }}>
<EuiPageBody>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<ClusterStatus stats={clusterStatus} />
<EuiSpacer/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
@ -40,8 +41,8 @@ export function ElasticsearchOverview({
))}
</EuiFlexGrid>
<ShardActivity data={shardActivity} {...props} />
</EuiPageBody>
</EuiPage>
</Fragment>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import React from 'react';
import { EuiLink } from '@elastic/eui';
import { Snapshot } from './snapshot';
import { FormattedMessage } from '@kbn/i18n/react';
@ -13,7 +13,7 @@ export const RecoveryIndex = (props) => {
const { name, shard, relocationType } = props;
return (
<Fragment>
<div>
<EuiLink href={`#/elasticsearch/indices/${name}`}>{name}</EuiLink><br />
<FormattedMessage
id="xpack.monitoring.elasticsearch.shardActivity.recoveryIndex.shardDescription"
@ -32,6 +32,6 @@ export const RecoveryIndex = (props) => {
<div>
<Snapshot {...props} />
</div>
</Fragment>
</div>
);
};

View file

@ -6,13 +6,7 @@
import React, { Fragment } from 'react';
import { EuiText, EuiTitle, EuiLink, EuiSpacer, EuiSwitch } from '@elastic/eui';
import {
KuiTableRowCell,
KuiTableRow,
KuiToolBarSection,
KuiToolBarText
} from '@kbn/ui-framework/components';
import { MonitoringTable } from 'plugins/monitoring/components/table';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { RecoveryIndex } from './recovery_index';
import { TotalTime } from './total_time';
import { SourceDestination } from './source_destination';
@ -23,89 +17,55 @@ import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
const columns = [
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.indexTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.indexTitle', {
defaultMessage: 'Index'
}),
sortKey: null
field: 'name',
render: (_name, shard) => <RecoveryIndex {...shard} />
},
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.stageTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.stageTitle', {
defaultMessage: 'Stage'
}),
sortKey: null
field: 'stage'
},
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.totalTimeTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.totalTimeTitle', {
defaultMessage: 'Total Time'
}),
sortKey: null
field: null,
render: shard => <TotalTime {...shard} />
},
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.sourceDestinationTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.sourceDestinationTitle', {
defaultMessage: 'Source / Destination'
}),
sortKey: null
field: null,
render: shard => <SourceDestination {...shard} />
},
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.filesTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.filesTitle', {
defaultMessage: 'Files'
}),
sortKey: null
field: null,
render: shard => <FilesProgress {...shard} />
},
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.bytesTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.bytesTitle', {
defaultMessage: 'Bytes'
}),
sortKey: null
field: null,
render: shard => <BytesProgress {...shard} />
},
{
title: i18n.translate('xpack.monitoring.kibana.shardActivity.translogTitle', {
name: i18n.translate('xpack.monitoring.kibana.shardActivity.translogTitle', {
defaultMessage: 'Translog'
}),
sortKey: null
field: null,
render: shard => <TranslogProgress {...shard} />
}
];
const ActivityRow = props => (
<KuiTableRow>
<KuiTableRowCell>
<RecoveryIndex {...props} />
</KuiTableRowCell>
<KuiTableRowCell>{props.stage}</KuiTableRowCell>
<KuiTableRowCell>
<TotalTime {...props} />
</KuiTableRowCell>
<KuiTableRowCell>
<SourceDestination {...props} />
</KuiTableRowCell>
<KuiTableRowCell>
<FilesProgress {...props} />
</KuiTableRowCell>
<KuiTableRowCell>
<BytesProgress {...props} />
</KuiTableRowCell>
<KuiTableRowCell>
<TranslogProgress {...props} />
</KuiTableRowCell>
</KuiTableRow>
);
const ToggleCompletedSwitch = ({ toggleHistory, showHistory }) => (
<KuiToolBarSection>
<KuiToolBarText>
<EuiSwitch
id="monitoring_completed_recoveries"
label={(
<FormattedMessage
id="xpack.monitoring.elasticsearch.shardActivity.completedRecoveriesLabel"
defaultMessage="Completed recoveries"
/>
)}
onChange={toggleHistory}
checked={showHistory}
/>
</KuiToolBarText>
</KuiToolBarSection>
);
class ShardActivityUI extends React.Component {
constructor(props) {
@ -146,18 +106,20 @@ class ShardActivityUI extends React.Component {
render() {
// data prop is an array of table row data, or null (which triggers no data message)
const { data: rawData } = this.props;
const {
data: rawData,
sorting,
pagination,
onTableChange,
toggleShardActivityHistory,
showShardActivityHistory
} = this.props;
if (rawData === null) {
return null;
}
const rows = rawData.map(parseProps);
const renderToolBarSection = props => (
<ToggleCompletedSwitch
toggleHistory={props.toggleShardActivityHistory}
showHistory={props.showShardActivityHistory}
/>
);
const rows = rawData.map(parseProps);
return (
<Fragment>
@ -172,15 +134,27 @@ class ShardActivityUI extends React.Component {
</EuiTitle>
</EuiText>
<EuiSpacer />
<MonitoringTable
<EuiSwitch
id="monitoring_completed_recoveries"
label={(
<FormattedMessage
id="xpack.monitoring.elasticsearch.shardActivity.completedRecoveriesLabel"
defaultMessage="Completed recoveries"
/>
)}
onChange={toggleShardActivityHistory}
checked={showShardActivityHistory}
/>
<EuiSpacer/>
<EuiMonitoringTable
className="esShardActivityTable"
rows={rows}
renderToolBarSections={renderToolBarSection}
columns={columns}
rowComponent={ActivityRow}
getNoDataMessage={this.getNoDataMessage}
alwaysShowPageControls={true}
{...this.props}
message={this.getNoDataMessage()}
sorting={sorting}
search={false}
pagination={pagination}
onTableChange={onTableChange}
/>
</Fragment>
);

View file

@ -80,44 +80,18 @@ monitoring-shard-allocation {
.shard {
align-self: center;
padding: 5px 7px;
background-color: $euiColorPrimary;
font: 10px sans-serif;
border-left: 1px solid $euiColorEmptyShade;
position: relative;
color: $euiColorGhost;
.shard-tooltip {
padding: 5px;
bottom: 25px;
left: 0;
background-color: $euiColorLightShade;
position: absolute;
color: $euiColorDarkShade;
border: 1px solid $euiColorLightShade;
white-space: nowrap;
}
&.replica {
background-color: tintOrShade($euiColorPrimary, 15%, 15%);
}
&.unassigned {
background-color: $euiColorMediumShade !important;
color: $euiColorFullShade;
}
&.emergency {
background-color: $euiColorDanger !important;
color: $euiColorFullShade;
}
&.relocating {
background-color: $euiColorVis3;
}
&.initializing {
background-color: tintOrShade($euiColorVis3, 15%, 15%);
}
}
.legend {

View file

@ -9,8 +9,8 @@
import { get, sortBy } from 'lodash';
import React from 'react';
import { Shard } from './shard';
import { calculateClass } from '../lib/calculateClass';
import { generateQueryAndLink } from '../lib/generateQueryAndLink';
import { calculateClass } from '../lib/calculate_class';
import { generateQueryAndLink } from '../lib/generate_query_and_link';
import {
EuiKeyboardAccessible,
} from '@elastic/eui';

View file

@ -7,8 +7,8 @@
import React from 'react';
import { TableHead } from './tableHead';
import { TableBody } from './tableBody';
import { TableHead } from './table_head';
import { TableBody } from './table_body';
import { i18n } from '@kbn/i18n';
export class ClusterView extends React.Component {
@ -24,7 +24,7 @@ export class ClusterView extends React.Component {
this.state = {
labels: props.scope.labels || [],
showing: props.scope.showing || [],
shardStats: props.shardStats,
shardStats: props.scope.pageData.shardStats,
showSystemIndices: props.showSystemIndices,
toggleShowSystemIndices: props.toggleShowSystemIndices,
angularChangeUrl: (url) => {
@ -45,7 +45,7 @@ export class ClusterView extends React.Component {
componentWillMount() {
this.props.scope.$watch('showing', this.setShowing);
this.props.scope.$watch('shardStats', this.setShardStats);
this.props.scope.$watch(() => this.props.scope.pageData.shardStats, this.setShardStats);
}
hasUnassigned = () => {

View file

@ -7,9 +7,32 @@
import React from 'react';
import { calculateClass } from '../lib/calculateClass';
import { calculateClass } from '../lib/calculate_class';
import { vents } from '../lib/vents';
import { i18n } from '@kbn/i18n';
import { EuiTextColor } from '@elastic/eui';
function getColor(classes) {
return classes.split(' ').reduce((color, cls) => {
if (color) {
return color;
}
switch (cls) {
case 'primary':
return 'ghost';
case 'replica':
return 'secondary';
case 'relocation':
return 'accent';
case 'initializing':
return 'default';
case 'emergency':
case 'unassigned':
return 'danger';
}
}, null);
}
export class Shard extends React.Component {
static displayName = i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.shardDisplayName', {
@ -71,6 +94,7 @@ export class Shard extends React.Component {
}
const classes = calculateClass(shard);
const color = getColor(classes);
const classification = classes + ' ' + shard.shard;
// data attrs for automated testing verification
@ -83,7 +107,9 @@ export class Shard extends React.Component {
data-shard-classification={classification}
data-test-subj="shardIcon"
>
{tooltip}{shard.shard}
<EuiTextColor color={color}>
{tooltip}{shard.shard}
</EuiTextColor>
</div>
);
}

View file

@ -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 { ShardAllocation } from './shard_allocation';

View file

@ -0,0 +1,91 @@
/*
* 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 { EuiTitle, EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { ClusterView } from './components/cluster_view';
export const ShardAllocation = ({
scope,
kbnUrl,
type,
shardStats,
}) => {
const types = [
{
label: i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.primaryLabel', {
defaultMessage: 'Primary'
}),
color: 'primary'
},
{
label: i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.replicaLabel', {
defaultMessage: 'Replica'
}),
color: 'secondary'
},
{
label: i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.relocatingLabel', {
defaultMessage: 'Relocating'
}),
color: 'accent'
},
{
label: i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.initializingLabel', {
defaultMessage: 'Initializing'
}),
color: 'default'
},
{
label: i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel', {
defaultMessage: 'Unassigned Primary'
}),
color: 'danger'
},
{
label: i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel', {
defaultMessage: 'Unassigned Replica'
}),
color: 'warning'
},
];
return (
<div className="monCluster">
<EuiTitle>
<h1>
<FormattedMessage
id="xpack.monitoring.elasticsearch.shardAllocation.shardLegendTitle"
defaultMessage="Shard Legend"
/>
</h1>
</EuiTitle>
<EuiSpacer size="xs"/>
<EuiFlexGroup wrap responsive={false} gutterSize="xs">
{
types.map(type => (
<EuiFlexItem grow={false} key={type.label}>
<EuiBadge color={type.color}>
{type.label}
</EuiBadge>
</EuiFlexItem>
))
}
</EuiFlexGroup>
<EuiSpacer size="s"/>
<ClusterView
scope={scope}
shardStats={shardStats}
kbnUrl={kbnUrl}
showSystemIndices={scope.showSystemIndices}
toggleShowSystemIndices={scope.toggleShowSystemIndices}
type={type}
/>
</div>
);
};

View file

@ -7,7 +7,7 @@
import _ from 'lodash';
import { hasPrimaryChildren } from '../lib/hasPrimaryChildren';
import { hasPrimaryChildren } from '../lib/has_primary_children';
import { decorateShards } from '../lib/decorate_shards';
export function nodesByIndices() {

View file

@ -28,7 +28,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Instances'
}),
value: instances,
dataTestSubj: 'instances'
'data-test-subj': 'instances'
},
{
label: intl.formatMessage({
@ -36,7 +36,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Memory'
}),
value: formatMetric(memSize, 'byte') + ' / ' + formatMetric(memLimit, 'byte'),
dataTestSubj: 'memory'
'data-test-subj': 'memory'
},
{
label: intl.formatMessage({
@ -44,7 +44,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Requests'
}),
value: requests,
dataTestSubj: 'requests'
'data-test-subj': 'requests'
},
{
label: intl.formatMessage({
@ -52,7 +52,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Connections'
}),
value: connections,
dataTestSubj: 'connections'
'data-test-subj': 'connections'
},
{
label: intl.formatMessage({
@ -60,7 +60,7 @@ function ClusterStatusUI({ stats, intl }) {
defaultMessage: 'Max. Response Time'
}),
value: formatMetric(maxResponseTime, '0', 'ms'),
dataTestSubj: 'maxResponseTime'
'data-test-subj': 'maxResponseTime'
}
];

View file

@ -22,7 +22,7 @@ function DetailStatusUI({ stats, intl }) {
const metrics = [
{
value: transportAddress,
dataTestSubj: 'transportAddress'
'data-test-subj': 'transportAddress'
},
{
label: intl.formatMessage({
@ -30,7 +30,7 @@ function DetailStatusUI({ stats, intl }) {
defaultMessage: 'OS Free Memory'
}),
value: formatMetric(osFreeMemory, 'byte'),
dataTestSubj: 'osFreeMemory'
'data-test-subj': 'osFreeMemory'
},
{
label: intl.formatMessage({
@ -38,7 +38,7 @@ function DetailStatusUI({ stats, intl }) {
defaultMessage: 'Version'
}),
value: version,
dataTestSubj: 'version'
'data-test-subj': 'version'
},
{
label: intl.formatMessage({
@ -46,7 +46,7 @@ function DetailStatusUI({ stats, intl }) {
defaultMessage: 'Uptime'
}),
value: formatMetric(uptime, 'time_since'),
dataTestSubj: 'uptime'
'data-test-subj': 'uptime'
}
];

View file

@ -24,28 +24,28 @@ function ClusterStatusUi({ stats, intl }) {
id: 'xpack.monitoring.logstash.clusterStatus.nodesLabel', defaultMessage: 'Nodes'
}),
value: nodeCount,
dataTestSubj: 'node_count'
'data-test-subj': 'node_count'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.clusterStatus.memoryLabel', defaultMessage: 'Memory'
}),
value: formatMetric(avgMemoryUsed, 'byte') + ' / ' + formatMetric(avgMemory, 'byte'),
dataTestSubj: 'memory_used'
'data-test-subj': 'memory_used'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.clusterStatus.eventsReceivedLabel', defaultMessage: 'Events Received'
}),
value: formatMetric(eventsInTotal, '0.[0]a'),
dataTestSubj: 'events_in_total'
'data-test-subj': 'events_in_total'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.clusterStatus.eventsEmittedLabel', defaultMessage: 'Events Emitted'
}),
value: formatMetric(eventsOutTotal, '0.[0]a'),
dataTestSubj: 'events_out_total'
'data-test-subj': 'events_out_total'
}
];

View file

@ -23,42 +23,42 @@ function DetailStatusUi({ stats, intl }) {
const firstMetrics = [
{
value: httpAddress,
dataTestSubj: 'httpAddress'
'data-test-subj': 'httpAddress'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.detailStatus.eventsReceivedLabel', defaultMessage: 'Events Received'
}),
value: formatMetric(events.in, '0.[0]a'),
dataTestSubj: 'eventsIn'
'data-test-subj': 'eventsIn'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.detailStatus.eventsEmittedLabel', defaultMessage: 'Events Emitted'
}),
value: formatMetric(events.out, '0.[0]a'),
dataTestSubj: 'eventsOut'
'data-test-subj': 'eventsOut'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.detailStatus.configReloadsLabel', defaultMessage: 'Config Reloads'
}),
value: reloads.successes,
dataTestSubj: 'numReloads'
'data-test-subj': 'numReloads'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.detailStatus.pipelineWorkersLabel', defaultMessage: 'Pipeline Workers'
}),
value: pipeline.workers,
dataTestSubj: 'pipelineWorkers'
'data-test-subj': 'pipelineWorkers'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.detailStatus.batchSizeLabel', defaultMessage: 'Batch Size'
}),
value: pipeline.batch_size,
dataTestSubj: 'pipelineBatchSize'
'data-test-subj': 'pipelineBatchSize'
}
];
@ -68,14 +68,14 @@ function DetailStatusUi({ stats, intl }) {
id: 'xpack.monitoring.logstash.detailStatus.versionLabel', defaultMessage: 'Version'
}),
value: version,
dataTestSubj: 'version'
'data-test-subj': 'version'
},
{
label: intl.formatMessage({
id: 'xpack.monitoring.logstash.detailStatus.uptimeLabel', defaultMessage: 'Uptime'
}),
value: formatMetric(uptime, 'time_since'),
dataTestSubj: 'uptime'
'data-test-subj': 'uptime'
}
];
@ -87,7 +87,7 @@ function DetailStatusUi({ stats, intl }) {
id: 'xpack.monitoring.logstash.detailStatus.queueTypeLabel', defaultMessage: 'Queue Type'
}),
value: queueType,
dataTestSubj: 'queueType'
'data-test-subj': 'queueType'
});
}
metrics.push(...lastMetrics);

View file

@ -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 { Listing } from './listing';

View file

@ -0,0 +1,117 @@
/*
* 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, { PureComponent } from 'react';
import { EuiPage, EuiLink, EuiPageBody, EuiPageContent, EuiSpacer } from '@elastic/eui';
import { formatPercentageUsage, formatNumber } from '../../../lib/format_number';
import { ClusterStatus } from '..//cluster_status';
import { EuiMonitoringTable } from '../../table';
import { injectI18n } from '@kbn/i18n/react';
class ListingUI extends PureComponent {
getColumns() {
const { kbnUrl, scope } = this.props.angular;
return [
{
name: 'Name',
field: 'logstash.name',
render: (name, node) => (
<div>
<div>
<EuiLink
onClick={() => {
scope.$evalAsync(() => {
kbnUrl.changePath(`/logstash/node/${node.logstash.uuid}`);
});
}}
>
{name}
</EuiLink>
</div>
<div>
{node.logstash.http_address}
</div>
</div>
)
},
{
name: 'CPU Usage',
field: 'process.cpu.percent',
render: value => formatPercentageUsage(value, 100)
},
{
name: 'Load Average',
field: 'os.cpu.load_average.1m',
render: value => formatNumber(value, '0.00')
},
{
name: 'JVM Heap Used',
field: 'jvm.mem.heap_used_percent',
render: value => formatPercentageUsage(value, 100)
},
{
name: 'Events Ingested',
field: 'events.out',
render: value => formatNumber(value, '0.[0]a')
},
{
name: 'Config Reloads',
render: node => (
<div>
<div>{ node.reloads.successes } successes</div>
<div>{ node.reloads.failures } failures</div>
</div>
)
},
{
name: 'Version',
field: 'logstash.version',
render: value => formatNumber(value)
}
];
}
render() {
const { data, stats, sorting, pagination, onTableChange, intl } = this.props;
const columns = this.getColumns();
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<ClusterStatus stats={stats} />
<EuiSpacer size="m"/>
<EuiMonitoringTable
className="logstashNodesTable"
rows={data}
columns={columns}
sorting={{
...sorting,
sort: {
...sorting.sort,
field: 'logstash.name'
}
}}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: intl.formatMessage({
id: 'xpack.monitoring.logstash.filterNodesPlaceholder',
defaultMessage: 'Filter Nodes…'
})
},
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
}
export const Listing = injectI18n(ListingUI);

View file

@ -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 { Node } from './node';

View file

@ -0,0 +1,47 @@
/*
* 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, { PureComponent } from 'react';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
import { DetailStatus } from '../detail_status';
import { MonitoringTimeseriesContainer } from '../../chart';
export class Node extends PureComponent {
render() {
const { stats, metrics, ...rest } = this.props;
const metricsToShow = [
metrics.logstash_events_input_rate,
metrics.logstash_jvm_usage,
metrics.logstash_events_output_rate,
metrics.logstash_node_cpu_metric,
metrics.logstash_events_latency,
metrics.logstash_os_load,
];
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<DetailStatus stats={stats}/>
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
<MonitoringTimeseriesContainer
series={metric}
{...rest}
/>
<EuiSpacer size="m"/>
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
}

View file

@ -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 { Overview } from './overview';

View file

@ -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 React, { PureComponent } from 'react';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
import { ClusterStatus } from '../cluster_status';
import { MonitoringTimeseriesContainer } from '../../chart';
export class Overview extends PureComponent {
render() {
const { stats, metrics } = this.props;
const metricsToShow = [
metrics.logstash_cluster_events_input_rate,
metrics.logstash_cluster_events_output_rate,
metrics.logstash_cluster_events_latency
];
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<ClusterStatus stats={stats} />
<EuiSpacer size="m"/>
<EuiFlexGrid columns={2} gutterSize="none">
{metricsToShow.map((metric, index) => (
<EuiFlexItem key={index} style={{ width: '50%' }}>
<MonitoringTimeseriesContainer
series={metric}
/>
<EuiSpacer size="m"/>
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
}

View file

@ -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 { PipelineListing } from './pipeline_listing';

View file

@ -0,0 +1,178 @@
/*
* 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, { Component } from 'react';
import moment from 'moment';
import { partialRight } from 'lodash';
import { EuiPage, EuiLink, EuiPageBody, EuiPageContent, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { formatMetric } from '../../../lib/format_number';
import { ClusterStatus } from '../cluster_status';
import { Sparkline } from 'plugins/monitoring/components/sparkline';
import { EuiMonitoringTable } from '../../table';
import { injectI18n } from '@kbn/i18n/react';
class PipelineListingUI extends Component {
tooltipXValueFormatter(xValue) {
return moment(xValue).format(this.props.dateFormat);
}
tooltipYValueFormatter(yValue, format, units) {
return formatMetric(yValue, format, units);
}
getColumns() {
const { onBrush } = this.props;
const { kbnUrl, scope } = this.props.angular;
return [
{
name: 'ID',
field: 'id',
sortable: true,
render: (id) => (
<EuiLink
data-test-subj="id"
onClick={() => {
scope.$evalAsync(() => {
kbnUrl.changePath(`/logstash/pipelines/${id}`);
});
}}
>
{id}
</EuiLink>
)
},
{
name: 'Events Emitted Rate',
field: 'latestThroughput',
sortable: true,
render: (value, pipeline) => {
const throughput = pipeline.metrics.throughput;
return (
<EuiFlexGroup
gutterSize="none"
alignItems="center"
>
<EuiFlexItem>
<Sparkline
series={throughput.data}
onBrush={onBrush}
tooltip={{
xValueFormatter: value => this.tooltipXValueFormatter(value),
yValueFormatter: partialRight(this.tooltipYValueFormatter, throughput.metric.format, throughput.metric.units)
}}
options={{ xaxis: throughput.timeRange }}
/>
</EuiFlexItem>
<EuiFlexItem
className="monTableCell__number"
data-test-subj="eventsEmittedRate"
>
{ formatMetric(value, '0.[0]a', throughput.metric.units) }
</EuiFlexItem>
</EuiFlexGroup>
);
}
},
{
name: 'Number of Nodes',
field: 'latestNodesCount',
sortable: true,
render: (value, pipeline) => {
const nodesCount = pipeline.metrics.nodesCount;
return (
<EuiFlexGroup
gutterSize="none"
alignItems="center"
>
<EuiFlexItem>
<Sparkline
series={nodesCount.data}
onBrush={onBrush}
tooltip={{
xValueFormatter: this.tooltipXValueFormatter,
yValueFormatter: partialRight(this.tooltipYValueFormatter, nodesCount.metric.format, nodesCount.metric.units)
}}
options={{ xaxis: nodesCount.timeRange }}
/>
</EuiFlexItem>
<EuiFlexItem
className="monTableCell__number"
data-test-subj="nodeCount"
>
{ formatMetric(value, '0a') }
</EuiFlexItem>
</EuiFlexGroup>
);
}
},
];
}
renderStats() {
if (this.props.statusComponent) {
const Component = this.props.statusComponent;
return (
<Component stats={this.props.stats}/>
);
}
return (
<ClusterStatus stats={this.props.stats}/>
);
}
render() {
const {
data,
sorting,
pagination,
onTableChange,
upgradeMessage,
className,
intl
} = this.props;
const columns = this.getColumns();
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
{this.renderStats()}
<EuiSpacer size="m"/>
<EuiMonitoringTable
className={className || 'logstashNodesTable'}
rows={data}
columns={columns}
sorting={{
...sorting,
sort: {
...sorting.sort,
field: 'id'
}
}}
message={upgradeMessage}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: intl.formatMessage({
id: 'xpack.monitoring.logstash.filterPipelinesPlaceholder',
defaultMessage: 'Filter Pipelines…'
})
},
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
}
export const PipelineListing = injectI18n(PipelineListingUI);

View file

@ -2,249 +2,226 @@
exports[`Summary Status Component should allow label to be optional 1`] = `
<div
class="monSummaryStatus"
role="status"
intl="[object Object]"
>
<div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<div
class="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
class="euiStat euiStat--leftAligned"
>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiText euiText--small euiStat__description"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="transportAddress"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
/>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<strong>
127.0.0.1:9300
</strong>
</div>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="documentCount"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Documents:
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<strong>
24.8k
</strong>
</div>
</div>
</div>
<p>
Status:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
Status:
<span
class="kuiStatusText"
>
<img
alt="Status: yellow"
data-test-subj="statusIcon"
src="../plugins/monitoring/icons/health-yellow.svg"
/>
</span>
 Yellow
</p>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="transportAddress"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
class="euiStat euiStat--leftAligned"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiText euiText--small euiStat__description"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero eui-textNoWrap"
>
Status:
<span
class="kuiStatusText"
>
<img
alt="Status: yellow"
data-test-subj="statusIcon"
src="../plugins/monitoring/icons/health-yellow.svg"
/>
</span>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Yellow
</div>
<p />
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
127.0.0.1:9300
</p>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="documentCount"
>
<div
class="euiStat euiStat--leftAligned"
>
<div
class="euiText euiText--small euiStat__description"
>
<p>
Documents:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
24.8k
</p>
</div>
</div>
</div>
<hr
class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginLarge"
/>
</div>
`;
exports[`Summary Status Component should allow status to be optional 1`] = `
<div
class="monSummaryStatus"
role="status"
intl="[object Object]"
>
<div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<div
class="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="freeDiskSpace"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
class="euiStat euiStat--leftAligned"
>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiText euiText--small euiStat__description"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="freeDiskSpace"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Free Disk Space:
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<strong>
173.9 GB
</strong>
</div>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="documentCount"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Documents:
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<strong>
24.8k
</strong>
</div>
</div>
</div>
<p>
Free Disk Space:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
173.9 GB
</p>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="documentCount"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
/>
class="euiStat euiStat--leftAligned"
>
<div
class="euiText euiText--small euiStat__description"
>
<p>
Documents:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
24.8k
</p>
</div>
</div>
</div>
<hr
class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginLarge"
/>
</div>
`;
exports[`Summary Status Component should render metrics in a summary bar 1`] = `
<div
class="monSummaryStatus"
role="status"
intl="[object Object]"
>
<div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<div
class="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
class="euiStat euiStat--leftAligned"
>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiText euiText--small euiStat__description"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="freeDiskSpace"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Free Disk Space:
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<strong>
173.9 GB
</strong>
</div>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="documentCount"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Documents:
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
<strong>
24.8k
</strong>
</div>
</div>
</div>
<p>
Status:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
Status:
<span
class="kuiStatusText"
>
<img
alt="Status: green"
data-test-subj="statusIcon"
src="../plugins/monitoring/icons/health-green.svg"
/>
</span>
 Green
</p>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="freeDiskSpace"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
class="euiStat euiStat--leftAligned"
>
<div
class="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
class="euiText euiText--small euiStat__description"
>
<div
class="euiFlexItem euiFlexItem--flexGrowZero eui-textNoWrap"
>
Status:
<span
class="kuiStatusText"
>
<img
alt="Status: green"
data-test-subj="statusIcon"
src="../plugins/monitoring/icons/health-green.svg"
/>
</span>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
>
Green
</div>
<p>
Free Disk Space:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
173.9 GB
</p>
</div>
</div>
<div
class="euiFlexItem euiFlexItem--flexGrowZero"
data-test-subj="documentCount"
>
<div
class="euiStat euiStat--leftAligned"
>
<div
class="euiText euiText--small euiStat__description"
>
<p>
Documents:
</p>
</div>
<p
class="euiTitle euiTitle--small euiStat__title"
>
24.8k
</p>
</div>
</div>
</div>
<hr
class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginLarge"
/>
</div>
`;

View file

@ -7,47 +7,29 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { isEmpty, capitalize } from 'lodash';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui';
import { StatusIcon } from '../status_icon/index.js';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
const wrapChild = ({ label, value, dataTestSubj }, index) => (
const wrapChild = ({ label, value, ...props }, index) => (
<EuiFlexItem
key={`summary-status-item-${index}`}
grow={false}
data-test-subj={dataTestSubj}
{...props}
>
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
{label ? label + ': ' : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<strong>{value}</strong>
</EuiFlexItem>
</EuiFlexGroup>
<EuiStat
title={value}
titleSize="s"
textAlign="left"
description={label ? `${label}:` : ''}
/>
</EuiFlexItem>
);
const DefaultIconComponent = ({ status }) => (
<Fragment>
<FormattedMessage
id="xpack.monitoring.summaryStatus.statusIconTitle"
defaultMessage="Status: {statusIcon}"
values={{
statusIcon: (
<StatusIcon
type={status.toUpperCase()}
label={i18n.translate('xpack.monitoring.summaryStatus.statusIconLabel', {
defaultMessage: 'Status: {status}',
values: {
status
}
})}
/>
)
}}
/>
Status: {(
<StatusIcon type={status.toUpperCase()} label={`Status: ${status}`} />
)}
</Fragment>
);
@ -57,33 +39,34 @@ const StatusIndicator = ({ status, isOnline, IconComponent }) => {
}
return (
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false} className="eui-textNoWrap">
<IconComponent status={status} isOnline={isOnline} />{' '}
</EuiFlexItem>
<EuiFlexItem grow={false}>
{capitalize(status)}
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem
key={`summary-status-item-status`}
grow={false}
>
<EuiStat
title={(
<Fragment>
<IconComponent status={status} isOnline={isOnline} />
&nbsp;
{capitalize(status)}
</Fragment>
)}
titleSize="s"
textAlign="left"
description="Status:"
/>
</EuiFlexItem>
);
};
// eslint-disable-next-line no-unused-vars
export function SummaryStatus({ metrics, status, isOnline, IconComponent = DefaultIconComponent, intl, ...props }) {
export function SummaryStatus({ metrics, status, isOnline, IconComponent = DefaultIconComponent, ...props }) {
return (
<div className="monSummaryStatus" role="status">
<div {...props}>
<EuiFlexGroup gutterSize="none" alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiFlexGroup>
{metrics.map(wrapChild)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<StatusIndicator status={status} IconComponent={IconComponent} isOnline={isOnline} />
</EuiFlexItem>
</EuiFlexGroup>
</div>
<div {...props}>
<EuiFlexGroup justifyContent="spaceBetween">
<StatusIndicator status={status} IconComponent={IconComponent} isOnline={isOnline} />
{metrics.map(wrapChild)}
</EuiFlexGroup>
<EuiHorizontalRule/>
</div>
);
}

View file

@ -15,12 +15,12 @@ describe('Summary Status Component', () => {
{
label: 'Free Disk Space',
value: '173.9 GB',
dataTestSubj: 'freeDiskSpace'
'data-test-subj': 'freeDiskSpace'
},
{
label: 'Documents',
value: '24.8k',
dataTestSubj: 'documentCount'
'data-test-subj': 'documentCount'
},
],
status: 'green'
@ -34,12 +34,12 @@ describe('Summary Status Component', () => {
metrics: [
{
value: '127.0.0.1:9300',
dataTestSubj: 'transportAddress'
'data-test-subj': 'transportAddress'
},
{
label: 'Documents',
value: '24.8k',
dataTestSubj: 'documentCount'
'data-test-subj': 'documentCount'
},
],
status: 'yellow'
@ -54,12 +54,12 @@ describe('Summary Status Component', () => {
{
label: 'Free Disk Space',
value: '173.9 GB',
dataTestSubj: 'freeDiskSpace'
'data-test-subj': 'freeDiskSpace'
},
{
label: 'Documents',
value: '24.8k',
dataTestSubj: 'documentCount'
'data-test-subj': 'documentCount'
},
]
};

View file

@ -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.
*/
import React from 'react';
import {
EuiInMemoryTable
} from '@elastic/eui';
export class EuiMonitoringTable extends React.PureComponent {
render() {
const {
rows: items,
search = {},
columns: _columns,
...props
} = this.props;
if (search.box && !search.box['data-test-subj']) {
search.box['data-test-subj'] = 'monitoringTableToolBar';
}
const columns = _columns.map(column => {
if (!column['data-test-subj']) {
column['data-test-subj'] = 'monitoringTableHasData';
}
return column;
});
return (
<div data-test-subj={`${this.props.className}Container`}>
<EuiInMemoryTable
items={items}
search={search}
columns={columns}
{...props}
/>
</div>
);
}
}

View file

@ -5,4 +5,5 @@
*/
export { MonitoringTable } from './table';
export { tableStorageGetter, tableStorageSetter } from './storage';
export { EuiMonitoringTable } from './eui_table';
export { tableStorageGetter, tableStorageSetter, euiTableStorageGetter, euiTableStorageSetter } from './storage';

View file

@ -33,3 +33,26 @@ export const tableStorageSetter = keyPrefix => {
return localStorageData;
};
};
export const euiTableStorageGetter = keyPrefix => {
return storage => {
const localStorageData = storage.get(STORAGE_KEY) || {};
const sort = get(localStorageData, [ keyPrefix, 'sort' ]);
const page = get(localStorageData, [ keyPrefix, 'page' ]);
return { page, sort };
};
};
export const euiTableStorageSetter = keyPrefix => {
return (storage, { sort, page }) => {
const localStorageData = storage.get(STORAGE_KEY) || {};
set(localStorageData, [ keyPrefix, 'sort' ], sort || undefined); // don`t store empty data
set(localStorageData, [ keyPrefix, 'page' ], page || undefined);
storage.set(STORAGE_KEY, localStorageData);
return localStorageData;
};
};

View file

@ -1,173 +0,0 @@
/*
* 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 { capitalize } from 'lodash';
import React from 'react';
import { render } from 'react-dom';
import { EuiIcon, EuiHealth } from '@elastic/eui';
import { uiModules } from 'ui/modules';
import { KuiTableRowCell, KuiTableRow } from '@kbn/ui-framework/components';
import { MonitoringTable } from 'plugins/monitoring/components/table';
import { CALCULATE_DURATION_SINCE, SORT_DESCENDING } from '../../../common/constants';
import { Tooltip } from 'plugins/monitoring/components/tooltip';
import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert';
import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity';
import { formatTimestampToDuration } from '../../../common/format_timestamp_to_duration';
import { formatDateTimeLocal } from '../../../common/formatting';
import { i18n } from '@kbn/i18n';
import { injectI18n, I18nProvider, FormattedMessage } from '@kbn/i18n/react';
const linkToCategories = {
'elasticsearch/nodes': i18n.translate('xpack.monitoring.alerts.esNodesCategoryLabel', {
defaultMessage: 'Elasticsearch Nodes',
}),
'elasticsearch/indices': i18n.translate('xpack.monitoring.alerts.esIndicesCategoryLabel', {
defaultMessage: 'Elasticsearch Indices',
}),
'kibana/instances': i18n.translate('xpack.monitoring.alerts.kibanaInstancesCategoryLabel', {
defaultMessage: 'Kibana Instances',
}),
'logstash/instances': i18n.translate('xpack.monitoring.alerts.logstashNodesCategoryLabel', {
defaultMessage: 'Logstash Nodes',
}),
};
const filterFields = [ 'message', 'severity_group', 'prefix', 'suffix', 'metadata.link', 'since', 'timestamp', 'update_timestamp' ];
const columns = [
{
title: i18n.translate('xpack.monitoring.alerts.statusColumnTitle', {
defaultMessage: 'Status',
}),
sortKey: 'metadata.severity',
sortOrder: SORT_DESCENDING
},
{
title: i18n.translate('xpack.monitoring.alerts.resolvedColumnTitle', {
defaultMessage: 'Resolved',
}),
sortKey: 'resolved_timestamp'
},
{
title: i18n.translate('xpack.monitoring.alerts.messageColumnTitle', {
defaultMessage: 'Message',
}),
sortKey: 'message'
},
{
title: i18n.translate('xpack.monitoring.alerts.categoryColumnTitle', {
defaultMessage: 'Category',
}),
sortKey: 'metadata.link'
},
{
title: i18n.translate('xpack.monitoring.alerts.lastCheckedColumnTitle', {
defaultMessage: 'Last Checked',
}),
sortKey: 'update_timestamp'
},
{
title: i18n.translate('xpack.monitoring.alerts.triggeredColumnTitle', {
defaultMessage: 'Triggered',
}),
sortKey: 'timestamp'
},
];
const alertRowFactory = (scope, kbnUrl) => {
return injectI18n(props => {
const changeUrl = target => {
scope.$evalAsync(() => {
kbnUrl.changePath(target);
});
};
const severityIcon = mapSeverity(props.metadata.severity);
const resolution = {
icon: null,
text: props.intl.formatMessage({ id: 'xpack.monitoring.alerts.notResolvedDescription',
defaultMessage: 'Not Resolved',
})
};
if (props.resolved_timestamp) {
resolution.text = props.intl.formatMessage({ id: 'xpack.monitoring.alerts.resolvedAgoDescription',
defaultMessage: '{duration} ago',
}, { duration: formatTimestampToDuration(props.resolved_timestamp, CALCULATE_DURATION_SINCE) }
);
} else {
resolution.icon = (
<EuiIcon
type="alert"
size="m"
aria-label={props.intl.formatMessage({ id: 'xpack.monitoring.alerts.notResolvedAriaLabel', defaultMessage: 'Not Resolved', })}
/>
);
}
return (
<KuiTableRow>
<KuiTableRowCell tabIndex="0">
<Tooltip text={severityIcon.title} placement="bottom" trigger="hover">
<EuiHealth color={severityIcon.color} data-test-subj="alertIcon" aria-label={severityIcon.title}>
{ capitalize(severityIcon.value) }
</EuiHealth>
</Tooltip>
</KuiTableRowCell>
<KuiTableRowCell tabIndex="0">
{ resolution.icon } { resolution.text }
</KuiTableRowCell>
<KuiTableRowCell tabIndex="0">
<FormattedAlert
prefix={props.prefix}
suffix={props.suffix}
message={props.message}
metadata={props.metadata}
changeUrl={changeUrl}
/>
</KuiTableRowCell>
<KuiTableRowCell tabIndex="0">
{ linkToCategories[props.metadata.link] ? linkToCategories[props.metadata.link] :
props.intl.formatMessage({ id: 'xpack.monitoring.alerts.generalCategoryLabel', defaultMessage: 'General', }) }
</KuiTableRowCell>
<KuiTableRowCell tabIndex="0">
{ formatDateTimeLocal(props.update_timestamp) }
</KuiTableRowCell>
<KuiTableRowCell tabIndex="0">
<FormattedMessage
id="xpack.monitoring.alerts.triggeredAgoDescription"
defaultMessage="{duration} ago"
values={{ duration: formatTimestampToDuration(props.timestamp, CALCULATE_DURATION_SINCE) }}
/>
</KuiTableRowCell>
</KuiTableRow>
);
});
};
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringClusterAlertsListing', (kbnUrl, i18n) => {
return {
restrict: 'E',
scope: { alerts: '=' },
link(scope, $el) {
const filterAlertsPlaceholder = i18n('xpack.monitoring.alerts.filterAlertsPlaceholder', { defaultMessage: 'Filter Alerts…' });
scope.$watch('alerts', (alerts = []) => {
const alertsTable = (
<I18nProvider>
<MonitoringTable
className="alertsTable"
rows={alerts}
placeholder={filterAlertsPlaceholder}
filterFields={filterFields}
columns={columns}
rowComponent={alertRowFactory(scope, kbnUrl)}
/>
</I18nProvider>
);
render(alertsTable, $el[0]);
});
}
};
});

View file

@ -7,15 +7,12 @@
import './main';
import './chart';
import './sparkline';
import './alerts';
import './cluster/overview';
import './cluster/listing';
import './elasticsearch/cluster_status';
import './elasticsearch/index_summary';
import './elasticsearch/node_summary';
import './elasticsearch/ml_job_listing';
import './elasticsearch/shard_allocation';
import './elasticsearch/shard_allocation/directives/clusterView';
import './logstash/cluster_status';
import './logstash/listing';
import './logstash/node_summary';

View file

@ -7,160 +7,40 @@
import React from 'react';
import { render } from 'react-dom';
import { uiModules } from 'ui/modules';
import { Stats } from 'plugins/monitoring/components/beats';
import { formatMetric } from 'plugins/monitoring/lib/format_number';
import { I18nProvider } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import {
SORT_ASCENDING,
SORT_DESCENDING,
TABLE_ACTION_UPDATE_FILTER,
} from '../../../../common/constants';
import {
KuiTableRowCell,
KuiTableRow
} from '@kbn/ui-framework/components';
import { MonitoringTable } from 'plugins/monitoring/components/table';
import {
EuiLink,
} from '@elastic/eui';
const filterFields = [ 'name', 'type', 'version', 'output' ];
const columns = [
{
title: i18n.translate('xpack.monitoring.beats.instances.nameTitle', { defaultMessage: 'Name' }),
sortKey: 'name',
sortOrder: SORT_ASCENDING
},
{
title: i18n.translate('xpack.monitoring.beats.instances.typeTitle', { defaultMessage: 'Type' }),
sortKey: 'type'
},
{
title: i18n.translate('xpack.monitoring.beats.instances.outputEnabledTitle', { defaultMessage: 'Output Enabled' }),
sortKey: 'output'
},
{
title: i18n.translate('xpack.monitoring.beats.instances.totalEventsRateTitle', { defaultMessage: 'Total Events Rate' }),
sortKey: 'total_events_rate',
secondarySortOrder: SORT_DESCENDING
},
{
title: i18n.translate('xpack.monitoring.beats.instances.bytesSentRateTitle', { defaultMessage: 'Bytes Sent Rate' }),
sortKey: 'bytes_sent_rate'
},
{
title: i18n.translate('xpack.monitoring.beats.instances.outputErrorsTitle', { defaultMessage: 'Output Errors' }),
sortKey: 'errors'
},
{
title: i18n.translate('xpack.monitoring.beats.instances.allocatedMemoryTitle', { defaultMessage: 'Allocated Memory' }),
sortKey: 'memory'
},
{
title: i18n.translate('xpack.monitoring.beats.instances.versionTitle', { defaultMessage: 'Version' }),
sortKey: 'version'
},
];
const beatRowFactory = (scope, kbnUrl) => {
return props => {
const goToBeat = uuid => () => {
scope.$evalAsync(() => {
kbnUrl.changePath(`/beats/beat/${uuid}`);
});
};
const applyFiltering = filterText => () => {
props.dispatchTableAction(TABLE_ACTION_UPDATE_FILTER, filterText);
};
return (
<KuiTableRow>
<KuiTableRowCell>
<div className="monTableCell__name">
<EuiLink
onClick={goToBeat(props.uuid)}
data-test-subj={`beatLink-${props.name}`}
>
{props.name}
</EuiLink>
</div>
</KuiTableRowCell>
<KuiTableRowCell>
<EuiLink
onClick={applyFiltering(props.type)}
>
{props.type}
</EuiLink>
</KuiTableRowCell>
<KuiTableRowCell>
{props.output}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.total_events_rate, '', '/s')}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.bytes_sent_rate, 'byte', '/s')}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.errors, '0')}
</KuiTableRowCell>
<KuiTableRowCell>
{formatMetric(props.memory, 'byte')}
</KuiTableRowCell>
<KuiTableRowCell>
<EuiLink
onClick={applyFiltering(props.version)}
>
{props.version}
</EuiLink>
</KuiTableRowCell>
</KuiTableRow>
);
};
};
import { Listing } from '../../../components/beats/listing/listing';
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringBeatsListing', (kbnUrl, i18n) => {
uiModule.directive('monitoringBeatsListing', (kbnUrl) => {
return {
restrict: 'E',
scope: {
data: '=',
pageIndex: '=',
filterText: '=',
sortKey: '=',
sortOrder: '=',
onNewState: '=',
sorting: '=',
pagination: '=paginationSettings',
onTableChange: '=',
},
link(scope, $el) {
scope.$watch('data', (data = {}) => {
const filterBeatsPlaceholder = i18n('xpack.monitoring.beats.filterBeatsPlaceholder', { defaultMessage: 'Filter Beats…' });
function renderReact(data) {
render((
<I18nProvider>
<div>
<Stats stats={data.stats} />
<div className="page-row">
<MonitoringTable
className="beatsTable"
rows={data.listing}
pageIndex={scope.pageIndex}
filterText={scope.filterText}
sortKey={scope.sortKey}
sortOrder={scope.sortOrder}
onNewState={scope.onNewState}
placeholder={filterBeatsPlaceholder}
filterFields={filterFields}
columns={columns}
rowComponent={beatRowFactory(scope, kbnUrl)}
/>
</div>
</div>
<Listing
stats={data.stats}
data={data.listing}
sorting={scope.sorting}
pagination={scope.pagination}
onTableChange={scope.onTableChange}
angular={{
kbnUrl,
scope,
}}
/>
</I18nProvider>
), $el[0]);
}
scope.$watch('data', (data = {}) => {
renderReact(data);
});
}
};
});

View file

@ -6,408 +6,403 @@
import React, { Fragment } from 'react';
import { render } from 'react-dom';
import { capitalize, get } from 'lodash';
import { capitalize, partial } from 'lodash';
import moment from 'moment';
import numeral from '@elastic/numeral';
import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
import {
KuiTableRowCell,
KuiTableRow
} from '@kbn/ui-framework/components';
import {
EuiHealth,
EuiLink,
EuiPage,
EuiPageBody,
EuiPageContent,
} from '@elastic/eui';
import { toastNotifications } from 'ui/notify';
import { MonitoringTable } from 'plugins/monitoring/components/table';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { Tooltip } from 'plugins/monitoring/components/tooltip';
import { AlertsIndicator } from 'plugins/monitoring/components/cluster/listing/alerts_indicator';
import { SORT_ASCENDING } from '../../../../common/constants';
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
const filterFields = [ 'cluster_name', 'status', 'license.type' ];
const columns = [
{
title: i18n.translate('xpack.monitoring.cluster.listing.nameColumnTitle', {
defaultMessage: 'Name',
}),
sortKey: 'cluster_name', sortOrder: SORT_ASCENDING
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.statusColumnTitle', {
defaultMessage: 'Status',
}),
sortKey: 'status'
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.nodesColumnTitle', {
defaultMessage: 'Nodes',
}),
sortKey: 'elasticsearch.cluster_stats.nodes.count.total'
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.indicesColumnTitle', {
defaultMessage: 'Indices',
}),
sortKey: 'elasticsearch.cluster_stats.indices.count'
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.dataColumnTitle', {
defaultMessage: 'Data',
}),
sortKey: 'elasticsearch.cluster_stats.indices.store.size_in_bytes'
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.logstashColumnTitle', {
defaultMessage: 'Logstash',
}),
sortKey: 'logstash.node_count'
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.kibanaColumnTitle', {
defaultMessage: 'Kibana',
}),
sortKey: 'kibana.count'
},
{
title: i18n.translate('xpack.monitoring.cluster.listing.licenseColumnTitle', {
defaultMessage: 'License',
}),
sortKey: 'license.type'
const IsClusterSupported = ({ isSupported, children }) => {
return isSupported ? children : '-';
};
/*
* This checks if alerts feature is supported via monitoring cluster
* license. If the alerts feature is not supported because the prod cluster
* license is basic, IsClusterSupported makes the status col hidden
* completely
*/
const IsAlertsSupported = (props) => {
const {
alertsMeta = { enabled: true },
clusterMeta = { enabled: true }
} = props.cluster.alerts;
if (alertsMeta.enabled && clusterMeta.enabled) {
return <span>{ props.children }</span>;
}
];
const clusterRowFactory = (scope, globalState, kbnUrl, showLicenseExpiration) => {
return class ClusterRow extends React.Component {
constructor(props) {
super(props);
}
const message = alertsMeta.message || clusterMeta.message;
return (
<Tooltip
text={message}
placement="bottom"
trigger="hover"
>
<EuiHealth color="subdued" data-test-subj="alertIcon">
N/A
</EuiHealth>
</Tooltip>
);
};
changeCluster() {
scope.$evalAsync(() => {
globalState.cluster_uuid = this.props.cluster_uuid;
globalState.ccs = this.props.ccs;
globalState.save();
kbnUrl.changePath('/overview');
});
}
licenseWarning({ title, text }) {
scope.$evalAsync(() => {
toastNotifications.addWarning({ title, text, 'data-test-subj': 'monitoringLicenseWarning' });
});
}
handleClickIncompatibleLicense() {
this.licenseWarning({
title: (
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.warningMessageTitle"
defaultMessage="You can't view the {clusterName} cluster"
values={{ clusterName: '"' + this.props.cluster_name + '"' }}
/>
),
text: (
<Fragment>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.noMultiClusterSupportMessage"
defaultMessage="The Basic license does not support multi-cluster monitoring."
/>
</p>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.infoMessage"
defaultMessage="Need to monitor multiple clusters? {getLicenseInfoLink} to enjoy multi-cluster monitoring."
values={{
getLicenseInfoLink: (
<a href="https://www.elastic.co/subscriptions/xpack" target="_blank">
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.getLicenseLinkLabel"
defaultMessage="Get a license with full functionality"
/>
</a>
)
}}
/>
</p>
</Fragment>
),
});
}
handleClickInvalidLicense() {
const licensingPath = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management/home`;
this.licenseWarning({
title: (
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.warningMessageTitle"
defaultMessage="You can't view the {clusterName} cluster"
values={{ clusterName: '"' + this.props.cluster_name + '"' }}
/>
),
text: (
<Fragment>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.invalidInfoMessage"
defaultMessage="The license information is invalid."
/>
</p>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.infoMessage"
defaultMessage="Need a license? {getBasicLicenseLink} or {getLicenseInfoLink} to enjoy multi-cluster monitoring."
values={{
getBasicLicenseLink: (
<a href={licensingPath}>
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.getBasicLicenseLinkLabel"
defaultMessage="Get a free Basic license"
/>
</a>
),
getLicenseInfoLink: (
<a href="https://www.elastic.co/subscriptions/xpack" target="_blank">
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.getLicenseLinkLabel"
defaultMessage="get a license with full functionality"
/>
</a>
)
}}
/>
</p>
</Fragment>
),
});
}
getClusterAction() {
if (this.props.isSupported) {
return (
<EuiLink
onClick={this.changeCluster.bind(this)}
data-test-subj="clusterLink"
>
{ this.props.cluster_name }
</EuiLink>
);
}
// not supported because license is basic/not compatible with multi-cluster
if (this.props.license) {
return (
<EuiLink
onClick={this.handleClickIncompatibleLicense.bind(this)}
data-test-subj="clusterLink"
>
{ this.props.cluster_name }
</EuiLink>
);
}
// not supported because license is invalid
return (
<EuiLink
onClick={this.handleClickInvalidLicense.bind(this)}
data-test-subj="clusterLink"
>
{ this.props.cluster_name }
</EuiLink>
);
}
getLicenseInfo() {
if (this.props.license) {
const licenseExpiry = () => {
if (this.props.license.expiry_date_in_millis < moment().valueOf()) {
// license is expired
return (
<span className="monTableCell__clusterCellExpired">
<FormattedMessage
id="xpack.monitoring.cluster.listing.licenseInfo.expiredLabel"
defaultMessage="Expired"
/>
</span>
);
}
// license is fine
const getColumns = (
showLicenseExpiration,
changeCluster,
handleClickIncompatibleLicense,
handleClickInvalidLicense
) => {
return [
{
name: i18n.translate('xpack.monitoring.cluster.listing.nameColumnTitle', {
defaultMessage: 'Name',
}),
field: 'cluster_name',
sortable: true,
render: (value, cluster) => {
if (cluster.isSupported) {
return (
<span>
<FormattedMessage
id="xpack.monitoring.cluster.listing.licenseInfo.expiresLabel"
defaultMessage="Expires {date}"
values={{ date: moment(this.props.license.expiry_date_in_millis).format('D MMM YY') }}
/>
</span>
<EuiLink
onClick={() => changeCluster(cluster.cluster_uuid, cluster.ccs)}
data-test-subj="clusterLink"
>
{ value }
</EuiLink>
);
};
return (
<div>
<div className="monTableCell__clusterCellLiscense">
{ capitalize(this.props.license.type) }
</div>
<div className="monTableCell__clusterCellExpiration">
{ showLicenseExpiration ? licenseExpiry() : null }
</div>
</div>
);
}
// there is no license!
return (
<EuiLink
onClick={this.handleClickInvalidLicense.bind(this)}
>
<EuiHealth color="subdued" data-test-subj="alertIcon">
<FormattedMessage
id="xpack.monitoring.cluster.listing.licenseInfo.notAvailableDescription"
defaultMessage="N/A"
/>
</EuiHealth>
</EuiLink>
);
}
render() {
const isSupported = this.props.isSupported;
const isClusterSupportedFactory = () => {
return (props) => {
if (isSupported) {
return <span>{ props.children }</span>;
}
return <span>-</span>;
};
};
const IsClusterSupported = isClusterSupportedFactory(isSupported);
const classes = [];
if (!isSupported) {
classes.push('basic');
}
/*
* This checks if alerts feature is supported via monitoring cluster
* license. If the alerts feature is not supported because the prod cluster
* license is basic, IsClusterSupported makes the status col hidden
* completely
*/
const IsAlertsSupported = (props) => {
const {
alertsMeta = { enabled: true },
clusterMeta = { enabled: true }
} = props.cluster.alerts;
if (alertsMeta.enabled && clusterMeta.enabled) {
return <span>{ props.children }</span>;
}
const message = alertsMeta.message || clusterMeta.message;
// not supported because license is basic/not compatible with multi-cluster
if (cluster.license) {
return (
<EuiLink
onClick={() => handleClickIncompatibleLicense(cluster.cluster_name)}
data-test-subj="clusterLink"
>
{ value }
</EuiLink>
);
}
// not supported because license is invalid
return (
<Tooltip
text={message}
placement="bottom"
trigger="hover"
<EuiLink
onClick={() => handleClickInvalidLicense(cluster.cluster_name)}
data-test-subj="clusterLink"
>
{ value }
</EuiLink>
);
}
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.statusColumnTitle', {
defaultMessage: 'Status',
}),
field: 'status',
'data-test-subj': 'alertsStatus',
sortable: true,
render: (_status, cluster) => (
<IsClusterSupported {...cluster}>
<IsAlertsSupported cluster={cluster}>
<AlertsIndicator alerts={cluster.alerts} />
</IsAlertsSupported>
</IsClusterSupported>
)
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.nodesColumnTitle', {
defaultMessage: 'Nodes',
}),
field: 'elasticsearch.cluster_stats.nodes.count.total',
'data-test-subj': 'nodesCount',
sortable: true,
render: (total, cluster) => (
<IsClusterSupported {...cluster}>
{ numeral(total).format('0,0') }
</IsClusterSupported>
)
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.indicesColumnTitle', {
defaultMessage: 'Indices',
}),
field: 'elasticsearch.cluster_stats.indices.count',
'data-test-subj': 'indicesCount',
sortable: true,
render: (count, cluster) => (
<IsClusterSupported {...cluster}>
{ numeral(count).format('0,0') }
</IsClusterSupported>
)
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.dataColumnTitle', {
defaultMessage: 'Data',
}),
field: 'elasticsearch.cluster_stats.indices.store.size_in_bytes',
'data-test-subj': 'dataSize',
sortable: true,
render: (size, cluster) => (
<IsClusterSupported {...cluster}>
{ numeral(size).format('0,0[.]0 b')}
</IsClusterSupported>
)
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.logstashColumnTitle', {
defaultMessage: 'Logstash',
}),
field: 'logstash.node_count',
'data-test-subj': 'logstashCount',
sortable: true,
render: (count, cluster) => (
<IsClusterSupported {...cluster}>
{ numeral(count).format('0,0') }
</IsClusterSupported>
)
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.kibanaColumnTitle', {
defaultMessage: 'Kibana',
}),
field: 'kibana.count',
'data-test-subj': 'kibanaCount',
sortable: true,
render: (count, cluster) => (
<IsClusterSupported {...cluster}>
{ numeral(count).format('0,0') }
</IsClusterSupported>
)
},
{
name: i18n.translate('xpack.monitoring.cluster.listing.licenseColumnTitle', {
defaultMessage: 'License',
}),
field: 'license.type',
'data-test-subj': 'clusterLicense',
sortable: true,
render: (licenseType, cluster) => {
const license = cluster.license;
if (license) {
const licenseExpiry = () => {
if (license.expiry_date_in_millis < moment().valueOf()) {
// license is expired
return (
<span className="monTableCell__clusterCellExpired">
Expired
</span>
);
}
// license is fine
return (
<span>
Expires { moment(license.expiry_date_in_millis).format('D MMM YY') }
</span>
);
};
return (
<div>
<div className="monTableCell__clusterCellLiscense">
{ capitalize(licenseType) }
</div>
<div className="monTableCell__clusterCellExpiration">
{ showLicenseExpiration ? licenseExpiry() : null }
</div>
</div>
);
}
// there is no license!
return (
<EuiLink
onClick={() => handleClickInvalidLicense(cluster.cluster_name)}
>
<EuiHealth color="subdued" data-test-subj="alertIcon">
<FormattedMessage
id="xpack.monitoring.cluster.listing.alerts.notAvailableLabel"
defaultMessage="N/A"
/>
N/A
</EuiHealth>
</Tooltip>
</EuiLink>
);
};
return (
<KuiTableRow data-test-subj={`clusterRow_${this.props.cluster_uuid}`}>
<KuiTableRowCell>
<span className="monTableCell__name">
{ this.getClusterAction() }
</span>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="alertsStatus">
<IsClusterSupported>
<IsAlertsSupported cluster={this.props}>
<AlertsIndicator alerts={this.props.alerts} />
</IsAlertsSupported>
</IsClusterSupported>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="nodesCount">
<IsClusterSupported>
{ numeral(get(this.props, 'elasticsearch.cluster_stats.nodes.count.total')).format('0,0') }
</IsClusterSupported>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="indicesCount">
<IsClusterSupported>
{ numeral(get(this.props, 'elasticsearch.cluster_stats.indices.count')).format('0,0') }
</IsClusterSupported>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="dataSize">
<IsClusterSupported>
{ numeral(get(this.props, 'elasticsearch.cluster_stats.indices.store.size_in_bytes')).format('0,0[.]0 b') }
</IsClusterSupported>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="logstashCount">
<IsClusterSupported>
{ numeral(get(this.props, 'logstash.node_count')).format('0,0') }
</IsClusterSupported>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="kibanaCount">
<IsClusterSupported>
{ numeral(get(this.props, 'kibana.count')).format('0,0') }
</IsClusterSupported>
</KuiTableRowCell>
<KuiTableRowCell data-test-subj="clusterLicense">
{ this.getLicenseInfo() }
</KuiTableRowCell>
</KuiTableRow>
);
}
}
];
};
};
const changeCluster = (scope, globalState, kbnUrl, clusterUuid, ccs) => {
scope.$evalAsync(() => {
globalState.cluster_uuid = clusterUuid;
globalState.ccs = ccs;
globalState.save();
kbnUrl.changePath('/overview');
});
};
const licenseWarning = (scope, { title, text }) => {
scope.$evalAsync(() => {
toastNotifications.addWarning({ title, text, 'data-test-subj': 'monitoringLicenseWarning' });
});
};
const handleClickIncompatibleLicense = (scope, clusterName) => {
licenseWarning(scope, {
title: (
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.warningMessageTitle"
defaultMessage="You can't view the {clusterName} cluster"
values={{ clusterName: '"' + clusterName + '"' }}
/>
),
text: (
<Fragment>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.noMultiClusterSupportMessage"
defaultMessage="The Basic license does not support multi-cluster monitoring."
/>
</p>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.infoMessage"
defaultMessage="Need to monitor multiple clusters? {getLicenseInfoLink} to enjoy multi-cluster monitoring."
values={{
getLicenseInfoLink: (
<a href="https://www.elastic.co/subscriptions/xpack" target="_blank">
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.getLicenseLinkLabel"
defaultMessage="Get a license with full functionality"
/>
</a>
)
}}
/>
</p>
</Fragment>
),
});
};
const handleClickInvalidLicense = (scope, clusterName) => {
const licensingPath = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management/home`;
licenseWarning(scope, {
title: (
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.warningMessageTitle"
defaultMessage="You can't view the {clusterName} cluster"
values={{ clusterName: '"' + clusterName + '"' }}
/>
),
text: (
<Fragment>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.invalidInfoMessage"
defaultMessage="The license information is invalid."
/>
</p>
<p>
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.infoMessage"
defaultMessage="Need a license? {getBasicLicenseLink} or {getLicenseInfoLink} to enjoy multi-cluster monitoring."
values={{
getBasicLicenseLink: (
<a href={licensingPath}>
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.getBasicLicenseLinkLabel"
defaultMessage="Get a free Basic license"
/>
</a>
),
getLicenseInfoLink: (
<a href="https://www.elastic.co/subscriptions/xpack" target="_blank">
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.getLicenseLinkLabel"
defaultMessage="get a license with full functionality"
/>
</a>
)
}}
/>
</p>
</Fragment>
),
});
};
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringClusterListing', ($injector, i18n) => {
uiModule.directive('monitoringClusterListing', ($injector) => {
return {
restrict: 'E',
scope: {
clusters: '=',
pageIndex: '=',
sorting: '=',
filterText: '=',
sortKey: '=',
sortOrder: '=',
onNewState: '=',
paginationSettings: '=pagination',
onTableChange: '=',
},
link(scope, $el) {
const globalState = $injector.get('globalState');
const kbnUrl = $injector.get('kbnUrl');
const showLicenseExpiration = $injector.get('showLicenseExpiration');
const filterClustersPlaceholder = i18n('xpack.monitoring.cluster.listing.filterClustersPlaceholder',
{ defaultMessage: 'Filter Clusters…' }
);
const _changeCluster = partial(changeCluster, scope, globalState, kbnUrl);
const _handleClickIncompatibleLicense = partial(handleClickIncompatibleLicense, scope);
const _handleClickInvalidLicense = partial(handleClickInvalidLicense, scope);
const { sorting, pagination, onTableChange } = scope;
scope.$watch('clusters', (clusters = []) => {
const clusterTable = (
<I18nProvider>
<MonitoringTable
className="clusterTable"
rows={clusters}
pageIndex={scope.pageIndex}
filterText={scope.filterText}
sortKey={scope.sortKey}
sortOrder={scope.sortOrder}
onNewState={scope.onNewState}
placeholder={filterClustersPlaceholder}
filterFields={filterFields}
columns={columns}
rowComponent={clusterRowFactory(scope, globalState, kbnUrl, showLicenseExpiration)}
/>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<EuiMonitoringTable
className="clusterTable"
rows={clusters}
columns={getColumns(
showLicenseExpiration,
_changeCluster,
_handleClickIncompatibleLicense,
_handleClickInvalidLicense
)}
rowProps={item => {
return {
'data-test-subj': `clusterRow_${item.cluster_uuid}`
};
}}
sorting={{
...sorting,
sort: {
...sorting.sort,
field: 'cluster_name'
}
}}
pagination={pagination}
search={{
box: {
incremental: true,
placeholder: scope.filterText
},
}}
onTableChange={onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>
);
render(clusterTable, $el[0]);

View file

@ -9,150 +9,156 @@ import numeral from '@elastic/numeral';
import React from 'react';
import { render } from 'react-dom';
import { uiModules } from 'ui/modules';
import {
KuiTableRowCell,
KuiTableRow
} from '@kbn/ui-framework/components';
import { MonitoringTable } from 'plugins/monitoring/components/table';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { MachineLearningJobStatusIcon } from 'plugins/monitoring/components/elasticsearch/ml_job_listing/status_icon';
import { SORT_ASCENDING } from '../../../../common/constants';
import { LARGE_ABBREVIATED, LARGE_BYTES } from '../../../../common/formatting';
import {
EuiLink,
EuiPage,
EuiPageContent,
EuiPageBody,
} from '@elastic/eui';
import { ClusterStatus } from '../../../components/elasticsearch/cluster_status';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
const filterFields = [ 'job_id', 'state', 'node.name' ];
const columns = [
const getColumns = (kbnUrl, scope) => ([
{
title: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.jobIdTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.jobIdTitle', {
defaultMessage: 'Job ID'
}),
sortKey: 'job_id',
sortOrder: SORT_ASCENDING
field: 'job_id',
sortable: true
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.stateTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.stateTitle', {
defaultMessage: 'State'
}),
sortKey: 'state'
field: 'state',
sortable: true,
render: state => (
<div>
<MachineLearningJobStatusIcon status={state} />&nbsp;
{ capitalize(state) }
</div>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.processedRecordsTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.processedRecordsTitle', {
defaultMessage: 'Processed Records'
}),
sortKey: 'data_counts.processed_record_count'
field: 'data_counts.processed_record_count',
sortable: true,
render: value => (
<span>
{numeral(value).format(LARGE_ABBREVIATED)}
</span>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.modelSizeTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.modelSizeTitle', {
defaultMessage: 'Model Size'
}),
sortKey: 'model_size_stats.model_bytes'
field: 'model_size_stats.model_bytes',
sortable: true,
render: value => (
<span>
{numeral(value).format(LARGE_BYTES)}
</span>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.forecastsTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.forecastsTitle', {
defaultMessage: 'Forecasts'
}),
sortKey: 'forecasts_stats.total'
field: 'forecasts_stats.total',
sortable: true,
render: value => (
<span>
{numeral(value).format(LARGE_ABBREVIATED)}
</span>
)
},
{
title: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.nodeTitle', {
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.nodeTitle', {
defaultMessage: 'Node'
}),
sortKey: 'node.name'
}
];
const jobRowFactory = (scope, kbnUrl) => {
const goToNode = nodeId => {
scope.$evalAsync(() => kbnUrl.changePath(`/elasticsearch/nodes/${nodeId}`));
};
const getNode = node => {
if (node) {
field: 'node.name',
sortable: true,
render: (name, node) => {
if (node) {
return (
<EuiLink
onClick={() => {
scope.$evalAsync(() => kbnUrl.changePath(`/elasticsearch/nodes/${node.id}`));
}}
>
{ name }
</EuiLink>
);
}
return (
<EuiLink
onClick={goToNode.bind(null, node.id)}
>
{ node.name }
</EuiLink>
<FormattedMessage
id="xpack.monitoring.elasticsearch.mlJobListing.noDataLabel"
defaultMessage="N/A"
/>
);
}
return i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.noDataLabel', {
defaultMessage: 'N/A'
});
};
return function JobRow(props) {
return (
<KuiTableRow>
<KuiTableRowCell>{ props.job_id }</KuiTableRowCell>
<KuiTableRowCell>
<MachineLearningJobStatusIcon status={props.state} />&nbsp;
{ capitalize(props.state) }
</KuiTableRowCell>
<KuiTableRowCell>{ numeral(props.data_counts.processed_record_count).format(LARGE_ABBREVIATED) }</KuiTableRowCell>
<KuiTableRowCell>{ numeral(props.model_size_stats.model_bytes).format(LARGE_BYTES) }</KuiTableRowCell>
<KuiTableRowCell>{ numeral(props.forecasts_stats.total).format(LARGE_ABBREVIATED) }</KuiTableRowCell>
<KuiTableRowCell>
{ getNode(props.node) }
</KuiTableRowCell>
</KuiTableRow>
);
};
};
}
]);
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringMlListing', (kbnUrl, i18n) => {
uiModule.directive('monitoringMlListing', kbnUrl => {
return {
restrict: 'E',
scope: {
jobs: '=',
pageIndex: '=',
filterText: '=',
sortKey: '=',
sortOrder: '=',
onNewState: '=',
paginationSettings: '=',
sorting: '=',
onTableChange: '=',
status: '=',
},
link(scope, $el) {
const columns = getColumns(kbnUrl, scope);
const getNoDataMessage = filterText => {
if (filterText) {
return (
i18n('xpack.monitoring.elasticsearch.mlJobListing.noFilteredJobsDescription', {
// eslint-disable-next-line max-len
defaultMessage: 'There are no Machine Learning Jobs that match the filter [{filterText}] or the time range. Try changing the filter or time range.',
values: {
filterText: filterText.trim()
}
})
);
}
return i18n('xpack.monitoring.elasticsearch.mlJobListing.noJobsDescription', {
defaultMessage: 'There are no Machine Learning Jobs that match your query. Try changing the time range selection.'
});
};
const filterJobsPlaceholder = i18n('xpack.monitoring.elasticsearch.mlJobListing.filterJobsPlaceholder', {
const filterJobsPlaceholder = i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.filterJobsPlaceholder', {
defaultMessage: 'Filter Jobs…'
});
scope.$watch('jobs', (jobs = []) => {
const mlTable = (
<I18nProvider>
<MonitoringTable
className="mlJobsTable"
rows={jobs}
pageIndex={scope.pageIndex}
filterText={scope.filterText}
sortKey={scope.sortKey}
sortOrder={scope.sortOrder}
onNewState={scope.onNewState}
placeholder={filterJobsPlaceholder}
filterFields={filterFields}
columns={columns}
rowComponent={jobRowFactory(scope, kbnUrl)}
getNoDataMessage={getNoDataMessage}
/>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<ClusterStatus stats={scope.status} />
<EuiMonitoringTable
className="mlJobsTable"
rows={jobs}
columns={columns}
sorting={{
...scope.sorting,
sort: {
...scope.sorting.sort,
field: 'job_id'
}
}}
pagination={scope.paginationSettings}
message={i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.noJobsDescription', {
defaultMessage: 'There are no Machine Learning Jobs that match your query. Try changing the time range selection.'
})}
search={{
box: {
incremental: true,
placeholder: filterJobsPlaceholder
},
}}
onTableChange={scope.onTableChange}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>
);
render(mlTable, $el[0]);

View file

@ -1,42 +0,0 @@
/*
* 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 ReactDOM from 'react-dom';
import { ClusterView } from '../components/clusterView';
import { uiModules } from 'ui/modules';
import { I18nProvider } from '@kbn/i18n/react';
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('clusterView', kbnUrl => {
return {
restrict: 'E',
scope: {
totalCount: '=',
filter: '=',
showing: '=',
labels: '=',
shardStats: '=',
showSystemIndices: '=',
toggleShowSystemIndices: '='
},
link: function (scope, element) {
ReactDOM.render(
<I18nProvider>
<ClusterView
scope={scope}
kbnUrl={kbnUrl}
showSystemIndices={scope.showSystemIndices}
toggleShowSystemIndices={scope.toggleShowSystemIndices}
/>
</I18nProvider>,
element[0]
);
}
};
});

View file

@ -1,57 +0,0 @@
<div class="page-row">
<div class="monCluster">
<h1
class="monClusterTitle"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.shardLegendTitle"
i18n-default-message="Shard Legend"
></h1>
<div class="legend">
<span class="shard">&nbsp;</span>
<span
class="shard-label"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.primaryLabel"
i18n-default-message="Primary"
></span>
<span class="shard replica">&nbsp;</span>
<span
class="shard-label"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.replicaLabel"
i18n-default-message="Replica"
></span>
<span class="shard relocating">&nbsp;</span>
<span
class="shard-label"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.relocatingLabel"
i18n-default-message="Relocating"
></span>
<span class="shard initializing">&nbsp;</span>
<span
class="shard-label"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.initializingLabel"
i18n-default-message="Initializing"
></span>
<span class="shard emergency" ng-if="isIndexView">&nbsp;</span>
<span
class="shard-label"
ng-if="isIndexView"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel"
i18n-default-message="Unassigned Primary"
></span>
<span class="shard unassigned replica" ng-if="isIndexView">&nbsp;</span>
<span
class="shard-label"
ng-if="isIndexView"
i18n-id="xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel"
i18n-default-message="Unassigned Replica"
></span>
</div>
<cluster-view
shard-stats="shardStats"
total-count="totalCount"
labels="labels"
showing="showing"
show-system-indices="showSystemIndices"
toggle-show-system-indices="toggleShowSystemIndices"
></cluster-view>
</div>
</div>

View file

@ -1,45 +0,0 @@
/*
* 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 { uiModules } from 'ui/modules';
import { labels } from './lib/labels';
import { indicesByNodes } from './transformers/indices_by_nodes';
import { nodesByIndices } from './transformers/nodes_by_indices';
import template from './index.html';
const uiModule = uiModules.get('monitoring/directives', []);
uiModule.directive('monitoringShardAllocation', () => {
return {
restrict: 'E',
template,
scope: {
view: '@',
shards: '=',
nodes: '=',
shardStats: '=',
showSystemIndices: '=',
toggleShowSystemIndices: '='
},
link: (scope) => {
const isIndexView = scope.view === 'index';
const transformer = (isIndexView) ? indicesByNodes() : nodesByIndices();
scope.isIndexView = isIndexView;
scope.$watch('shards', (shards) => {
let view = scope.view;
scope.totalCount = shards.length;
scope.showing = transformer(shards, scope.nodes);
if (isIndexView && shards.some((shard) => shard.state === 'UNASSIGNED')) {
view = 'indexWithUnassigned';
}
scope.labels = labels[view];
});
}
};
});

View file

@ -20,4 +20,4 @@
@import 'components/table/index';
@import 'components/logstash/pipeline_viewer/views/index';
@import 'directives/chart/index';
@import 'directives/elasticsearch/shard_allocation/index';
@import 'components/elasticsearch/shard_allocation/index';

View file

@ -1,28 +1,3 @@
<monitoring-main name="alerts">
<div class="page-row">
<p ng-if="alerts.data.message">
{{ alerts.data.message }}
</p>
</div>
<div class="page-row">
<h3
class="kuiScreenReaderOnly"
i18n-id="xpack.monitoring.alerts.alertsTitle"
i18n-default-message="Alerts"
>
</h3>
<monitoring-cluster-alerts-listing alerts="alerts.data"></monitoring-cluster-alerts-listing>
</div>
<div class="page-row">
<p>
<a
kbn-href="#/overview"
i18n-id="xpack.monitoring.alerts.clusterOverviewLinkLabel"
i18n-default-message="« Cluster Overview"
>
</a>
</p>
</div>
<div id="monitoringAlertsApp"></div>
</monitoring-main>

View file

@ -4,13 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { render } from 'react-dom';
import { find, get } from 'lodash';
import uiRoutes from 'ui/routes';
import template from './index.html';
import { MonitoringViewBaseController } from 'plugins/monitoring/views';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { timefilter } from 'ui/timefilter';
import { Alerts } from '../../components/alerts';
import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller';
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui';
function getPageData($injector) {
const globalState = $injector.get('globalState');
@ -44,10 +49,11 @@ uiRoutes.when('/alerts', {
alerts: getPageData
},
controllerAs: 'alerts',
controller: class AlertsView extends MonitoringViewBaseController {
controller: class AlertsView extends MonitoringViewBaseEuiTableController {
constructor($injector, $scope, i18n) {
const $route = $injector.get('$route');
const globalState = $injector.get('globalState');
const kbnUrl = $injector.get('kbnUrl');
// breadcrumbs + page title
$scope.cluster = find($route.current.locals.clusters, { cluster_uuid: globalState.cluster_uuid });
@ -60,6 +66,41 @@ uiRoutes.when('/alerts', {
});
this.data = $route.current.locals.alerts;
const renderReact = data => {
const app = data.message
? (<p>{data.message}</p>)
: (<Alerts
alerts={data}
angular={{ kbnUrl, scope: $scope }}
sorting={this.sorting}
pagination={this.pagination}
onTableChange={this.onTableChange}
/>);
render(
<I18nProvider>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
{app}
<EuiSpacer size="m"/>
<EuiLink
href="#/overview"
>
<FormattedMessage
id="xpack.monitoring.alerts.clusterOverviewLinkLabel"
defaultMessage="« Cluster Overview"
/>
</EuiLink>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>,
document.getElementById('monitoringAlertsApp')
);
};
$scope.$watch(() => this.data, data => renderReact(data));
}
}
});

View file

@ -10,7 +10,7 @@ import uiRoutes from'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import template from './index.html';
import { ApmServerInstances } from '../../../components/apm/instances';
import { MonitoringViewBaseTableController } from '../../base_table_controller';
import { MonitoringViewBaseEuiTableController } from '../..';
import { I18nProvider } from '@kbn/i18n/react';
uiRoutes.when('/apm/instances', {
@ -21,7 +21,7 @@ uiRoutes.when('/apm/instances', {
return routeInit();
},
},
controller: class extends MonitoringViewBaseTableController {
controller: class extends MonitoringViewBaseEuiTableController {
constructor($injector, $scope, i18n) {
const $route = $injector.get('$route');
const globalState = $injector.get('globalState');
@ -51,22 +51,18 @@ uiRoutes.when('/apm/instances', {
renderReact(data) {
const {
pageIndex,
filterText,
sortKey,
sortOrder,
onNewState,
pagination,
sorting,
onTableChange,
} = this;
const component = (
<I18nProvider>
<ApmServerInstances
apms={{
pageIndex,
filterText,
sortKey,
sortOrder,
onNewState,
pagination,
sorting,
onTableChange,
data,
}}
/>

View file

@ -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 { MonitoringViewBaseController } from './';
import { euiTableStorageGetter, euiTableStorageSetter } from 'plugins/monitoring/components/table';
import { EUI_SORT_ASCENDING } from '../../common/constants';
/**
* Class to manage common instantiation behaviors in a view controller
* And add persistent state to a table:
* - page index: in table pagination, which page are we looking at
* - filter text: what filter was entered in the table's filter bar
* - sortKey: which column field of table data is used for sorting
* - sortOrder: is sorting ordered ascending or descending
*
* This is expected to be extended, and behavior enabled using super();
*/
export class MonitoringViewBaseEuiTableController extends MonitoringViewBaseController {
/**
* Create a table view controller
* - used by parent class:
* @param {String} title - Title of the page
* @param {Function} getPageData - Function to fetch page data
* @param {Service} $injector - Angular dependency injection service
* @param {Service} $scope - Angular view data binding service
* @param {Boolean} options.enableTimeFilter - Whether to show the time filter
* @param {Boolean} options.enableAutoRefresh - Whether to show the auto refresh control
* - specific to this class:
* @param {String} storageKey - the namespace that will be used to keep the state data in the Monitoring localStorage object
*
*/
constructor(args) {
super(args);
const { storageKey, $injector } = args;
const storage = $injector.get('localStorage');
const getLocalStorageData = euiTableStorageGetter(storageKey);
const setLocalStorageData = euiTableStorageSetter(storageKey);
const { page, sort } = getLocalStorageData(storage);
this.pagination = page || {
initialPageSize: 20,
pageSizeOptions: [5, 10, 20, 50]
};
this.sorting = sort || {
sort: {
field: 'name',
direction: EUI_SORT_ASCENDING
}
};
this.onTableChange = ({ page, sort }) => {
setLocalStorageData(storage, {
page,
sort: {
sort
}
});
};
}
}

View file

@ -50,5 +50,4 @@ export class MonitoringViewBaseTableController extends MonitoringViewBaseControl
setLocalStorageData(storage, newState);
};
}
}

View file

@ -1,18 +1,8 @@
<monitoring-main name="listing">
<div class="page-row">
<h3
class="kuiScreenReaderOnly"
i18n-id="xpack.monitoring.cluster.listing.clustersTitle"
i18n-default-message="Clusters"
>
</h3>
<monitoring-cluster-listing
page-index="clusters.pageIndex"
filter-text="clusters.filterText"
sort-key="clusters.sortKey"
sort-order="clusters.sortOrder"
on-new-state="clusters.onNewState"
clusters="clusters.data"
></monitoring-cluster-listing>
</div>
<monitoring-cluster-listing
pagination-settings="clusters.pagination"
sorting="clusters.sorting"
on-table-change="clusters.onTableChange"
clusters="clusters.data"
></monitoring-cluster-listing>
</monitoring-main>

View file

@ -6,7 +6,7 @@
import uiRoutes from 'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { MonitoringViewBaseTableController } from '../../';
import { MonitoringViewBaseEuiTableController } from '../../';
import template from './index.html';
const getPageData = $injector => {
@ -35,7 +35,7 @@ uiRoutes.when('/home', {
}
},
controllerAs: 'clusters',
controller: class ClustersList extends MonitoringViewBaseTableController {
controller: class ClustersList extends MonitoringViewBaseEuiTableController {
constructor($injector, $scope) {
super({

View file

@ -50,7 +50,9 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
this.renderReact = (props) => {
super.renderReact(
<I18nProvider><CcrShard {...props} /> </I18nProvider>
<I18nProvider>
<CcrShard {...props} />
</I18nProvider>
);
};
}

View file

@ -4,21 +4,5 @@
resolver="{{ indexName }}"
page="advanced"
>
<monitoring-index-summary summary="pageData.indexSummary"></monitoring-index-summary>
<div class="page-row">
<div class="row" ng-if="pageData.metrics">
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_1"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_2"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_3"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_4"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_total"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_time"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_refresh"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_throttling"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_disk"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_segment_count"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_latency"/></div>
</div>
</div>
<div id="monitoringElasticsearchAdvancedIndexApp"></div>
</monitoring-main>

View file

@ -7,12 +7,17 @@
/**
* Controller for Advanced Index Detail
*/
import React from 'react';
import { render } from 'react-dom';
import { find } from 'lodash';
import uiRoutes from 'ui/routes';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import template from './index.html';
import { timefilter } from 'ui/timefilter';
import { AdvancedIndex } from '../../../../components/elasticsearch/index/advanced';
import { I18nProvider } from '@kbn/i18n/react';
import moment from 'moment';
function getPageData($injector) {
const globalState = $injector.get('globalState');
@ -75,5 +80,28 @@ uiRoutes.when('/elasticsearch/indices/:index/advanced', {
$executor.start($scope);
$scope.$on('$destroy', $executor.destroy);
function onBrush({ xaxis }) {
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute',
});
}
this.renderReact = () => {
render(
<I18nProvider>
<AdvancedIndex
indexSummary={$scope.pageData.indexSummary}
metrics={$scope.pageData.metrics}
onBrush={onBrush}
/>
</I18nProvider>,
document.getElementById('monitoringElasticsearchAdvancedIndexApp')
);
};
$scope.$watch('pageData', this.renderReact);
}
});

View file

@ -5,24 +5,5 @@
resolver="{{ indexName }}"
page="overview"
>
<monitoring-index-summary summary="pageData.indexSummary"></monitoring-index-summary>
<div class="page-row">
<div class="row" ng-if="pageData.metrics">
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_mem"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_size"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_search_request_rate"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_request_rate"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_segment_count"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.index_document_count"></monitoring-chart></div>
</div>
</div>
<monitoring-shard-allocation
id="indexName"
view="index"
nodes="pageData.nodes"
shards="pageData.shards"
shard-stats="pageData.shardStats"
></monitoring-shard-allocation>
<div id="monitoringElasticsearchIndexApp"></div>
</monitoring-main>

View file

@ -7,12 +7,19 @@
/**
* Controller for single index detail
*/
import React from 'react';
import { render } from 'react-dom';
import { find } from 'lodash';
import moment from 'moment';
import uiRoutes from 'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import template from './index.html';
import { timefilter } from 'ui/timefilter';
import { I18nProvider } from '@kbn/i18n/react';
import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
import { indicesByNodes } from '../../../components/elasticsearch/shard_allocation/transformers/indices_by_nodes';
import { Index } from '../../../components/elasticsearch/index/index';
function getPageData($injector) {
const $http = $injector.get('$http');
@ -51,6 +58,7 @@ uiRoutes.when('/elasticsearch/indices/:index', {
timefilter.enableAutoRefreshSelector();
const $route = $injector.get('$route');
const kbnUrl = $injector.get('kbnUrl');
const globalState = $injector.get('globalState');
$scope.cluster = find($route.current.locals.clusters, { cluster_uuid: globalState.cluster_uuid });
$scope.pageData = $route.current.locals.pageData;
@ -75,5 +83,39 @@ uiRoutes.when('/elasticsearch/indices/:index', {
$executor.start($scope);
$scope.$on('$destroy', $executor.destroy);
function onBrush({ xaxis }) {
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute',
});
}
const transformer = indicesByNodes();
this.renderReact = () => {
const shards = $scope.pageData.shards;
$scope.totalCount = shards.length;
$scope.showing = transformer(shards, $scope.pageData.nodes);
if (shards.some((shard) => shard.state === 'UNASSIGNED')) {
$scope.labels = labels.indexWithUnassigned;
} else {
$scope.labels = labels.index;
}
render(
<I18nProvider>
<Index
scope={$scope}
kbnUrl={kbnUrl}
onBrush={onBrush}
{...$scope.pageData}
/>
</I18nProvider>,
document.getElementById('monitoringElasticsearchIndexApp')
);
};
$scope.$watch('pageData', this.renderReact);
}
});

View file

@ -8,7 +8,7 @@ import React from 'react';
import { find } from 'lodash';
import uiRoutes from 'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { MonitoringViewBaseTableController } from '../../';
import { MonitoringViewBaseEuiTableController } from '../../';
import { ElasticsearchIndices } from '../../../components';
import template from './index.html';
import { I18nProvider } from '@kbn/i18n/react';
@ -22,7 +22,7 @@ uiRoutes.when('/elasticsearch/indices', {
}
},
controllerAs: 'elasticsearchIndices',
controller: class ElasticsearchIndicesController extends MonitoringViewBaseTableController {
controller: class ElasticsearchIndicesController extends MonitoringViewBaseEuiTableController {
constructor($injector, $scope, i18n) {
const $route = $injector.get('$route');
const globalState = $injector.get('globalState');
@ -69,6 +69,9 @@ uiRoutes.when('/elasticsearch/indices', {
indices={indices}
showSystemIndices={showSystemIndices}
toggleShowSystemIndices={toggleShowSystemIndices}
sorting={this.sorting}
pagination={this.pagination}
onTableChange={this.onTableChange}
/>
</I18nProvider>
);

View file

@ -1,18 +1,9 @@
<monitoring-main product="elasticsearch" name="ml">
<monitoring-cluster-status-elasticsearch status="mlJobs.data.clusterStatus"></monitoring-cluster-status-elasticsearch>
<div class="page-row">
<h3
class="kuiScreenReaderOnly"
i18n-id="xpack.monitoring.elasticsearch.mlJobsTitle"
i18n-default-message="Machine Learning Jobs"
></h3>
<monitoring-ml-listing
page-index="mlJobs.pageIndex"
filter-text="mlJobs.filterText"
sort-key="mlJobs.sortKey"
sort-order="mlJobs.sortOrder"
on-new-state="mlJobs.onNewState"
jobs="mlJobs.data.rows"
></monitoring-ml-listing>
</div>
<monitoring-ml-listing
paginationSettings="mlJobs.pagination"
sorting="mlJobs.sorting"
on-table-change="mlJobs.onTableChange"
jobs="mlJobs.data.rows"
status="mlJobs.data.clusterStatus"
></monitoring-ml-listing>
</monitoring-main>

View file

@ -7,7 +7,7 @@
import { find } from 'lodash';
import uiRoutes from 'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { MonitoringViewBaseTableController } from '../../';
import { MonitoringViewBaseEuiTableController } from '../../';
import { getPageData } from './get_page_data';
import template from './index.html';
@ -21,7 +21,7 @@ uiRoutes.when('/elasticsearch/ml_jobs', {
pageData: getPageData
},
controllerAs: 'mlJobs',
controller: class MlJobsList extends MonitoringViewBaseTableController {
controller: class MlJobsList extends MonitoringViewBaseEuiTableController {
constructor($injector, $scope, i18n) {
super({

View file

@ -7,25 +7,5 @@
tab-icon-class="{{ pageData.nodeSummary.nodeTypeClass }}"
tab-icon-class="{{ pageData.nodeSummary.nodeTypeLabel }}"
>
<monitoring-node-summary node="pageData.nodeSummary"></monitoring-node-summary>
<div class="page-row">
<div class="row" ng-if="pageData.metrics">
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_gc"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_gc_time"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_jvm_mem"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_cpu_utilization"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_index_1"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_index_2"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_index_3"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_index_4"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_index_time"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_request_total"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_index_threads"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_read_threads"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_cgroup_cpu"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_cgroup_stats"/></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_latency"/></div>
</div>
</div>
<div id="monitoringElasticsearchAdvancedNodeApp"></div>
</monitoring-main>

View file

@ -7,12 +7,17 @@
/**
* Controller for Advanced Node Detail
*/
import React from 'react';
import { render } from 'react-dom';
import { find } from 'lodash';
import uiRoutes from 'ui/routes';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import template from './index.html';
import { timefilter } from 'ui/timefilter';
import { I18nProvider } from '@kbn/i18n/react';
import { AdvancedNode } from '../../../../components/elasticsearch/node/advanced';
import moment from 'moment';
function getPageData($injector) {
const $http = $injector.get('$http');
@ -76,5 +81,28 @@ uiRoutes.when('/elasticsearch/nodes/:node/advanced', {
$executor.start($scope);
$scope.$on('$destroy', $executor.destroy);
function onBrush({ xaxis }) {
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute',
});
}
this.renderReact = () => {
render(
<I18nProvider>
<AdvancedNode
nodeSummary={$scope.pageData.nodeSummary}
metrics={$scope.pageData.metrics}
onBrush={onBrush}
/>
</I18nProvider>,
document.getElementById('monitoringElasticsearchAdvancedNodeApp')
);
};
$scope.$watch('pageData', this.renderReact);
}
});

View file

@ -7,25 +7,5 @@
tab-icon-class="{{ pageData.nodeSummary.nodeTypeClass }}"
tab-icon-class="{{ pageData.nodeSummary.nodeTypeLabel }}"
>
<monitoring-node-summary node="pageData.nodeSummary"></monitoring-node-summary>
<div class="page-row">
<div class="row" ng-if="pageData.metrics">
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_jvm_mem"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_mem"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_cpu_metric"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_load_average"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_latency"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.node_segment_count"></monitoring-chart></div>
</div>
</div>
<monitoring-shard-allocation
view="node"
nodes="pageData.nodes"
shards="pageData.shards"
shard-stats="pageData.shardStats"
show-system-indices="showSystemIndices"
toggle-show-system-indices="toggleShowSystemIndices"
></monitoring-shard-allocation>
<div id="monitoringElasticsearchNodeApp"></div>
</monitoring-main>

View file

@ -7,12 +7,19 @@
/**
* Controller for Node Detail
*/
import React from 'react';
import { render } from 'react-dom';
import { find, partial } from 'lodash';
import uiRoutes from 'ui/routes';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { getPageData } from './get_page_data';
import template from './index.html';
import { timefilter } from 'ui/timefilter';
import { Node } from '../../../components/elasticsearch/node/node';
import { I18nProvider } from '@kbn/i18n/react';
import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices';
import moment from 'moment';
uiRoutes.when('/elasticsearch/nodes/:node', {
template,
@ -28,6 +35,7 @@ uiRoutes.when('/elasticsearch/nodes/:node', {
timefilter.enableAutoRefreshSelector();
const $route = $injector.get('$route');
const kbnUrl = $injector.get('kbnUrl');
const globalState = $injector.get('globalState');
$scope.cluster = find($route.current.locals.clusters, { cluster_uuid: globalState.cluster_uuid });
$scope.pageData = $route.current.locals.pageData;
@ -65,5 +73,35 @@ uiRoutes.when('/elasticsearch/nodes/:node', {
$executor.start($scope);
$scope.$on('$destroy', $executor.destroy);
function onBrush({ xaxis }) {
timefilter.setTime({
from: moment(xaxis.from),
to: moment(xaxis.to),
mode: 'absolute',
});
}
const transformer = nodesByIndices();
this.renderReact = () => {
const shards = $scope.pageData.shards;
$scope.totalCount = shards.length;
$scope.showing = transformer(shards, $scope.pageData.nodes);
$scope.labels = labels.node;
render(
<I18nProvider>
<Node
scope={$scope}
kbnUrl={kbnUrl}
onBrush={onBrush}
{...$scope.pageData}
/>
</I18nProvider>,
document.getElementById('monitoringElasticsearchNodeApp')
);
};
$scope.$watch('pageData', this.renderReact);
}
});

View file

@ -9,7 +9,7 @@ import { find } from 'lodash';
import uiRoutes from 'ui/routes';
import template from './index.html';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import { MonitoringViewBaseTableController } from '../../';
import { MonitoringViewBaseEuiTableController } from '../../';
import { ElasticsearchNodes } from '../../../components';
import { I18nProvider } from '@kbn/i18n/react';
@ -22,7 +22,7 @@ uiRoutes.when('/elasticsearch/nodes', {
}
},
controllerAs: 'elasticsearchNodes',
controller: class ElasticsearchNodesController extends MonitoringViewBaseTableController {
controller: class ElasticsearchNodesController extends MonitoringViewBaseEuiTableController {
constructor($injector, $scope, i18n) {
const $route = $injector.get('$route');
const globalState = $injector.get('globalState');
@ -55,6 +55,9 @@ uiRoutes.when('/elasticsearch/nodes', {
clusterStatus={clusterStatus}
nodes={nodes}
showCgroupMetricsElasticsearch={showCgroupMetricsElasticsearch}
sorting={this.sorting}
pagination={this.pagination}
onTableChange={this.onTableChange}
/>
</I18nProvider>
);

View file

@ -6,3 +6,4 @@
export { MonitoringViewBaseController } from './base_controller';
export { MonitoringViewBaseTableController } from './base_table_controller';
export { MonitoringViewBaseEuiTableController } from './base_eui_table_controller';

View file

@ -4,15 +4,5 @@
instance="{{ pageData.kibanaSummary.name }}"
data-test-subj="kibanaInstancePage"
>
<monitoring-kibana-summary kibana="pageData.kibanaSummary"></monitoring-kibana-summary>
<div class="page-row">
<div class="row" ng-if="pageData.metrics">
<div class="col-md-6"><monitoring-chart series="pageData.metrics.kibana_requests"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.kibana_response_times"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.kibana_memory"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.kibana_average_concurrent_connections"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.kibana_os_load"></monitoring-chart></div>
<div class="col-md-6"><monitoring-chart series="pageData.metrics.kibana_process_delay"></monitoring-chart></div>
</div>
</div>
<div id="monitoringKibanaInstanceApp"></div>
</monitoring-main>

View file

@ -7,12 +7,18 @@
/*
* Kibana Instance
*/
import React from 'react';
import { render } from 'react-dom';
import { get, find } from 'lodash';
import uiRoutes from'ui/routes';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import template from './index.html';
import { timefilter } from 'ui/timefilter';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { MonitoringTimeseriesContainer } from '../../../components/chart';
import { DetailStatus } from 'plugins/monitoring/components/kibana/detail_status';
import { I18nProvider } from '@kbn/i18n/react';
function getPageData($injector) {
const $http = $injector.get('$http');
@ -45,7 +51,7 @@ uiRoutes.when('/kibana/instances/:uuid', {
},
pageData: getPageData
},
controller($injector, $scope, i18n) {
controller($injector, $scope) {
timefilter.enableTimeRangeSelector();
timefilter.enableAutoRefreshSelector();
@ -55,14 +61,7 @@ uiRoutes.when('/kibana/instances/:uuid', {
$scope.pageData = $route.current.locals.pageData;
const title = $injector.get('title');
const routeTitle = i18n('xpack.monitoring.kibana.instance.routeTitle', {
defaultMessage: 'Kibana - {kibanaSummaryName}',
values: {
kibanaSummaryName: get($scope.pageData, 'kibanaSummary.name')
}
});
title($scope.cluster, routeTitle);
title($scope.cluster, `Kibana - ${get($scope.pageData, 'kibanaSummary.name')}`);
const $executor = $injector.get('$executor');
$executor.register({
@ -73,5 +72,66 @@ uiRoutes.when('/kibana/instances/:uuid', {
$executor.start($scope);
$scope.$on('$destroy', $executor.destroy);
$scope.$watch('pageData', renderReact);
renderReact();
function renderReact() {
const app = document.getElementById('monitoringKibanaInstanceApp');
if (!app) {
return;
}
const overviewPage = (
<I18nProvider>
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<DetailStatus stats={$scope.pageData.kibanaSummary} />
<EuiSpacer size="m"/>
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={$scope.pageData.metrics.kibana_requests}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={$scope.pageData.metrics.kibana_response_times}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={$scope.pageData.metrics.kibana_memory}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={$scope.pageData.metrics.kibana_average_concurrent_connections}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={$scope.pageData.metrics.kibana_os_load}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={$scope.pageData.metrics.kibana_process_delay}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>
);
render(overviewPage, app);
}
}
});

View file

@ -3,19 +3,5 @@
name="kibanas"
data-test-subj="kibanaInstancesPage"
>
<monitoring-cluster-status-kibana status="kibanas.data.clusterStatus"></monitoring-cluster-status-kibana>
<div class="page-row">
<h3 class="kuiScreenReaderOnly"
i18n-id="xpack.monitoring.kibana.instancesTitle"
i18n-default-message="Kibana Instances"
></h3>
<monitoring-kibana-listing
page-index="kibanas.pageIndex"
filter-text="kibanas.filterText"
sort-key="kibanas.sortKey"
sort-order="kibanas.sortOrder"
on-new-state="kibanas.onNewState"
instances="kibanas.data.kibanas"
></monitoring-kibana-listing>
</div>
<div id="monitoringKibanaInstancesApp"></div>
</monitoring-main>

Some files were not shown because too many files have changed in this diff Show more